STM32F103C8T6(Blue Pill)首次通过SPI读取NRF24L01+寄存器值异常问题
STM32F103C8T6(Blue Pill)首次通过SPI读取NRF24L01+寄存器值异常问题
看起来你已经做了非常细致的排查——逻辑分析仪确认NRF24输出完全正常,但STM32的SPI DR寄存器就是读不到正确的首字节,而且预读一次之后就恢复正常,这个现象其实是STM32 SPI外设常见的首次通信同步/时序等待缺失问题,我之前帮朋友排查过几乎一模一样的情况,给你拆解下原因和解决办法:
核心问题分析
从你的现象和排查结果来看,NRF24本身的输出没问题,问题出在STM32的SPI接收逻辑或者初始状态上:
- 首次SPI传输的等待逻辑缺失:你的读写函数里,大概率在发送命令字节后,没有等待
RXNE(接收缓冲区非空)标志位置位就直接读取了SPI1->DR。STM32的SPI是全双工的,发送一个字节的同时会接收一个字节,但接收完成需要时间——首次通信时,SPI外设的状态机还没完全就绪,RXNE还没触发你就读了DR,自然拿到的是寄存器初始值0x00。而当你先调用一次读函数,相当于完成了一次正确的带等待逻辑的传输,之后的状态机就同步了。 - 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, ¤t_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
相关产品推荐
相关产品推荐

