nRF24L01+

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>

Nordic_Read_Reg_00

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.

Nordic_Write_Reg 0

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>

Nordic_Rx_Payload

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>

Nordic_Tx_Payload

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 ;
  }

 

Leave a Reply