SPI function for K66F

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

SPI function for K66F

2,997 Views
constantineulen
Contributor II

Hi everybody,

I am planning to establish a wireless connection between 2 K66F boards using nrf24L01+ modules. Therefore I plan on using the nrf24l01 library. It is a generic code except for the SPI and delay functions.

The SPI function is supposed to take the argument unsigned char byte and sent it through the SPI port to the 24L01. Then, it should wait until the 24L01 has returned its response over SPI. This received byte should be the return value of the function.

You find this kind of SPI function and the corresponding libraries for AVR-chips and other microcontrollers.

So I was wondering if anybody has already written a similar SPI function for the K66F (or one that is usable with my board) and would be willing to share it with me.

Thanks in advance and best regards,

Constantin

9 Replies

2,214 Views
mjbcswitzerland
Specialist V

Hi Constantin

The uTasker project supports nRF24L01P on the FRDM-K66F. You can get the setup for the open source version at the links below.
In fact the only difference to the FRDM-K64F board (in case you have a reference for that) is that the Tx enable signal is connected on PTB20 rather than PTC12 - the SPI interface is identical and thus needs no modification.

As reference the complete configuration for FRDMK64F and FRDM-K66F is shown here:

    #define nRF24L01P_SLAVE()    (_READ_PORT_MASK(B, PORTB_BIT19))       // K64 J1-3/K66 J2-4 held to ground selects slave mode
    #define nRF24L01P_CS         PORTD_BIT4                              // SPI chip select (active low)
    #define nRF24L01P_DOUT       PORTD_BIT6                              // SPI data to the nRF24L01+
    #define nRF24L01P_SCLK       PORTD_BIT5                              // SPI clock to the nRF24L01+
    #define nRF24L01P_DIN        PORTD_BIT7                              // SPI data from the nRF24L01+
    #define nRF24L01P_IRQ        PORTC_BIT18                             // maskable interrupt pin from the nRF24L01+, active low
    #define nRF24L01P_IRQ_PORT   PORTC
    #define nRF24L01P_IRQ_PRIORITY PRIORITY_PORT_C_INT
    #if defined FRDM_K66F
        #define nRF24L01P_TX_ENABLE  PORTB_BIT20                         // chip enable activates rx or tx mode
        #define CONFIGURE_INTERFACE_nRF24L01() _CONFIG_PORT_INPUT_FAST_HIGH(B, (PORTB_BIT19), PORT_PS_UP_ENABLE); \
                                 _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_HIGH(B, nRF24L01P_TX_ENABLE, 0, (PORT_SRE_SLOW | PORT_DSE_LOW));\
                                 _CONFIG_PERIPHERAL(D, 4, (PD_4_SPI1_PCS0 | PORT_DSE_HIGH | PORT_PS_UP_ENABLE | PORT_SRE_FAST));\
                                 _CONFIG_PERIPHERAL(D, 6, (PD_6_SPI1_SOUT | PORT_DSE_HIGH | PORT_PS_UP_ENABLE | PORT_SRE_FAST));\
                                 _CONFIG_PERIPHERAL(D, 7, (PD_7_SPI1_SIN | PORT_PS_UP_ENABLE));\
                                 _CONFIG_PERIPHERAL(D, 5, (PD_5_SPI1_SCK  | PORT_DSE_HIGH | PORT_PS_UP_ENABLE | PORT_SRE_FAST))
    #else
        #define nRF24L01P_TX_ENABLE  PORTC_BIT12                         // chip enable activates rx or tx mode
        #define CONFIGURE_INTERFACE_nRF24L01() _CONFIG_PORT_INPUT_FAST_HIGH(B, (PORTB_BIT19), PORT_PS_UP_ENABLE); \
                                 _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(C, nRF24L01P_TX_ENABLE, 0, (PORT_SRE_SLOW | PORT_DSE_LOW));\
                                 _CONFIG_PERIPHERAL(D, 4, (PD_4_SPI1_PCS0 | PORT_DSE_HIGH | PORT_PS_UP_ENABLE | PORT_SRE_FAST));\
                                 _CONFIG_PERIPHERAL(D, 6, (PD_6_SPI1_SOUT | PORT_DSE_HIGH | PORT_PS_UP_ENABLE | PORT_SRE_FAST));\
                                 _CONFIG_PERIPHERAL(D, 7, (PD_7_SPI1_SIN | PORT_PS_UP_ENABLE));\
                                 _CONFIG_PERIPHERAL(D, 5, (PD_5_SPI1_SCK  | PORT_DSE_HIGH | PORT_PS_UP_ENABLE | PORT_SRE_FAST))
    #endif

    #define CONFIGURE_nRF24L01_SPI_MODE() POWER_UP_ATOMIC(6, SPI1); \
                                 SPI1_MCR = (SPI_MCR_MSTR | SPI_MCR_DCONF_SPI | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS_CS0 | SPI_MCR_PCSIS_CS1 | SPI_MCR_PCSIS_CS2 | SPI_MCR_PCSIS_CS3 | SPI_MCR_PCSIS_CS4 | SPI_MCR_PCSIS_CS5);\
                                 SPI1_CTAR0 = (SPI_CTAR_ASC_10 | SPI_CTAR_PBR_3 | SPI_CTAR_DBR | SPI_CTAR_FMSZ_8 | SPI_CTAR_PDT_7 | /*SPI_CTAR_BR_4*/SPI_CTAR_BR_2048 /*| SPI_CTAR_CPHA | SPI_CTAR_CPOL*/); // for 120MHz system, 10MHz speed

    #define FLUSH_nRF24L01_SPI_FIFO_AND_FLAGS()  SPI1_MCR |= SPI_MCR_CLR_RXF; SPI1_SR = (SPI_SR_EOQF | SPI_SR_TFUF | SPI_SR_TFFF | SPI_SR_RFOF | SPI_SR_RFDF)

    #define WRITE_nRF24L01_SPI(byte)            SPI1_PUSHR = (byte | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0) // write a single byte to the output FIFO - assert CS line
    #define WRITE_nRF24L01_SPI_LAST(byte)       SPI1_PUSHR = (byte | SPI_PUSHR_EOQ  | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0) // write final byte to output FIFO - this will negate the CS line when complete
    #define READ_nRF24L01_SPI_FLASH_DATA()      (unsigned char)SPI1_POPR
    #define WAIT_nRF24L01_SPI_RECEPTION_END()   while ((SPI1_SR & SPI_SR_RFDF) == 0) {}
    #define CLEAR_nRF24L01_SPI_RECEPTION_FLAG() SPI1_SR |= SPI_SR_RFDF

    #define SPI_FIFO_DEPTH_1                                             // K64/K66 SPI 1 has a FIFO depth of just 1 in SPI1 and SPI 2
    #if defined FRDM_K66F
        #define DISABLE_RX_TX()  _CLEARBITS(B, nRF24L01P_TX_ENABLE); fnRemoteSimulationInterface(REMOTE_RF_INTERFACE, REMOTE_RF_DISABLE_RX_TX, 0, 0, 0)
        #define ENABLE_RX_TX()   _SETBITS(B, nRF24L01P_TX_ENABLE);   fnRemoteSimulationInterface(REMOTE_RF_INTERFACE, REMOTE_RF_ENABLE_RX_TX,  0, 0, 0)
    #else
        #define DISABLE_RX_TX()  _CLEARBITS(C, nRF24L01P_TX_ENABLE); fnRemoteSimulationInterface(REMOTE_RF_INTERFACE, REMOTE_RF_DISABLE_RX_TX, 0, 0, 0)
        #define ENABLE_RX_TX()   _SETBITS(C, nRF24L01P_TX_ENABLE);   fnRemoteSimulationInterface(REMOTE_RF_INTERFACE, REMOTE_RF_ENABLE_RX_TX,  0, 0, 0)
    #endif‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍


