STM32F303 SPI接收数据字节顺序异常问题求助
STM32F303 SPI接收字节顺序异常问题排查与解决
问题现象
STM32F303读取SPI数据时出现字节顺序异常:
- 示波器显示数据序列为
0x9c、0、0、0,但实际接收的字节顺序完全不符; - 手动清理Rx FIFO后残留数据问题依旧;
- 使用DMA接收时,实际数据前会出现数量不定的0(3/2/1/0个),示波器显示正确顺序为
0xfa、0x29、0x82、0,但接收结果始终错位。
读取函数代码
volatile uint32_t wctr; #define WAITX(x) do{wctr = 0; while((x) && (++wctr < 360000)) IWDG->KR = IWDG_REFRESH; if(wctr==360000){ DBG("timeout"); return 0;}}while(0) int spi_read(uint8_t *data, uint32_t n){ if(spi_status != SPI_READY){ DBG("not ready"); return 0; } if(!spi_waitbsy()) return 0; // clear SPI Rx FIFO (void) SPI2->DR; while(SPI2->SR & SPI_SR_RXNE) (void) SPI2->DR; for(uint32_t x = 0; x < n; ++x){ WAITX(!(SPI2->SR & SPI_SR_TXE)); SPIDR = 0; WAITX(!(SPI2->SR & SPI_SR_RXNE)); data[x] = SPIDR; USB_sendstr("rd got "); USB_sendstr(uhex2str(data[x])); newline(); } return 1; }
轮询模式终端输出
rd got 0x00 rd got 0x00 rd got 0x00 rd got 0x9c Sregx10=0x9c000000 rd got 0x00 rd got 0x00 rd got 0x9c rd got 0x00 Sregx10=0x9c0000 rd got 0x00 rd got 0x9c rd got 0x00 rd got 0x00 Sregx10=0x9c00 rd got 0x9c rd got 0x00 rd got 0x00 rd got 0x00 Sregx10=0x9c rd got 0x00 rd got 0x00 rd got 0x00 rd got 0x9c Sregx10=0x9c000000
DMA模式接收结果
Sregn9=8 00 00 00 fa 29 82 00 00 Sregn9=8 00 00 fa 29 82 00 00 00 Sregn9=8 00 fa 29 82 00 00 00 00 Sregn9=8 fa 29 82 00 00 00 00 00
问题分析
基本可以排除硬件问题——轮询和DMA模式均出现相同异常,说明问题出在代码配置或逻辑上,核心排查点如下:
- SPI时序配置不匹配:CPOL(时钟极性)、CPHA(时钟相位)、数据位宽、MSB/LSB优先顺序若与外设不一致,会直接导致数据移位、错位,表现为多余的0或顺序颠倒。
- Rx FIFO清理不彻底:当前清理逻辑仅读取DR直到RXNE位清除,但STM32F3的SPI FIFO深度为4字节,仅检查RXNE可能无法完全清空FIFO,残留数据会干扰下一次接收。
- 片选信号时序错误:若片选拉低后未等待外设稳定就启动接收,或接收完成后未及时拉高片选,总线干扰会导致误接收空字节。
- 全双工模式冗余发送:当前代码用全双工模式发送空字节触发接收,若外设为半双工发送,多余的发送操作会引入时序偏差,导致接收错位。
解决方案
1. 核对SPI初始化参数
确保CPOL、CPHA、数据位宽、MSB/LSB顺序与外设手册完全一致。例如外设为MSB优先时,STM32不能配置为LSB优先,否则会出现字节内位序颠倒,表现为整体字节顺序异常。
2. 优化Rx FIFO清理逻辑
确保完全清空FIFO,可检查FIFO填充级别寄存器:
// 改进的FIFO清理逻辑 if(!spi_waitbsy()) return 0; // 读取所有RXNE数据 while(SPI2->SR & SPI_SR_RXNE) { (void)SPI2->DR; __NOP(); // 确保数据正确读出 } // 额外检查FIFO填充级别,确保为空 while((SPI2->SR & SPI_SR_FRLVL) != SPI_FRLVL_EMPTY) { (void)SPI2->DR; __NOP(); }
3. 修正片选信号时序
在片选拉低后添加外设稳定延时,接收完成后立即拉高片选:
int spi_read(uint8_t *data, uint32_t n){ if(spi_status != SPI_READY){ DBG("not ready"); return 0; } if(!spi_waitbsy()) return 0; // 拉低片选 SPI_CS_LOW(); // 等待外设稳定(根据外设手册调整延时长度) for(uint32_t i=0; i<100; i++) __NOP(); // 清空FIFO while(SPI2->SR & SPI_SR_RXNE) (void)SPI2->DR; while((SPI2->SR & SPI_SR_FRLVL) != SPI_FRLVL_EMPTY) (void)SPI2->DR; for(uint32_t x = 0; x < n; ++x){ WAITX(!(SPI2->SR & SPI_SR_TXE)); SPIDR = 0; WAITX(!(SPI2->SR & SPI_SR_RXNE)); data[x] = SPIDR; USB_sendstr("rd got "); USB_sendstr(uhex2str(data[x])); newline(); } // 接收完成后立即拉高片选 SPI_CS_HIGH(); return 1; }
4. 切换为半双工接收模式
若外设仅发送数据,将STM32 SPI配置为半双工接收模式(设置CR1寄存器的RXONLY位),无需发送空字节即可接收,避免冗余发送带来的时序干扰。
5. DMA模式优化
每次DMA传输前:
- 重置DMA传输计数器;
- 清空SPI的RXNE位与FIFO;
- 确保DMA传输完成后再读取数据,避免重复读取残留数据。
结论
该问题属于代码配置与逻辑错误,而非硬件问题,通过上述步骤可消除实际数据前数量不定的0,恢复正确的SPI接收顺序。
内容的提问来源于stack exchange,提问作者Eddy_Em
相关产品推荐
相关产品推荐

