2. DMA控制器原理:双缓冲模式与循环模式在SPI场景下的选择策略
DMA这东西,说白了就是帮CPU干苦力的。SPI通信时,每发一个字节CPU就得去填数据寄存器,每收一个字节又得赶紧去读走——这种"字节搬运"最浪费CPU。我早年做第一版SPI驱动时,CPU主频跑200MHz,结果SPI速率一上10MHz,CPU就被占得死死的,其他任务全卡住。后来上了DMA,整个世界清静了。
但DMA不是一开就完事。它的工作模式选不对,反而会出各种怪问题。今天咱们重点聊两种模式:双缓冲模式和循环模式。在SPI场景下,选哪个?怎么配?我踩过的坑,你最好别踩。
2.1 DMA传输的基本流程
先看一张图,把DMA怎么搬运数据理清楚:
流程很简单:CPU配好DMA通道,告诉它"从哪搬、搬到哪、搬多少",然后DMA就自己干活了。搬完了发个中断通知CPU。但这里有个关键问题——缓冲区用完了怎么办?
2.2 双缓冲模式(Double Buffer Mode)
双缓冲模式,就是DMA有两块缓冲区,轮流用。一块在传输时,另一块供CPU处理。我习惯叫它"乒乓缓冲"。
工作原理
- DMA先操作缓冲区A,传输数据
- 传输完成后,自动切换到缓冲区B
- 同时触发中断,通知CPU处理缓冲区A的数据
- CPU处理A时,DMA继续往B搬数据
- B完成后,再切回A,如此循环
核心优势:CPU和DMA可以并行工作,互不阻塞。数据处理的"流水线"效率极高。
SPI场景适用性
| 场景 | 推荐度 | 原因 |
|---|---|---|
| SPI从机接收不定长数据 | ★★★★★ | 双缓冲可以边收边处理,不会丢包 |
| SPI主机发送大块数据 | ★★★★☆ | CPU可以提前准备下一块数据 |
| SPI全双工通信 | ★★★☆☆ | 需要同时管理收发两个双缓冲,配置复杂 |
| SPI速率低于1MHz | ★★☆☆☆ | CPU直接处理就够了,双缓冲反而增加延迟 |
我的经验:做SPI从机接收传感器数据流时,我必用双缓冲。主机发数据包没有固定间隔,双缓冲能保证CPU任何时候都有完整的数据包可处理,不会因为处理慢了而丢数据。
2.3 循环模式(Circular Mode / Auto-Reload Mode)
循环模式,就是DMA传输完一轮后,自动把地址指针重置到起点,继续下一轮传输。缓冲区是"环形"的,永远不停止。
工作原理
- DMA配置一个固定大小的环形缓冲区
- 传输到末尾后,地址自动回绕到开头
- DMA持续运行,永不停止
- CPU通过"水位线"或"半满中断"来取数据
注意:循环模式下,DMA不会自动停止。如果CPU取数据的速度跟不上DMA写入的速度,新数据会覆盖旧数据——这就是经典的"数据覆盖"问题。
SPI场景适用性
| 场景 | 推荐度 | 原因 |
|---|---|---|
| SPI连续数据流采集(如音频、ADC) | ★★★★★ | 数据源源不断,循环模式最省事 |
| SPI周期性发送固定波形 | ★★★★★ | 配置一次,DMA自动循环发送 |
| SPI突发性通信 | ★☆☆☆☆ | 循环模式会浪费中断和带宽 |
| SPI从机接收不定长数据 | ★★☆☆☆ | 数据边界不好判断,容易覆盖 |
2.4 两种模式的核心对比
我整理了一张对比表,你选型时直接对着看:
| 对比维度 | 双缓冲模式 | 循环模式 |
|---|---|---|
| 缓冲区数量 | 2块(A/B) | 1块(环形) |
| 自动停止 | 每块传完自动停 | 永不停止 |
| 中断频率 | 每块传完一次中断 | 可配置半满/水位中断 |
| 数据覆盖风险 | 低(有明确边界) | 高(CPU处理慢时) |
| CPU负载 | 中等(中断+处理) | 较低(中断少) |
| 适用数据流 | 块数据、不定长数据 | 连续流数据 |
| 配置复杂度 | 中等 | 简单 |
2.5 选择策略:什么时候用哪个?
这个问题,我直接给结论:
- 数据是"包"还是"流"?
- 包(有开始有结束)→ 双缓冲
- 流(源源不断)→ 循环模式
- 数据速率高不高?
- 高(SPI时钟 > 10MHz)→ 双缓冲(防止覆盖)
- 低(SPI时钟 < 1MHz)→ 循环模式(省中断)
- CPU忙不忙?
- CPU很忙,只能偶尔处理 → 循环模式+大缓冲区
- CPU有空,需要实时处理 → 双缓冲
- 数据边界重要吗?
- 重要(比如协议帧)→ 双缓冲
- 不重要(比如连续采样)→ 循环模式
我的选择口诀:"有头有尾用双缓,源源不断用循环;高速怕丢用双缓,CPU太忙用循环。"
2.6 避坑指南
我曾经踩过的坑:做SPI从机接收时,我图省事用了循环模式。结果主机发数据包间隔不固定,CPU处理一个包时,DMA已经把下一个包写进来了——两个包的数据混在一起,解析全乱套。后来换成双缓冲,每个包都有完整的缓冲区,问题立刻解决。
还有几个常见问题:
- 双缓冲切换时丢数据?检查一下切换中断的响应时间。如果CPU关中断太久,DMA可能已经覆盖了缓冲区。我一般把DMA中断优先级设到最高。
- 循环模式半满中断不准?有些MCU的半满中断是在"半满"时触发一次,不是持续触发。需要配合DMA传输计数寄存器来判断实际水位。
- 缓冲区大小怎么定?双缓冲建议每块至少能装2-3个数据包;循环模式建议缓冲区大小是CPU处理周期的2倍以上,留出余量。
2.7 小结
双缓冲和循环模式,没有绝对的好坏。关键看你的SPI场景是"块传输"还是"流传输"。我个人的习惯是:不确定时先用双缓冲,因为它更安全、边界更清晰。等系统稳定了,再根据性能需求考虑是否换成循环模式。