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

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模式均出现相同异常,说明问题出在代码配置或逻辑上,核心排查点如下:

  1. SPI时序配置不匹配:CPOL(时钟极性)、CPHA(时钟相位)、数据位宽、MSB/LSB优先顺序若与外设不一致,会直接导致数据移位、错位,表现为多余的0或顺序颠倒。
  2. Rx FIFO清理不彻底:当前清理逻辑仅读取DR直到RXNE位清除,但STM32F3的SPI FIFO深度为4字节,仅检查RXNE可能无法完全清空FIFO,残留数据会干扰下一次接收。
  3. 片选信号时序错误:若片选拉低后未等待外设稳定就启动接收,或接收完成后未及时拉高片选,总线干扰会导致误接收空字节。
  4. 全双工模式冗余发送:当前代码用全双工模式发送空字节触发接收,若外设为半双工发送,多余的发送操作会引入时序偏差,导致接收错位。

解决方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:20:02