You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F103C8T6(Blue Pill)首次通过SPI读取NRF24L01+寄存器值异常问题

STM32F103C8T6(Blue Pill)首次通过SPI读取NRF24L01+寄存器值异常问题

看起来你已经做了非常细致的排查——逻辑分析仪确认NRF24输出完全正常,但STM32的SPI DR寄存器就是读不到正确的首字节,而且预读一次之后就恢复正常,这个现象其实是STM32 SPI外设常见的首次通信同步/时序等待缺失问题,我之前帮朋友排查过几乎一模一样的情况,给你拆解下原因和解决办法:

核心问题分析

从你的现象和排查结果来看,NRF24本身的输出没问题,问题出在STM32的SPI接收逻辑或者初始状态上:

  1. 首次SPI传输的等待逻辑缺失:你的读写函数里,大概率在发送命令字节后,没有等待RXNE(接收缓冲区非空)标志位置位就直接读取了SPI1->DR。STM32的SPI是全双工的,发送一个字节的同时会接收一个字节,但接收完成需要时间——首次通信时,SPI外设的状态机还没完全就绪,RXNE还没触发你就读了DR,自然拿到的是寄存器初始值0x00。而当你先调用一次读函数,相当于完成了一次正确的带等待逻辑的传输,之后的状态机就同步了。
  2. SPI外设初始状态未激活:STM32的SPI外设刚初始化完成后,内部状态机可能处于“未唤醒”的状态,需要一次完整的SPI传输(哪怕是空读NOP命令)来触发状态机进入正常工作模式。

具体解决办法

1. 修复SPI读写函数的等待逻辑

这是最关键的一点:任何时候读取SPI DR寄存器前,必须等待SPI_I2S_FLAG_RXNE标志位置位,而不是只等TXE(发送缓冲区空)或者BSY(总线忙)。

比如修改你的读写函数:

// 修正后的读8位寄存器函数
void NRF24_Read_8Bit_Register(uint8_t NRF24_Register, uint8_t *Data_Store){
    GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 拉低CS
    
    // 发送读寄存器命令
    SPI_I2S_SendData(SPI1, R_REGISTER | NRF24_Register);
    // 等待接收NRF24返回的STATUS寄存器
    while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
    uint8_t dummy_status = SPI_I2S_ReceiveData(SPI1); // 读取STATUS(可忽略或保存)
    
    // 发送dummy字节(0xFF)触发NRF24发送目标寄存器值
    SPI_I2S_SendData(SPI1, 0xFF);
    // 等待接收目标寄存器值
    while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
    *Data_Store = SPI_I2S_ReceiveData(SPI1);
    
    // 等待总线完全空闲
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
    GPIO_SetBits(GPIOA, GPIO_Pin_4); // 拉高CS
}

// 修正后的写8位寄存器函数
void NRF24_Write_8Bit_Register(uint8_t NRF24_Register, uint8_t NRF24_Register_Bit, uint8_t NRF24_Set_Reset){
    uint8_t current_val;
    NRF24_Read_8Bit_Register(NRF24_Register, &current_val);
    
    // 处理置位/复位操作
    if(NRF24_Set_Reset){
        current_val |= NRF24_Register_Bit;
    } else {
        current_val &= ~NRF24_Register_Bit;
    }
    
    GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 拉低CS
    
    // 发送写寄存器命令
    SPI_I2S_SendData(SPI1, W_REGISTER | NRF24_Register);
    while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
    (void)SPI_I2S_ReceiveData(SPI1); // 读取STATUS
    
    while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
    // 发送修改后的值
    SPI_I2S_SendData(SPI1, current_val);
    while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
    (void)SPI_I2S_ReceiveData(SPI1); // 读取空字节
    
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
    GPIO_SetBits(GPIOA, GPIO_Pin_4); // 拉高CS
}

2. 初始化后添加一次同步传输

在main函数中,完成SPI_SETUP和GPIO_SETUP后,先执行一次空的SPI传输(比如读STATUS寄存器或者发送NOP命令),同步STM32的SPI状态机:

int main(void){
    GPIO_SETUP();
    SPI_SETUP();
    USART_SETUP();
    SysTick_SETUP();
    
    // 新增:初始化后做一次同步传输
    uint8_t dummy;
    NRF24_Read_Status(&dummy); // 或者直接发送NOP命令做一次空读
    
    SysTick_Delay_mS(4000);
    // 后续的读写操作...
}

3. 检查CS引脚的时序和配置

  • 确认CS引脚的GPIO配置为推挽输出,速度设置为50MHz(SPI通信时钟可能较高,需要足够的切换速度)。
  • 确保CS引脚在初始化后处于高电平(NRF24的CS引脚高电平时处于待机模式,低电平时进入SPI模式),避免初始化过程中CS意外拉低导致NRF24进入异常状态。

为什么先调用读函数就正常?

因为先调用NRF24_Read_8Bit_Register时,这个函数(哪怕之前逻辑有小问题)会完成一次完整的SPI传输,相当于帮STM32的SPI状态机完成了“首次同步”,之后的读写操作就都能正确捕获NRF24返回的数据了。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 12:08:11