摘要:SPI 开了 DMA,数据却错位、丢字节?不是 DMA 坏了,而是 SPI 的 FIFO 阈值与 DMA 数据宽度不匹配。本文解析 8 位 SPI 如何对接 32 位 DMA。
一、问题描述(现象)
**SPI 发送 0xAA, 0xBB, 0xCC, 0xDD;
DMA 接收 Buffer 里却是 0xBB, 0xAA, 0xDD, 0xCC。**
很多工程师的排查方向是:
DMA 地址没递增?
SPI 模式配错了?
字节序搞反了?
二、原理分析
1. 物理模型
SPI 外设内部有一个 FIFO(先进先出队列)。
SPI Bus -> [FIFO (16-bit)] -> [DMA Request]
2. 核心参数
-
SPI Data Size:8-bit 或 16-bit。
-
DMA Data Width:Byte (8), Half-Word (16), Word (32)。
3. 反直觉真相
即使 SPI 配置为 8-bit,DMA 仍然可能按 16-bit 搬运。
当 SPI 的 FIFO 阈值(FTHLV)配置为 16-bit 时:
-
SPI 会等待凑齐 2 个字节才向 DMA 发起请求。
-
如果你的 DMA 配置为 Half-Word,数据顺序会发生混乱。
三、工程级解决方案
方案 1:对齐数据宽度(首选)
SPI 为 8-bit 时,DMA 必须为 Byte。
// SPI 配置
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
// DMA 配置
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
方案 2:处理 FIFO 阈值
如果使用 HAL 库,注意 SPI_CR2寄存器中的 FRXTH位。
-
8-bit 访问:FRXTH = 1(FIFO 阈值 8-bit)
-
16-bit 访问:FRXTH = 0(FIFO 阈值 16-bit)
HAL 库的坑:
HAL_SPI_Receive_DMA()在某些版本里不会自动设置 FRXTH,需要手动改寄存器。
方案 3:丢弃伪数据
在全双工 SPI 中,读的时候必须写。
// 错误:只读不写
DMA_Read();
// 正确:写 dummy byte 触发时钟
SPI->DR = 0xFF;
四、选型避坑建议
不要混用:SPI 8-bit 绝配 DMA Byte,SPI 16-bit 绝配 DMA Half-Word。
Cache 一致性:如果 MCU 有 D-Cache(如 STM32F7/H7),DMA 前必须 Invalidate或 Clean。
中断与 DMA:不要同时开启 SPI RXNE 中断和 DMA 接收。
五、总结 Checklist
-
[ ] SPI 数据位宽与 DMA 数据宽度是否一致?
-
[ ] FIFO 阈值(FRXTH)是否配置正确?
-
[ ] 是否处理了全双工下的 Dummy Write?
-
[ ] H7 系列是否处理了 Cache 一致性?
💡 注意区分:本文讨论的是 DMA 数据搬运错位。
若你的问题是 SPI 频率上不去,请检查 IO 速度,详见《SPI 速度上不去?》。
References
-
STM32 Reference Manual – SPI FIFO threshold
-
STM32 Application Note AN4031 – Using DMA with STM32


