nRF24L01基于FIFO TX队列的发送性能优化

RF24项目代码分析

函数 bool writeFast(const void* buf, uint8_t len)

这个函数充分利用了nrf24带有3x32字节的FIFO发送队列, 在队列未满之前持续写入.
使用这个函数时需要屏蔽IRQ的外部中断, 它不需要中断信号也不会去读取IRQ状态, 因为它是通过读取TX_FULL标志位来判断是否继续写入的

// 仅当 FIFO 已满时阻塞, 如果FIFO满, 会在这里阻塞直到TX成功或者失败
while ((get_status() & (_BV(TX_FULL)))) {
    if (status & _BV(MAX_RT)) {
        return 0;  // 如果状态MAX_RT标志置位, 说明之前的数据发送失败, 返回0, 上级调用得到0的结果后可以启用重新发送
    }
    //...
}
startFastWrite(buf, len, multicast);  // 开始写入

函数 void startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx = 1)

非阻塞的快速写入(发送), 这里最后一个参数默认值就是1, 所以默认情况下ce(HIGH)会一直被执行.
这个函数会使CE一直保持高电平, 这时候NRF24会一直保持在TX或STANDBY-II状态, 直到调用txStandBy()命令.
这个函数可以用于一次性调用NRF24发送多组数据.

// 根据文档, 如果要在非接收模式下设置PTX模式, 需要做的就是写入数据并设置CE为高电平, 在这个模式下, 如果持续往 FIFO buffers 里写入
// 数据包会立即被发送, 而不需要等待130us的间隔, 否则会进入 Standby-II 模式, 这样依然比待机模式快, 避免了每次发送时修改 config 寄存器并等待150us
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);
    }
}

注意: 不能让NRF24在TX模式下保持FIFO队列满状态超过4ms. 启用了自动重发(auto retransmit)和自动应答(autoAck)后, NRF24保持TX模式的时间长度依然满足这个规则, 这样允许调用txStandBy() 清空FIFO队列或确保发送之间保持足够的时间间隔.

函数 bool txStandBy();

读取FIFO_STATUS寄存器, 直至TX_EMPTY标志位置位.

  • 如果队列已空, 则停止发送ce(LOW), 进入STANDBY-I模式
  • 在循环中检查status中的MAX_RT标志位, 如果被置位也停止发送, 清空队列, 返回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;
}

函数 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);
}

读取过程

  • 根据手册 The STATUS register is serially shifted out on the MISO pin simultaneously to the SPI command word shift-ing to the MOSI pin 在每次读取或者写入操作时, 在发出指令的同时, 一边是上位机通过MOSI口往NRF24写, 一边NRF24会把STATUS寄存器的值通过MISO口往上位机写, 所以在任何读写寄存器的操作时, 都能交换得到STATUS寄存器的值
  • 根据手册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. 当IRQ从高电平转低电平时才会更新STATUS寄存器中3-bit的pipe信息, 当电平还在转换过程中时读取的STATUS寄存器值是不可靠的. 要先清除状态标志位, 再读取STATUS值.

读取时一般使用以下的步骤, 启用中断, 中断时先检查是什么标志位, 然后读取有数据的pipe编号

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
    //...
  }
}

读取status寄存器判断标志位和清除标志位是在一个指令中完成的

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

再次读取status寄存器, 用于判断pipe编号

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

在STC12C5A60S2上的测试

发送端

为了避免干扰, 注释掉了串口输出部分, 使用MAX7219的8x8LED作为输出显示

#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("
");
}

/**
* 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
", tx_ds, max_rt, rx_dr, pipe_num);
    //printf_tiny("TX_DS:%x
", 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
", nrf24_state);
        if (nrf24_state & NRF24_FLAG_MAX_RT) {
            return false;
        }
    }
    //printf_tiny("STATE:%x
", 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
");
        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
");
        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
", 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
");
        break;
    }
}

接收端

接收端也注释了串口输出

#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("
");
}

/**
* 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
", buf[0]);
    //printf_tiny("TX_DS:%x, MAX_RT:%x, RX_DR:%x, PIPE:%x, RX:%x
", 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
");
        sleep(500);
    }
    //printf_tiny("Check succeeded
");

    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
");
        break;
    }
}

在STC12C5A60S2上的测试结果

在STC12C5A60S2上测试并未达到nRF24L01的最大速度, 在发送端间隔小于10ms后, 错误率明显增大, 特别是打开串口输出对错误的影响很大, 猜测是MCU本身的性能约束.需要用STM32再做测试.

在STM32F401CCU6上测试

Update 2021-09-17: 今天在两块STM32F401CCU6的最小板上测试了, 使用的是STM32Cube F4 的 HAL 库, 发送是fastWrite, 关闭中断, 接收是基于中断的接收.

  • 接收和发送都是25MHz的外部振荡源
  • 以下情况, 发送端都是循环无延迟的发送
  • 接收端使用printf输出
    • 如果每次都需要输出32个字节的内容, 发送端的错误率占比接近55%
    • 如果每次只输出一个.号, 发送端错误率接近0
  • 接收端仅开启LED闪灯, 发送端错误率接近0

总结

综上, 基于TX FIFO进行发送的优化方式是有效的, 但是对MCU的性能要求较高, 需要接收端有很高的中断响应处理速度. 在速率要求较高的场合, STC12及以下的单片机是不太合适的.

参考

原文地址:https://www.cnblogs.com/milton/p/15259485.html