Overview
Embedded systems often consist of a microprocessor that communicates with one or more external peripheral devices. The external peripheral devices provide data, and in many situations, require some type of configuration by the microprocessor. Communication between the external peripheral device and the microprocessor often takes place over a serial interface. Data is transferred between devices by passing formatted data packets that are defined by the external peripheral device’s data sheet. We will examine how to use the SPI interface on the Tiva Launchpad to communicate with a Nordic nRF24L01+ wireless radio.
External Memory Map
When interfacing with an external peripheral device, we need to realize that the external peripheral devices have memory maps that are completely unrelated to the microprocessor’s memory map. The memory map consists of registers that define the behavior of the external peripheral device and also provides a mechanism to communicate data with the microprocessor. The registers in the external device are not found in the microprocessor’s memory map so we cannot use LDR or STR instructions to access these registers. Instead we must send a correctly formatted sequence of bytes over a serial interface in order to access these registers.
Formatting Data Packets
External peripheral devices support a set of commands that can be used by the microprocessor to indicate which operations (read, write, other) to execute. A command is normally followed by the address of the register that is being accessed. The specifics of the command sequences are defined by the peripheral device manufacturer. Accelerometers from different manufacturers most likely will have a different set of commands and a different memory map. This is one of the key reasons for re-using a known working peripheral device in new designs. Migrating to a similar, yet different, device will require additional software development time.
Nordic nRF24L01+ Examples
Pages 50 and 57 of the nRF24L01+ data sheet lists the supported commands and the memory map for the nRF24L01+. The examples below assume that two nRF24L01+ devices are configured in a simple point-to-point network. We will begin by examining how to read and write one of the 8-bit registers found in the nRF24L01+ register set. We will examine the CONFIG register at address 0x0. The CONFIG register is used to enable the nRF24L01+ (PWR_UP) and determine if the device is transmitting or receiving data (PRIM_RX).
Register Read
In order to read a register, we must issue a R_REGISTER command. The R_REGISTER command word specifies that bits 7-5 must be set to 000 and the lower 5 bits represent the address of the register being read. Using this information, we must transmit a byte of 0x00 to read the CONFIG register. The CONFIG register is an 8-bit register, so we need to transmit an additional byte of data so that the TM4C123 generates the clock pulses necessary to return the contents of the CONFIG register to the TM4C123 using the MISO line. In order to read the CONFIG register, we need to transmit
0x00 <Dont Care 0>
We can see that the data returned to the user is in the 2nd byte of data. The reason the data is returned in the 2nd byte is that until the last address bit is received by the nRF24L01+, it does not know which register is being read. Once the entire address is received, it beings to transmit back the contents of the CONFIG register. The first byte of data can be discarded from the data returned to the user. In this case, the nRF24L01+ returns a value of 0x0A.
Register Write
When we want to write a register, we need to use the W_REGISTER command. The W_REGISTER command word requires that bits 7-5 must be set to 001 and the lower 5 bits represent the address of the register being written to. This results in the first byte being 0x20. Since we are writing a value to the register, the 2nd byte is the value we want to write to the CONFIG register. The data packet would be
0x20 <DATA 0>
The image below is the resulting waveform.
Data is written on the MOSI line. The data returned on the MISO can be discarded.
Note: The first byte received on the MISO for of any SPI transaction from the nRF24L01+ contains the value of the STATUS register.
Receiving Data
When receiving data on the nRF24L01+, we must use the R_RX_PAYLOAD command (0x61). This command is followd by 1-32 bytes of data. The data transmitted on the MOSI is ignored by the nRF24L01+. The data received by the nRF24L01+ is returned on the MISO. The resulting data packet would be
0x61 <Don’t Care 3> < Don’t Care 2> <Don’t Care 1> <Don’t Care 0>
Transmitting Data
When sending data to the nRF24L01+ , we must use the W_TX_PAYLOAD command (0xA0). This command is then followed by anywhere from 1 to 32-bytes of data. The example below assumes that the size of the data packet is 4 bytes. The resulting data packet would be
0xA0 <DATA 3> < DATA 2> <DATA 1> <DATA 0>
Again, the data returned on the MISO line is discarded.
Configuring the nRF24L01+
Using the commands and registers defined in the nRF24L01+ data sheet, we can use the following functions to configure, transmit, and receive data. The sub functions contained in the code below can be written using the information on register accesses provided above.
//***************************************************************************** // Public Functions //***************************************************************************** //***************************************************************************** //***************************************************************************** wireless_com_status_t wireless_send_32( bool blockOnFull, bool retry, uint32_t data ) { uint8_t status; if( spiVerifyBaseAddr(wirelessPinConfig.rf_base)) { // Check the status of the device status = wireless_get_status(); if( wireless_status_tx_full_asserted(status) && (blockOnFull == false)) { return NRF24L01_TX_FIFO_FULL; } // Wait while the TX FIFO is not full while(wireless_status_tx_full_asserted(status)) { status = wireless_get_status(); } do { // Put into Standby-1 wireless_CE_low(); // Set tx_mode wireless_start_tx_mode(); // Flush any outstanding info in the TX FIFO wireless_flush_tx_fifo(); // Send the data to the TX_PLD wireless_tx_data_payload(data); // Pulse CE for a 15uS wireless_CE_Pulse(); status = wireless_wait_for_tx_ds(); if( status == false) { wireless_clear_max_rt(); } else { // Clear the tx_ds bit wireless_clear_tx_ds(); } } while( status == false && retry == true); // Default back to receive mode wireless_start_rx_mode(); // Enable Wireless transmission wireless_CE_high(); if (status == true) { return NRF24L01_TX_SUCCESS; } else { return NRF24L01_TX_PCK_LOST; } } else { return NRF24L01_ERR; } } //***************************************************************************** //***************************************************************************** wireless_com_status_t wireless_get_32( bool blockOnEmpty, uint32_t *data ) { //uint8_t status; if( spiVerifyBaseAddr(wirelessPinConfig.rf_base)) { if( wireless_rx_fifo_empty() == false) { // Read data from Rx FIFO wireless_rx_data_payload(data); // If Rx FIFO is empty, clear the RX_DR bit in the status // register if ( wireless_rx_fifo_empty() == true) { // Clear the RX_DR bit wireless_reg_write(NRF24L01_STATUS_R, NRF24L01_STATUS_RX_DR_M); } return NRF24L01_RX_SUCCESS; } else if ( (wireless_rx_fifo_empty() == true) && blockOnEmpty) { // Wait until the RX_DR bit is set wireless_wait_for_rx_dr(); // Read data from Rx FIFO wireless_rx_data_payload( data); // If Rx FIFO is empty, clear the RX_DR bit in the status // register if ( wireless_rx_fifo_empty() == true) { // Clear the RX_DR bit wireless_reg_write(NRF24L01_STATUS_R, NRF24L01_STATUS_RX_DR_M); } return NRF24L01_RX_SUCCESS; } else { return NRF24L01_RX_FIFO_EMPTY; } } else { return NRF24L01_ERR; } } //***************************************************************************** //***************************************************************************** bool wireless_configure_device( uint8_t *my_id, uint8_t *dest_id ) { if( spiVerifyBaseAddr(wirelessPinConfig.rf_base)) { wireless_CSN_high(); wireless_CE_low(); // Configure Common RF settings wireless_flush_tx_fifo(); wireless_flush_rx_fifo(); wireless_reg_write(NRF24L01_RF_SETUP_R, NRF24L01_RF_SETUP_RF_PWR_0DB | NRF24L01_RF_SETUP_250_KBPS); wireless_reg_write(NRF24L01_RF_CH_R, wirelessPinConfig.channel); wireless_reg_write( NRF24L01_STATUS_R, NRF24L01_STATUS_CLEAR_ALL); wireless_reg_write(NRF24L01_SETUP_RETR_R, NRF24L01_SETUP_RETR_ARD_0750_US | NRF24L01_SETUP_RETR_ARC_15); // Configure the address to transfer data to wireless_set_tx_addr(dest_id); // Configure Pipe 0 to receive the AUTO ACKs from the other device wireless_reg_write(NRF24L01_RX_PW_P0_R, wirelessPinConfig.payload_size); wireless_set_rx_addr(dest_id, 0); // Configure Pipe 1 wireless_reg_write(NRF24L01_RX_PW_P1_R, wirelessPinConfig.payload_size); wireless_set_rx_addr(my_id, 1); // Turn on Rx and AutoAcks for pipe 0 and 1 wireless_reg_write(NRF24L01_EN_RXADDR_R, NRF24L01_RXADDR_ERX_P0 | NRF24L01_RXADDR_ERX_P1); wireless_reg_write(NRF24L01_EN_AA_R, NRF24L01_ENAA_P0 | NRF24L01_ENAA_P1); // Enable the Radio in RX mode wireless_reg_write(NRF24L01_CONFIG_R,NRF24L01_CONFIG_PWR_UP | NRF24L01_CONFIG_EN_CRC | NRF24L01_CONFIG_PRIM_RX_PRX ); wireless_CE_high(); return true; } else { return false ; }