Regards

Mark

Kinetis: http://www.utasker.com/kinetis.html
Kinetis K66:
- http://www.utasker.com/kinetis/TWR-K65F180M.html
- http://www.utasker.com/kinetis/FRDM-K66F.html
- http://www.utasker.com/kinetis/TEENSY_3.6.html

Free Open Source solution: https://github.com/uTasker/uTasker-Kinetis
Working project in 15 minutes video: https://youtu.be/K8ScSgpgQ6M

For better, faster, cheaper product developments consider the uTasker developer's version, professional Kinetis support, one-on-one training and complete fast-track project solutions to set you apart from the herd : http://www.utasker.com/support.html

2,214 Views
constantineulen
Contributor II

Hi Mark,

thank you for your answer ! I am working on this project with another person who is going to implement the code for the receiver on another board. Therefore we both want to use the same libraries with the same commands (see attached). So I don't think I can use uTasker.

Thanks anyway !

0 Kudos
Reply

2,214 Views
mjbcswitzerland
Specialist V

Hi Constantin

Attached is the nRF24201.c file from the uTasker project which does the same as the code in your file. It supports both transmitter and receiver modes.

If you need a quick working solution with nRF2401, including USB, Ethernet, SD card etc. you can just download the open source project and immediately build the complete operation.

