RF24 project code analysis
- Header file https://github.com/nRF24/RF24/blob/master/RF24.h
- Source file https://github.com/nRF24/RF24/blob/master/RF24.cpp
- Data stream sending / receiving example https://github.com/nRF24/RF24/blob/master/examples_linux/streamingData.cpp
Function bool writeFast(const void* buf, uint8_t len)
This function makes full use of nrf24 FIFO sending queue with 3x32 bytes to write continuously before the queue is full
When using this function, you need to mask the external interrupt of IRQ. It does not need interrupt signal and does not read IRQ status, because it reads TX_ The full flag bit determines whether to continue writing
// Block only when FIFO is full. If FIFO is full, it will block here until TX succeeds or fails while ((get_status() & (_BV(TX_FULL)))) { if (status & _BV(MAX_RT)) { return 0; // If status Max_ The RT flag is set to indicate that the previous data transmission failed, and 0 is returned. After the superior call obtains the result of 0, re transmission can be enabled } //... } startFastWrite(buf, len, multicast); // Start writing
Function void startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx = 1)
For non blocking fast write (send), the default value of the last parameter here is 1, so ce(HIGH) will always be executed by default
This function will keep CE high. At this time, NRF24 will remain in TX or STANDBY-II state until txStandBy() command is called
This function can be used to call NRF24 to send multiple groups of data at one time
// According to the document, if you want to set PTX mode in non receiving mode, all you need to do is write data and set CE to high level. In this mode, if you continue to write to FIFO buffers // The packet will be sent immediately without waiting for an interval of 130us, otherwise it will enter the Standby-II mode, which is still faster than the standby mode, avoiding modifying the config register and waiting for 150us each time void RF24::startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx) { //TMRh20 write_payload(buf, len, multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD); if (startTx) { ce(HIGH); } }
Note: NRF24 cannot keep the FIFO queue full for more than 4ms in TX mode. After auto retransmit and auto ack are enabled, the length of time NRF24 keeps TX mode still meets this rule, which allows calling txStandBy() to clear the FIFO queue or ensure that sufficient time interval is maintained between transmissions
Function bool txStandBy();
Read FIFO_STATUS register until TX_EMPTY flag position bit
- If the queue is empty, stop sending ce(LOW) and enter STANDBY-I mode
- Check Max in status in the loop_ RT flag bit. If it is set, it also stops sending, clears the queue and returns 0
bool RF24::txStandBy() { while (!(read_register(FIFO_STATUS) & _BV(TX_EMPTY))) { if (status & _BV(MAX_RT)) { write_register(NRF_STATUS, _BV(MAX_RT)); ce(LOW); flush_tx(); //Non blocking, flush the data return 0; } } ce(LOW); //Set STANDBY-I mode return 1; }
Function void reUseTX()
void RF24::reUseTX() { write_register(NRF_STATUS, _BV(MAX_RT)); //Clear max retry flag write_register(REUSE_TX_PL, RF24_NOP, true); ce(LOW); //Re-Transfer packet ce(HIGH); }
Read process
- According to the manual, The STATUS register is serially shifted out on the MISO pin simultaneously to the SPI command word shift-ing to the MOSI pin, while issuing instructions, the upper computer writes to NRF24 through the MOSI port, and NRF24 writes the value of the STATUS register to the upper computer through the MISO port, Therefore, the value of the STATUS register can be exchanged during any operation of reading and writing the register
- According to the manual, the 3-bit pipe information in the STATUS register is updated during the IRQ pin high to low transition. The pipe information is unreliable if the STATUS register is read during an IRQ pin high to low transition, When the level is still in the process of conversion, the read STATUS register value is unreliable. Clear the STATUS flag bit before reading the STATUS value
The following steps are generally used when reading to enable interrupt. During interrupt, first check what flag bit is, and then read the pipe number with data
void setup() { pinMode(IRQ_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(IRQ_PIN), isrCallbackFunction, FALLING); } void isrCallbackFunction() { bool tx_ds, tx_df, rx_dr; radio.whatHappened(tx_ds, tx_df, rx_dr); // resets the IRQ pin to HIGH if (radio.available()) { // is there a payload radio.read(&buffer, SIZE); // fetch payload from FIFO //... } }
Reading the status register, judging the flag bit and clearing the flag bit are completed in one instruction
void RF24::whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready) { // Read the status & reset the status in one easy call // Or is that such a good idea? write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); // Report to the user what happened tx_ok = status & _BV(TX_DS); tx_fail = status & _BV(MAX_RT); rx_ready = status & _BV(RX_DR); }
Read the status register again to determine the pipe number
bool RF24::available(uint8_t* pipe_num) { // get implied RX FIFO empty flag from status byte uint8_t pipe = (get_status() >> RX_P_NO) & 0x07; if (pipe > 5) return 0; // If the caller wants the pipe number, include that if (pipe_num) *pipe_num = pipe; return 1; }
Test on STC12C5A60S2
Sender
In order to avoid interference, the serial port output part is commented out, and the 8X8 LED of MAX7219 is used as the output display
#include "hml/hml.h" //#include <stdio.h> #include "max7219.h" /********** SPI(nRF24L01) commands ***********/ // #define NRF24_CMD_R_REGISTER 0x00 // [000A AAAA] Register read #define NRF24_CMD_W_REGISTER 0x20 // [001A AAAA] Register write #define NRF24_CMD_R_RX_PAYLOAD 0x61 // Read RX payload #define NRF24_CMD_W_TX_PAYLOAD 0xA0 // Write TX payload #define NRF24_CMD_FLUSH_TX 0xE1 // Flush TX FIFO #define NRF24_CMD_FLUSH_RX 0xE2 // Flush RX FIFO #define NRF24_CMD_REUSE_TX_PL 0xE3 // Reuse TX payload #define NRF24_CMD_R_RX_PL_WID 0x60 // Read RX-payload width for the top R_RX_PAYLOAD in the RX FIFO. #define NRF24_CMD_W_ACK_PAYLOAD 0xA8 // [1010 1PPP] Write ACK Payload to be with ACK packet on PIPE PPP #define NRF24_CMD_W_TX_PAYLOAD_NOACK 0xB0 //Write TX payload and disable AUTOACK #define NRF24_CMD_NOP 0xFF // No operation (used for reading status register) #define NRF24_CMD_ACTIVATE 0x50 // (De)Activates R_RX_PL_WID, W_ACK_PAYLOAD, W_TX_PAYLOAD_NOACK features #define NRF24_CMD_LOCK_UNLOCK 0x50 // Lock/unlock exclusive features // SPI(nRF24L01) register address definitions #define NRF24_REG_CONFIG 0x00 // Configuration register #define NRF24_REG_EN_AA 0x01 // Enable "Auto acknowledgment" #define NRF24_REG_EN_RXADDR 0x02 // Enable RX addresses #define NRF24_REG_SETUP_AW 0x03 // Setup of address widths #define NRF24_REG_SETUP_RETR 0x04 // Setup of automatic re-transmit #define NRF24_REG_RF_CH 0x05 // RF channel #define NRF24_REG_RF_SETUP 0x06 // RF setup #define NRF24_REG_STATUS 0x07 // Status register #define NRF24_REG_OBSERVE_TX 0x08 // Transmit observe register #define NRF24_REG_RPD 0x09 // Received power detector #define NRF24_REG_RX_ADDR_P0 0x0A // Receive address data pipe 0 #define NRF24_REG_RX_ADDR_P1 0x0B // Receive address data pipe 1 #define NRF24_REG_RX_ADDR_P2 0x0C // Receive address data pipe 2 #define NRF24_REG_RX_ADDR_P3 0x0D // Receive address data pipe 3 #define NRF24_REG_RX_ADDR_P4 0x0E // Receive address data pipe 4 #define NRF24_REG_RX_ADDR_P5 0x0F // Receive address data pipe 5 #define NRF24_REG_TX_ADDR 0x10 // Transmit address #define NRF24_REG_RX_PW_P0 0x11 // Number of bytes in RX payload in data pipe 0 #define NRF24_REG_RX_PW_P1 0x12 // Number of bytes in RX payload in data pipe 1 #define NRF24_REG_RX_PW_P2 0x13 // Number of bytes in RX payload in data pipe 2 #define NRF24_REG_RX_PW_P3 0x14 // Number of bytes in RX payload in data pipe 3 #define NRF24_REG_RX_PW_P4 0x15 // Number of bytes in RX payload in data pipe 4 #define NRF24_REG_RX_PW_P5 0x16 // Number of bytes in RX payload in data pipe 5 #define NRF24_REG_FIFO_STATUS 0x17 // FIFO status register #define NRF24_REG_DYNPD 0x1C // Enable dynamic payload length #define NRF24_REG_FEATURE 0x1D // Feature register // Register bits definitions #define NRF24_CONFIG_PRIM_RX 0x01 // PRIM_RX bit in CONFIG register #define NRF24_CONFIG_PWR_UP 0x02 // PWR_UP bit in CONFIG register #define NRF24_FEATURE_EN_DYN_ACK 0x01 // EN_DYN_ACK bit in FEATURE register #define NRF24_FEATURE_EN_ACK_PAY 0x02 // EN_ACK_PAY bit in FEATURE register #define NRF24_FEATURE_EN_DPL 0x04 // EN_DPL bit in FEATURE register #define NRF24_FLAG_RX_DREADY 0x40 // RX_DR bit (data ready RX FIFO interrupt) #define NRF24_FLAG_TX_DSENT 0x20 // TX_DS bit (data sent TX FIFO interrupt) #define NRF24_FLAG_MAX_RT 0x10 // MAX_RT bit (maximum number of TX re-transmits interrupt) #define NRF24_FLAG_TX_FULL 0x01 // 1:TX FIFO full // Register masks definitions #define NRF24_MASK_REG_MAP 0x1F // Mask bits[4:0] for CMD_RREG and CMD_WREG commands #define NRF24_MASK_CRC 0x0C // Mask for CRC bits [3:2] in CONFIG register #define NRF24_MASK_STATUS_IRQ 0x70 // Mask for all IRQ bits in STATUS register #define NRF24_MASK_RF_PWR 0x06 // Mask RF_PWR[2:1] bits in RF_SETUP register #define NRF24_MASK_RX_P_NO 0x0E // Mask RX_P_NO[3:1] bits in STATUS register #define NRF24_MASK_DATARATE 0x28 // Mask RD_DR_[5,3] bits in RF_SETUP register #define NRF24_MASK_EN_RX 0x3F // Mask ERX_P[5:0] bits in EN_RXADDR register #define NRF24_MASK_RX_PW 0x3F // Mask [5:0] bits in RX_PW_Px register #define NRF24_MASK_RETR_ARD 0xF0 // Mask for ARD[7:4] bits in SETUP_RETR register #define NRF24_MASK_RETR_ARC 0x0F // Mask for ARC[3:0] bits in SETUP_RETR register #define NRF24_MASK_RXFIFO 0x03 // Mask for RX FIFO status bits [1:0] in FIFO_STATUS register #define NRF24_MASK_TXFIFO 0x30 // Mask for TX FIFO status bits [5:4] in FIFO_STATUS register #define NRF24_MASK_PLOS_CNT 0xF0 // Mask for PLOS_CNT[7:4] bits in OBSERVE_TX register #define NRF24_MASK_ARC_CNT 0x0F // Mask for ARC_CNT[3:0] bits in OBSERVE_TX register #define NRF24_ADDR_WIDTH 5 // RX/TX address width #define NRF24_PLOAD_WIDTH 32 // Payload width #define NRF24_TEST_ADDR "nRF24" uint8_t nrf24_state; typedef enum { NRF24_MODE_RX = 0x00, NRF24_MODE_TX = 0x01 } NRF24_MODE; typedef enum { NRF24_SCEN_RX = 0x00, NRF24_SCEN_TX = 0x01, NRF24_SCEN_HALF_DUPLEX = 0x02 } NRF24_SCEN; __xdata uint8_t xbuf[NRF24_PLOAD_WIDTH + 1]; const uint8_t TX_ADDRESS[NRF24_ADDR_WIDTH] = {0x32,0x4E,0x6F,0x64,0x65}; const uint8_t RX_ADDRESS[NRF24_ADDR_WIDTH] = {0x32,0x4E,0x6F,0x64,0x22}; const NRF24_SCEN CURRENT_SCEN = NRF24_SCEN_TX; #define NRF_CSN P1_4 #define NRF_MOSI P1_5 #define NRF_MISO P1_6 #define NRF_SCK P1_7 #define NRF_IRQ P3_2 #define NRF_CE P3_7 void NRF24L01_writeReg(uint8_t reg, uint8_t value) { NRF_CSN = 0; nrf24_state = SPI_RW(reg); SPI_RW(value); NRF_CSN = 1; } uint8_t NRF24L01_readReg(uint8_t reg) { uint8_t value; NRF_CSN = 0; nrf24_state = SPI_RW(reg); value = SPI_RW(NRF24_CMD_NOP); NRF_CSN = 1; return value; } void NRF24L01_readToBuf(uint8_t reg, uint8_t *pBuf, uint8_t len) { uint8_t u8_ctr; NRF_CSN = 0; nrf24_state = SPI_RW(reg); for (u8_ctr = 0; u8_ctr < len; u8_ctr++) pBuf[u8_ctr] = SPI_RW(NRF24_CMD_NOP); NRF_CSN = 1; } void NRF24L01_writeFromBuf(uint8_t reg, const uint8_t *pBuf, uint8_t len) { uint8_t u8_ctr; NRF_CSN = 0; nrf24_state = SPI_RW(reg); for (u8_ctr = 0; u8_ctr < len; u8_ctr++) SPI_RW(*pBuf++); NRF_CSN = 1; } void NRF24L01_printBuf(uint8_t *buf) { for (uint8_t i = 0; i < NRF24_PLOAD_WIDTH; i++) { //printf_tiny("%x ", buf[i]); } //printf_tiny("\r\n"); } /** * Flush the RX FIFO */ void NRF24L01_flushRX(void) { NRF24L01_writeReg(NRF24_CMD_FLUSH_RX, NRF24_CMD_NOP); } /** * Flush the TX FIFO */ void NRF24L01_flushTX(void) { NRF24L01_writeReg(NRF24_CMD_FLUSH_TX, NRF24_CMD_NOP); } void NRF24L01_checkFlag(uint8_t *tx_ds, uint8_t *max_rt, uint8_t *rx_dr) { // Read the status & reset the status in one easy call NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_STATUS, NRF24_FLAG_RX_DREADY|NRF24_FLAG_TX_DSENT|NRF24_FLAG_MAX_RT); // Report to the user what happened *tx_ds = nrf24_state & NRF24_FLAG_TX_DSENT; *max_rt = nrf24_state & NRF24_FLAG_MAX_RT; *rx_dr = nrf24_state & NRF24_FLAG_RX_DREADY; } bool NRF24L01_rxAvailable(uint8_t* pipe_num) { nrf24_state = NRF24L01_readReg(NRF24_REG_STATUS); uint8_t pipe = (nrf24_state >> 1) & 0x07; if (pipe > 5) return false; // If the caller wants the pipe number, include that if (pipe_num) *pipe_num = pipe; return true; } void NRF24L01_handelIrqFlag(uint8_t *buf) { int8_t tx_ds, max_rt, rx_dr, pipe_num; NRF24L01_checkFlag(&tx_ds, &max_rt, &rx_dr); if (NRF24L01_rxAvailable(&pipe_num)) { NRF24L01_readToBuf(NRF24_CMD_R_RX_PAYLOAD, buf, NRF24_PLOAD_WIDTH); } //printf_tiny("TX_DS:%x, MAX_RT:%x, RX_DR:%x, PIPE:%x\r\n", tx_ds, max_rt, rx_dr, pipe_num); //printf_tiny("TX_DS:%x\r\n", tx_ds); //NRF24L01_printBuf(xbuf); } void NRF24L01_tx(uint8_t *txbuf) { NRF_CE = 0; NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0E); NRF24L01_writeFromBuf(NRF24_CMD_W_TX_PAYLOAD, txbuf, NRF24_PLOAD_WIDTH); NRF_CE = 1; sleep(5); // 4ms+ for reliable DS state when SETUP_RETR is 0x13 NRF_CE = 0; NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0F); NRF_CE = 1; } void NRF24L01_startFastWrite(const void* txbuf) { NRF24L01_writeFromBuf(NRF24_CMD_W_TX_PAYLOAD, txbuf, NRF24_PLOAD_WIDTH); NRF_CE = 1; } bool NRF24L01_writeFast(const void* txbuf) { //Blocking only if FIFO is full. This will loop and block until TX is successful or fail while ((NRF24L01_readReg(NRF24_REG_STATUS) & NRF24_FLAG_TX_FULL)) { //printf_tiny(">STATE:%x\r\n", nrf24_state); if (nrf24_state & NRF24_FLAG_MAX_RT) { return false; } } //printf_tiny("STATE:%x\r\n", nrf24_state); NRF24L01_startFastWrite(txbuf); return true; } void NRF24L01_reUseTX(void) { NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_STATUS, NRF24_FLAG_MAX_RT);//Clear max retry flag NRF_CE = 0; NRF_CE = 1; } uint8_t NRF24L01_check(void) { uint8_t i; uint8_t *ptr = (uint8_t *)NRF24_TEST_ADDR; NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER | NRF24_REG_TX_ADDR, ptr, NRF24_ADDR_WIDTH); NRF24L01_readToBuf(NRF24_CMD_R_REGISTER | NRF24_REG_TX_ADDR, xbuf, NRF24_ADDR_WIDTH); for (i = 0; i < NRF24_ADDR_WIDTH; i++) { if (xbuf[i] != *ptr++) return 1; } return 0; } void NRF24L01_init(NRF24_MODE mode) { NRF_CE = 0; NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER + NRF24_REG_TX_ADDR, (uint8_t *)TX_ADDRESS, NRF24_ADDR_WIDTH); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RX_PW_P0, NRF24_PLOAD_WIDTH); NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER + NRF24_REG_RX_ADDR_P0, (uint8_t *)TX_ADDRESS, NRF24_ADDR_WIDTH); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RX_PW_P1, NRF24_PLOAD_WIDTH); NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER + NRF24_REG_RX_ADDR_P1, (uint8_t *)RX_ADDRESS, NRF24_ADDR_WIDTH); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_EN_AA, 0x3f); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_EN_RXADDR, 0x3f); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_SETUP_RETR, 0x13); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RF_CH, 40); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RF_SETUP, 0x07); switch (mode) { case NRF24_MODE_TX: NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x7E); break; case NRF24_MODE_RX: default: NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0F); break; } NRF_CE = 1; } void EXTI0_irqHandler(void) __interrupt (IE0_VECTOR) { NRF24L01_handelIrqFlag(xbuf); } void EXTI_init(void) { EXTI_configTypeDef ec; ec.mode = EXTI_mode_lowLevel; ec.priority = IntPriority_High; EXTI_config(PERIPH_EXTI_0, &ec); EXTI_cmd(PERIPH_EXTI_0, ENABLE); UTIL_setInterrupts(ENABLE); } void SPI_init(void) { SPI_configTypeDef sc; sc.baudRatePrescaler = SPI_BaudRatePrescaler_4; sc.cpha = SPI_CPHA_1Edge; sc.cpol = SPI_CPOL_low; sc.firstBit = SPI_FirstBit_MSB; sc.pinmap = SPI_pinmap_P1; sc.nss = SPI_NSS_Soft; sc.mode = SPI_Mode_Master; SPI_config(&sc); SPI_cmd(ENABLE); } void main(void) { const uint8_t tmp[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F}; //UTIL_enablePrintf(); SPI_init(); while (NRF24L01_check()) { //printf_tiny("Check failed\r\n"); sleep(500); } switch (CURRENT_SCEN) { case NRF24_SCEN_TX: MAX7219_init(1, 0x00, 0x07, 0x00); NRF24L01_init(NRF24_MODE_TX); //EXTI_init(); //printf_tiny("Initialized\r\n"); uint8_t pos = 0, failures = 0, i = 0, j = 1; for (i = 0; i < 7; i++) { MAX7219_singeWrite(0, i+1, 0x00); } while (true) { if (!NRF24L01_writeFast(tmp + pos)) { failures++; NRF24L01_reUseTX(); } else { i++; } pos++; if (pos > 0x0F) pos = 0x00; if (i == 0xFF || failures == 0xFF) { //printf_tiny("[x]F/S = %x/%x\r\n", failures, i); MAX7219_singeWrite(0, j, failures); MAX7219_singeWrite(0, j+1, i); i = 0x00; failures = 0x00; j += 2; if (j > 7) j = 1; } //sleep(1);// 20:err(40%~50%), 10:err(60%-70%) } break; case NRF24_SCEN_RX: NRF24L01_init(NRF24_MODE_RX); EXTI_init(); while(true); break; case NRF24_SCEN_HALF_DUPLEX: NRF24L01_init(NRF24_MODE_RX); EXTI_init(); while (true) { NRF24L01_tx((uint8_t *)tmp); sleep(500); } break; default: //printf_tiny("Unknown scen\r\n"); break; } }
receiving end
The receiver also annotates the serial port output
#include "hml/hml.h" //#include <stdio.h> /********** SPI(nRF24L01) commands ***********/ // #define NRF24_CMD_R_REGISTER 0x00 // [000A AAAA] Register read #define NRF24_CMD_W_REGISTER 0x20 // [001A AAAA] Register write #define NRF24_CMD_R_RX_PAYLOAD 0x61 // Read RX payload #define NRF24_CMD_W_TX_PAYLOAD 0xA0 // Write TX payload #define NRF24_CMD_FLUSH_TX 0xE1 // Flush TX FIFO #define NRF24_CMD_FLUSH_RX 0xE2 // Flush RX FIFO #define NRF24_CMD_REUSE_TX_PL 0xE3 // Reuse TX payload #define NRF24_CMD_R_RX_PL_WID 0x60 // Read RX-payload width for the top R_RX_PAYLOAD in the RX FIFO. #define NRF24_CMD_W_ACK_PAYLOAD 0xA8 // [1010 1PPP] Write ACK Payload to be with ACK packet on PIPE PPP #define NRF24_CMD_W_TX_PAYLOAD_NOACK 0xB0 //Write TX payload and disable AUTOACK #define NRF24_CMD_NOP 0xFF // No operation (used for reading status register) #define NRF24_CMD_ACTIVATE 0x50 // (De)Activates R_RX_PL_WID, W_ACK_PAYLOAD, W_TX_PAYLOAD_NOACK features #define NRF24_CMD_LOCK_UNLOCK 0x50 // Lock/unlock exclusive features // SPI(nRF24L01) register address definitions #define NRF24_REG_CONFIG 0x00 // Configuration register #define NRF24_REG_EN_AA 0x01 // Enable "Auto acknowledgment" #define NRF24_REG_EN_RXADDR 0x02 // Enable RX addresses #define NRF24_REG_SETUP_AW 0x03 // Setup of address widths #define NRF24_REG_SETUP_RETR 0x04 // Setup of automatic re-transmit #define NRF24_REG_RF_CH 0x05 // RF channel #define NRF24_REG_RF_SETUP 0x06 // RF setup #define NRF24_REG_STATUS 0x07 // Status register #define NRF24_REG_OBSERVE_TX 0x08 // Transmit observe register #define NRF24_REG_RPD 0x09 // Received power detector #define NRF24_REG_RX_ADDR_P0 0x0A // Receive address data pipe 0 #define NRF24_REG_RX_ADDR_P1 0x0B // Receive address data pipe 1 #define NRF24_REG_RX_ADDR_P2 0x0C // Receive address data pipe 2 #define NRF24_REG_RX_ADDR_P3 0x0D // Receive address data pipe 3 #define NRF24_REG_RX_ADDR_P4 0x0E // Receive address data pipe 4 #define NRF24_REG_RX_ADDR_P5 0x0F // Receive address data pipe 5 #define NRF24_REG_TX_ADDR 0x10 // Transmit address #define NRF24_REG_RX_PW_P0 0x11 // Number of bytes in RX payload in data pipe 0 #define NRF24_REG_RX_PW_P1 0x12 // Number of bytes in RX payload in data pipe 1 #define NRF24_REG_RX_PW_P2 0x13 // Number of bytes in RX payload in data pipe 2 #define NRF24_REG_RX_PW_P3 0x14 // Number of bytes in RX payload in data pipe 3 #define NRF24_REG_RX_PW_P4 0x15 // Number of bytes in RX payload in data pipe 4 #define NRF24_REG_RX_PW_P5 0x16 // Number of bytes in RX payload in data pipe 5 #define NRF24_REG_FIFO_STATUS 0x17 // FIFO status register #define NRF24_REG_DYNPD 0x1C // Enable dynamic payload length #define NRF24_REG_FEATURE 0x1D // Feature register // Register bits definitions #define NRF24_CONFIG_PRIM_RX 0x01 // PRIM_RX bit in CONFIG register #define NRF24_CONFIG_PWR_UP 0x02 // PWR_UP bit in CONFIG register #define NRF24_FEATURE_EN_DYN_ACK 0x01 // EN_DYN_ACK bit in FEATURE register #define NRF24_FEATURE_EN_ACK_PAY 0x02 // EN_ACK_PAY bit in FEATURE register #define NRF24_FEATURE_EN_DPL 0x04 // EN_DPL bit in FEATURE register #define NRF24_FLAG_RX_DREADY 0x40 // RX_DR bit (data ready RX FIFO interrupt) #define NRF24_FLAG_TX_DSENT 0x20 // TX_DS bit (data sent TX FIFO interrupt) #define NRF24_FLAG_MAX_RT 0x10 // MAX_RT bit (maximum number of TX re-transmits interrupt) #define NRF24_FLAG_TX_FULL 0x01 // 1:TX FIFO full // Register masks definitions #define NRF24_MASK_REG_MAP 0x1F // Mask bits[4:0] for CMD_RREG and CMD_WREG commands #define NRF24_MASK_CRC 0x0C // Mask for CRC bits [3:2] in CONFIG register #define NRF24_MASK_STATUS_IRQ 0x70 // Mask for all IRQ bits in STATUS register #define NRF24_MASK_RF_PWR 0x06 // Mask RF_PWR[2:1] bits in RF_SETUP register #define NRF24_MASK_RX_P_NO 0x0E // Mask RX_P_NO[3:1] bits in STATUS register #define NRF24_MASK_DATARATE 0x28 // Mask RD_DR_[5,3] bits in RF_SETUP register #define NRF24_MASK_EN_RX 0x3F // Mask ERX_P[5:0] bits in EN_RXADDR register #define NRF24_MASK_RX_PW 0x3F // Mask [5:0] bits in RX_PW_Px register #define NRF24_MASK_RETR_ARD 0xF0 // Mask for ARD[7:4] bits in SETUP_RETR register #define NRF24_MASK_RETR_ARC 0x0F // Mask for ARC[3:0] bits in SETUP_RETR register #define NRF24_MASK_RXFIFO 0x03 // Mask for RX FIFO status bits [1:0] in FIFO_STATUS register #define NRF24_MASK_TXFIFO 0x30 // Mask for TX FIFO status bits [5:4] in FIFO_STATUS register #define NRF24_MASK_PLOS_CNT 0xF0 // Mask for PLOS_CNT[7:4] bits in OBSERVE_TX register #define NRF24_MASK_ARC_CNT 0x0F // Mask for ARC_CNT[3:0] bits in OBSERVE_TX register #define NRF24_ADDR_WIDTH 5 // RX/TX address width #define NRF24_PLOAD_WIDTH 32 // Payload width #define NRF24_TEST_ADDR "nRF24" uint8_t nrf24_state; typedef enum { NRF24_MODE_RX = 0x00, NRF24_MODE_TX = 0x01 } NRF24_MODE; typedef enum { NRF24_SCEN_RX = 0x00, NRF24_SCEN_TX = 0x01, NRF24_SCEN_HALF_DUPLEX = 0x02 } NRF24_SCEN; uint8_t xbuf[NRF24_PLOAD_WIDTH + 1]; const uint8_t RX_ADDRESS[NRF24_ADDR_WIDTH] = {0x32,0x4E,0x6F,0x64,0x65}; const uint8_t TX_ADDRESS[NRF24_ADDR_WIDTH] = {0x32,0x4E,0x6F,0x64,0x22}; const NRF24_SCEN CURRENT_SCEN = NRF24_SCEN_RX; #define NRF_CSN P1_4 #define NRF_MOSI P1_5 #define NRF_MISO P1_6 #define NRF_SCK P1_7 #define NRF_IRQ P3_2 #define NRF_CE P3_7 void NRF24L01_writeReg(uint8_t reg, uint8_t value) { NRF_CSN = 0; nrf24_state = SPI_RW(reg); SPI_RW(value); NRF_CSN = 1; } uint8_t NRF24L01_readReg(uint8_t reg) { uint8_t value; NRF_CSN = 0; nrf24_state = SPI_RW(reg); value = SPI_RW(NRF24_CMD_NOP); NRF_CSN = 1; return value; } void NRF24L01_readToBuf(uint8_t reg, uint8_t *pBuf, uint8_t len) { uint8_t u8_ctr; NRF_CSN = 0; nrf24_state = SPI_RW(reg); for (u8_ctr = 0; u8_ctr < len; u8_ctr++) pBuf[u8_ctr] = SPI_RW(NRF24_CMD_NOP); NRF_CSN = 1; } void NRF24L01_writeFromBuf(uint8_t reg, const uint8_t *pBuf, uint8_t len) { uint8_t u8_ctr; NRF_CSN = 0; nrf24_state = SPI_RW(reg); for (u8_ctr = 0; u8_ctr < len; u8_ctr++) SPI_RW(*pBuf++); NRF_CSN = 1; } void NRF24L01_printBuf(uint8_t *buf) { for (uint8_t i = 0; i < NRF24_PLOAD_WIDTH; i++) { //printf_tiny("%x ", buf[i]); } //printf_tiny("\r\n"); } /** * Flush the RX FIFO */ void NRF24L01_flushRX(void) { NRF24L01_writeReg(NRF24_CMD_FLUSH_RX, NRF24_CMD_NOP); } /** * Flush the TX FIFO */ void NRF24L01_flushTX(void) { NRF24L01_writeReg(NRF24_CMD_FLUSH_TX, NRF24_CMD_NOP); } void NRF24L01_checkFlag(uint8_t *tx_ds, uint8_t *max_rt, uint8_t *rx_dr) { // Read the status & reset the status in one easy call NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_STATUS, NRF24_FLAG_RX_DREADY|NRF24_FLAG_TX_DSENT|NRF24_FLAG_MAX_RT); // Report to the user what happened *tx_ds = nrf24_state & NRF24_FLAG_TX_DSENT; *max_rt = nrf24_state & NRF24_FLAG_MAX_RT; *rx_dr = nrf24_state & NRF24_FLAG_RX_DREADY; } bool NRF24L01_rxAvailable(uint8_t* pipe_num) { nrf24_state = NRF24L01_readReg(NRF24_REG_STATUS); uint8_t pipe = (nrf24_state >> 1) & 0x07; if (pipe > 5) return false; // If the caller wants the pipe number, include that if (pipe_num) *pipe_num = pipe; return true; } void NRF24L01_handelIrqFlag(uint8_t *buf) { int8_t tx_ds, max_rt, rx_dr, pipe_num; NRF24L01_checkFlag(&tx_ds, &max_rt, &rx_dr); if (NRF24L01_rxAvailable(&pipe_num)) { NRF24L01_readToBuf(NRF24_CMD_R_RX_PAYLOAD, buf, NRF24_PLOAD_WIDTH); } //printf_tiny("RX:%x\r\n", buf[0]); //printf_tiny("TX_DS:%x, MAX_RT:%x, RX_DR:%x, PIPE:%x, RX:%x\r\n", tx_ds, max_rt, rx_dr, pipe_num, buf[0]); //NRF24L01_printBuf(xbuf); } void NRF24L01_tx(uint8_t *txbuf) { NRF_CE = 0; NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0E); NRF24L01_writeFromBuf(NRF24_CMD_W_TX_PAYLOAD, txbuf, NRF24_PLOAD_WIDTH); NRF_CE = 1; sleep(4); // 4ms+ for reliable DS state when SETUP_RETR is 0x13 NRF_CE = 0; NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0F); NRF_CE = 1; } uint8_t NRF24L01_check(void) { uint8_t i; uint8_t *ptr = (uint8_t *)NRF24_TEST_ADDR; NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER | NRF24_REG_TX_ADDR, ptr, NRF24_ADDR_WIDTH); NRF24L01_readToBuf(NRF24_CMD_R_REGISTER | NRF24_REG_TX_ADDR, xbuf, NRF24_ADDR_WIDTH); for (i = 0; i < NRF24_ADDR_WIDTH; i++) { if (xbuf[i] != *ptr++) return 1; } return 0; } void NRF24L01_init(NRF24_MODE mode) { NRF_CE = 0; NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER + NRF24_REG_TX_ADDR, (uint8_t *)TX_ADDRESS, NRF24_ADDR_WIDTH); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RX_PW_P0, NRF24_PLOAD_WIDTH); NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER + NRF24_REG_RX_ADDR_P0, (uint8_t *)TX_ADDRESS, NRF24_ADDR_WIDTH); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RX_PW_P1, NRF24_PLOAD_WIDTH); NRF24L01_writeFromBuf(NRF24_CMD_W_REGISTER + NRF24_REG_RX_ADDR_P1, (uint8_t *)RX_ADDRESS, NRF24_ADDR_WIDTH); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_EN_AA, 0x3f); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_EN_RXADDR, 0x3f); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_SETUP_RETR, 0x13); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RF_CH, 40); NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_RF_SETUP, 0x07); switch (mode) { case NRF24_MODE_TX: NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0E); break; case NRF24_MODE_RX: default: NRF24L01_writeReg(NRF24_CMD_W_REGISTER + NRF24_REG_CONFIG, 0x0F); break; } NRF_CE = 1; } void EXTI0_irqHandler(void) __interrupt (IE0_VECTOR) { NRF24L01_handelIrqFlag(xbuf); } void EXTI_init(void) { EXTI_configTypeDef ec; ec.mode = EXTI_mode_lowLevel; ec.priority = IntPriority_High; EXTI_config(PERIPH_EXTI_0, &ec); EXTI_cmd(PERIPH_EXTI_0, ENABLE); UTIL_setInterrupts(ENABLE); } void SPI_init(void) { SPI_configTypeDef sc; sc.baudRatePrescaler = SPI_BaudRatePrescaler_4; sc.cpha = SPI_CPHA_1Edge; sc.cpol = SPI_CPOL_low; sc.firstBit = SPI_FirstBit_MSB; sc.pinmap = SPI_pinmap_P1; sc.nss = SPI_NSS_Soft; sc.mode = SPI_Mode_Master; SPI_config(&sc); SPI_cmd(ENABLE); } void main(void) { uint8_t sta; uint8_t tmp[] = { 0x1F, 0x80, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x28, 0x31, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x38, 0x41, 0x12, 0x13, 0x14, 0x15, 0x16, 0x37, 0x48}; //UTIL_enablePrintf(); SPI_init(); while (NRF24L01_check()) { //printf_tiny("Check failed\r\n"); sleep(500); } //printf_tiny("Check succeeded\r\n"); switch (CURRENT_SCEN) { case NRF24_SCEN_TX: NRF24L01_init(NRF24_MODE_TX); while (true) { // sta = NRF24L01_blockingTx(tmp); // tmp[1] = sta; sleep(500); } break; case NRF24_SCEN_RX: NRF24L01_init(NRF24_MODE_RX); EXTI_init(); while(true); break; case NRF24_SCEN_HALF_DUPLEX: NRF24L01_init(NRF24_MODE_RX); EXTI_init(); while (true) { NRF24L01_tx(tmp); sleep(500); } break; default: //printf_tiny("Unknown scen\r\n"); break; } }
test result
The test on STC12C5A60S2 did not reach the maximum speed of nRF24L01. After the interval between the transmitting ends was less than 10ms, the error rate increased significantly. In particular, opening the serial port output has a great impact on the error. It is speculated that it is the performance constraint of MCU itself. It needs to be tested with STM32
reference resources
- https://nrf24.github.io/RF24/
- https://tmrh20.blogspot.com/2014/03/high-speed-data-transfers-and-wireless.html
- https://github.com/nRF24/RF24