If you don't have any urgency and only need the nRF2401 without any requirements for using the other features of the K66 you can also analyse the functions and the headers in order to work out how to interface to the HW in your original library code too; essentially is is a case of inserting the HW operations shown in the header into the interface routines that are empty in your library.

Regards

Mark

0 Kudos
Reply

2,214 Views
mjbcswitzerland
Specialist V

Hi

I have attached a binary file that can be loaded to the FRDM-K66F boards. Connect an nRF24201 module to each on the 8 pin RF/WIFI connector.

The board will be a primary device (master) unless there is a connection between GND and J2-4, therefore set one of the two boards to secondary (slave) mode.

The primary device will now send a test message (10 bytes of content 0,1,2,3,4,5,6,7,8,9) ever 5 seconds.

Connect the OpenSDA VCOM at 115'200 Baud for debug interface (or connect the K66's HS USB, which will also appear as a VCOM with the same debug interface) and the primary device does:

nRF24L01P+ master
Eth0 link-up 100 full-duplex
Ping RF                                 <----------------- sends a broadcast message
No ACK ;-(                            <----------------- it is normal that no ACK is received from a broadcast
Ping RF                                 <----------------- sends a multicast message
No ACK ;-(                             <----------------- it is normal that no ACK is received from a multicast
Ping RF                                 <----------------- sends a message addressed to the slave
DATA ACKED!!                      <---------------- ACK is received
Rx data available - pipe 0 : 5   <-------------- Slave echoes the first 5 bytes of data back
 0x01 0x02 0x03 0x04 0x05    <-------------- The received data (from slave) is displayed
.. repeats.

At the board that is configured as slave:

nRF24L01P+ slave
Eth0 link-up 100 full-duplex

Rx data available - pipe 1 : 10   <-----------------This is the reception  of the broadcast (which doesn't get acknowledge)
 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a   <--- the data that is received is displayed
Rx data available - pipe 1 : 10    <--------------- This is the reception on the slave's address
 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a   <--- the data that is received is displayed
DATA ACKED!!                           <--- this is the acknowledgement to the data that the slave echoed back
.. repeats

To get an idea of how the address is controlled and the data sent, this is the routine that is called every 5s at the primary device and cycles through the three addressing types:

extern void fnTest_nRF24L01_Write(int iPingPong)
{
    static const unsigned char ucTestData[] = {1,2,3,4,5,6,7,8,9,10};
    switch (iPingPong) {
    case 0:
        fnWrite_nRF24L01((nRF24L01_TX_ADDR | W_REGISTER), (unsigned char *)cAddress, sizeof(cAddress)); // set the address for transmission
        fnData_nRF24L01((unsigned char *)ucTestData, sizeof(ucTestData), W_TX_PAYLOAD); // send test transmission
        break;
    case 1:
        fnWrite_nRF24L01((nRF24L01_TX_ADDR | W_REGISTER), (unsigned char *)cBroadcastAddress, sizeof(cBroadcastAddress)); // set the address for transmission
        fnData_nRF24L01((unsigned char *)ucTestData, sizeof(ucTestData), W_ACK_PAYLOAD_NOACK); // send test transmission
        break;
    case 2:
        fnWrite_nRF24L01((nRF24L01_TX_ADDR | W_REGISTER), (unsigned char *)cMulticastAddress, sizeof(cMulticastAddress)); // set the address for transmission
        fnData_nRF24L01((unsigned char *)ucTestData, sizeof(ucTestData), W_ACK_PAYLOAD_NOACK); // send test transmission
        break;
    }
}

To make it a bit more interesting, there is also a TCP/IP stack on the Ethernet interface of the board with web server and telnet (the same command line interface can also be accessed via Telnet). It also has an MQTT client which can be controlled in the MQTT menu (on the command line interface).

If the slave device is connected to a remote MQTT broker (such as "test.mosquitto.org" - use the command "mqtt_con 37.187.106.16" to connect to this one) it will publish the data that it receives to a topic called "nRF24201" at the broker. All other devices subscribed to this topic (anywhere in the world) will then also receive the same data!

Regards

Mark

0 Kudos
Reply

2,214 Views
constantineulen
Contributor II

Hi Kerry,

Thank you for your fast response! I have already downloaded the SDK Builder for my board and the corresponding drivers. Nevertheless I don't find any function which would fulfill the criteria I am looking for (neither in the fsl_dspi-files nor in fsl_dspi_cmsis). How should I port the 24L01 code to the K66F platform since the generic 24L01 code depends on this one function (which I don't find in the driver codes)? Is the only possibility to write the code myself (which I am not sure to be capable of) ? 

Best,

Constantin

0 Kudos
Reply

2,214 Views
troelsoesteraa
Contributor III

The SDKs are confusing for the simple reason that they are generic and try to be as complete as possible, e.g. from looking at it, it also contains routines for SPI slave modes, which you have no use for in your application. A good example of "generic-ism" is the function "DSPI_MasterSetDelayScaler". While it's intention is good, you can write what you need by a single line of code, because you are targetting a specific SPI device.

And with some help, I'm sure you'll be able to do it from scratch. In that way you understand each line of the code and can maintain it if something goes wrong, or you need to expand it with further SPI devices. :smileyhappy:

Have you already setup the port configuration of the K66 to SPI for the specific pins you need (CSN, SCK, MOSI, MISO)?

The data sheet of the device is presumably this one: https://www.sparkfun.com/datasheets/Components/nRF24L01_prelim_prod_spec_1_2.pdf

?

0 Kudos
Reply

2,214 Views
constantineulen
Contributor II

Hi,

Thank you so much for your help! 

First of all I will be using the attached libraries for nrf24L01P. 

I did setup the port configuration for CE, CSN and IRQ: 

//defines for uC pins CE pin is connected to

//This is used so that the routines can send TX payload data and 

// properly initialize the nrf24l01 in TX and RX states.

//Change these definitions (and then recompile) to suit your particular application.

#define nrf24l01_CE_IOREGISTER PORTB

#define nrf24l01_CE_PINMASK 0x14

//defines for uC pins CSN pin is connected to

//This is used so that the routines can send properly operate the SPI interface

// on the nrf24l01.

//Change these definitions (and then recompile) to suit your particular application.

#define nrf24l01_CSN_IOREGISTER PORTD

#define nrf24l01_CSN_PINMASK 0x04

//defines for uC pins IRQ pin is connected to

//This is used so that the routines can poll for IRQ or create an ISR.

//Change these definitions (and then recompile) to suit your particular application.

#define nrf24l01_IRQ_IOREGISTER PORTC

#define nrf24l01_IRQ_PINMASK 0x12

I guess I will setup the configuration for MISO, MOSI and SCK in the SPI file.

The part of the file nrf24l01.h I am concerned about right now is the following:

/////////////////////////////////////////////////////////////////////////////////

// SPI function requirements

//

// The user must define a function to send one byte of data and also return the

//   resulting byte of data data through the SPI port. The function used here 

//   has the function prototype

//

//     unsigned char spi_send_read_byte(unsigned char byte);

//

// This function should take the argument unsigned char byte and send it through

//   the SPI port to the 24L01.  Then, it should wait until the 24L01 has returned

//   its response over SPI.  This received byte should be the return value of the

//   function.

//

// You should also change the include file name below to whatever the name of your 

//   SPI include file is.

//////////////////////////////////////////////////////////////////////////////////

//#include "spi1.h"

#define spi_send_read_byte(byte) spi1_send_read_byte(byte)

So you would suggest to create a new SPI library and not change some code in the SDK-file and use it instead ?

Thanks again!

 

0 Kudos
Reply

2,214 Views
troelsoesteraa
Contributor III

As you will likely need to discard most of the SDK file lines, i'd start over.

Not sure completely what K66 part you're using or what compiler, but I'm assuming MK66FN2M0VLQ18. I assume you include the "MK66F18.h" which is included in the SDK, accessing the different parts of the K66 becomes a lot easier.

So here goes. No matter which module you want to use, you should always enable the clock for it first. After we have done this, the different modules are configured. I did not try to compile this, as I'm out of office this week. I also only have experience with K2x, but the K66 should be similar in this regard. I am using Pin 1-4, e.g. PTE0-3 as they have SPI functionality. I also assume the bus clock (SPI module clock) is 60 MHz.

void PORT_init(void)
{
   SIM->SIM_SCG5 |= SIM_SCGC5_PORTE_MASK;
   PORTE->PCR[0] = PORT_PCR_MUX(2); // SPI1_PCS1 - CSN
   PORTE->PCR[1] = PORT_PCR_MUX(2); // SPI1_SOUT - MOSI  
   PORTE->PCR[2] = PORT_PCR_MUX(2); // SPI1_SCK - SCK
   PORTE->PCR[3] = PORT_PCR_MUX(2); // SPI1_SIN - MISO

   return;
}

void SPI1_init(void)
{
   SIM->SIM_SCG6 |= SIM_SCGC6_SPI1_MASK;
   
   // Enable SPI1 module (MDIS is cleared), set halt mode.
   SPI1->MCR = SPI_MCR_HALT_MASK;
   
   // Set the K66 as master, and do not use any kind of FIFOs. For certain bits to be accepted, MDIS must have been cleared first, as we did above.
   // Also set the inactive level of PCS0-1 as high. Remember to update the initialization if higher than PCS1 is to be used with high as inactive.
   SPI1->MCR |= SPI_MCR_MSTR_MASK | SPI_MCR_CLR_RXF_MASK | SPI_MCR_CLR_TXF_MASK | SPI_MCR_DIS_RXF_MASK | SPI_MCR_DIS_TXF_MASK | SPI_MCR_PCSIS(0x03);

   /* Establish CTAR (Clock and Transfer Attribute Register) suitable for device.
   The idea with this is that you can have more pre-configured CTARs, and when you read/write the specific SPI device, you just specify
   which pre-configured CTAR you want to use for the transfer.
     
   nRF24L01 requirements:
   SPI mode 3 -> CPOL=1, CPHA=1
   SCK <= 8 MHz
   CSC => 2 ns, no compensation needed
   ASC >= 2 ns, no compensation needed
   50 ns hold time between assertions
   */         

   // Thus CTAR0: CPOL=1, CPHA=1, PBR = 2, BR = 8 (3.75 MHz SCK), PDT = 1, DT = 4 (66.6 ns hold time between assertions)
   // Just use 8 bit frame sizes, even though the nRF24L01 uses 24 frames. As long as we keep CSN low between multiple 8 bit accesses, there is no problem.
   SPI1->CTAR[0] = SPI_CTAR_FMSZ(0x07) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(3) | SPI_CTAR_CPOL_MASK | SPI_CTAR_CPHA_MASK | SPI_CTAR_DT(1);

   return;
}

// Now for the read/write routines. It is up for you to define the uData_tx array with the correct data.

void SPI1_read_write(uint8_t *uData_tx, uint8_t *uData_rx, uint8_t uBytes)
{
   uint8_t i;
   
   // Clear previous completed transfer. If not doing so, SPI module remains in stopped state.
   SPI1->SR |= SPI_SR_EOQF_MASK;

   for(i=0;i<uBytes;i++)
   {
      SPI1->PUSHR = SPI_PUSHR_PCS(2) | SPI_PUSHR_CTAS(0) | uData_tx[i]; 
      if(i==uBytes-1)
      {
         SPI1->PUSHR |= SPI_PUSHR_EOQ_MASK;
      }
      else
      {
         SPI1->PUSHR |= SPI_PUSHR_CONT_MASK;
      }
      // Transfer SPI frame
      SPI1->MCR &= ~SPI_MCR_HALT_MASK;
      // Wait for transfer completion
      while(!(SPI1->SR & SPI_SR_TCF_MASK));
      // Clear TCF
      SPI1->SR &= ~SPI_SR_TCF_MASK;
      // Read MISO data for each 8 bit frame.       uData_rx[i] = SPI1->POPR;           }    return; }

2,214 Views
kerryzhou
NXP TechSupport
NXP TechSupport

HI Customer,

   About the K66F SPI code for 24L01, we still don't have the direct code for your reference.

   But don't worry, official have the K66 SPI driver, actually, if you have the other MCU code for 24L01 application, you totally can port the other MCU's 24L01 code to the K66 platform, with the official K66 SPI driver.

   K66 SPI driver can be found in the SDK, you can download it from this link:

Welcome | MCUXpresso SDK Builder 

 Choose the chip as K66, then generate the code and download it. Find the SPI project, then import your for 24L01 code.

  When you meet the detail problem with 24L01 and K66 SPI, please kindly let me know!


Have a great day,
Kerry

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos
Reply