DMA传输模式:单次、突发、循环与链表模式
这一节我们聊聊DMA的传输模式。说实话,很多工程师对DMA的理解停留在“能传数据”的层面,但不同模式的性能差异和适用场景,才是真正拉开差距的地方。我个人习惯把这四种模式看作一个成熟度进阶——从“能用”到“用得好”,中间差的就是对这些模式的理解。
核心认知:DMA传输模式决定了你的数据搬运效率、CPU干预频率和系统吞吐上限。选错了模式,总线带宽再高也白搭。
3.1 单次传输(Single Transfer)
最简单的模式。每次DMA请求只传输一个数据单元(一个字或一个字节),然后释放总线。下次传输需要重新触发。
我在项目中遇到过,某块低速传感器采集卡就是用单次模式。为什么?因为数据量小,传感器自己的采样率也低,单次传输足够用了。反而用突发模式的话,总线抢占太频繁,还会影响其他实时任务。
- 特点:传输粒度小,总线占用时间极短
- 中断频率:每传一个数据单元触发一次中断,中断延迟敏感
- 适用场景:低频外设、控制类应用、数据量小的采集
警告:别在高速外设(如千兆网卡、高速ADC)上用单次模式。我曾经见过有人这么干——中断风暴直接导致系统响应超时,整个协议栈都崩了。
3.2 突发传输(Burst Transfer)
突发模式就是一次DMA请求,连续传输多个数据单元。突发长度(Burst Length)通常可配置,常见的有4、8、16、32等。
你想想看,为什么突发模式更高效?因为每次DMA请求只需要一次总线仲裁,后续数据都在同一个总线事务里完成。说白了,省掉了反复申请总线的开销。
| 突发长度 | 总线效率 | 中断次数 | 典型场景 |
|---|---|---|---|
| 4 | 中等 | 较高 | SPI Flash读取 |
| 8 | 良好 | 中等 | 低速网络接口 |
| 16 | 高 | 较低 | 高速ADC、摄像头 |
| 32 | 极高 | 低 | DDR内存搬运 |
这里有个坑:突发长度不是越大越好。总线会被长时间占用,其他主设备(比如CPU本身)就得等着。我之前在做视频采集卡时,把突发长度设到了64,结果CPU的Cache refill等着总线空闲,帧处理延迟直接翻倍。
经验之谈:突发长度的选取要结合总线带宽和系统实时性要求。我的建议是,从8开始试,逐步增加,监控总线的等待周期。
3.3 循环模式(Circular Mode / Auto-Reload)
循环模式,也叫自动重载模式。DMA传输完成后,地址和计数器自动复位到初始值,又开始下一次传输。这样就不用每次重新配置DMA。
我记得做音频采集时用到这个模式。音频数据流是连续的,ADC采样率固定,用循环模式配合乒乓缓冲,完美解决了数据丢帧问题。
- 优点:无需CPU反复操作DMA寄存器,减少软件开销
- 中断策略:通常在半传输和完全传输时各触发一次中断,用于交换缓冲
- 痛点:如果中断处理不及时,DMA会覆盖未处理的数据——这就是所谓的“数据覆盖”问题
循环模式的一个关键参数是缓冲区大小。设太小,中断太频繁;设太大,响应延迟增加。我个人的习惯:在音频场景下,缓冲区设为1ms的数据量是个不错的起点。
3.4 链表模式(Linked List / Descriptor Mode)
这是DMA最灵活、也最有难度的模式。每个DMA传输任务由“描述符”(Descriptor)定义,多个描述符通过链表串起来。DMA控制器自动按链表顺序执行传输,完成后自动跳到下一个描述符。
讲真,链表模式在高端嵌入式芯片(比如TI的C6678、NXP的i.MX系列)里用得很多。它特别适合处理不规则的数据结构或复杂传输序列。
// 一个典型的DMA描述符结构(伪代码)
struct dma_descriptor {
void *src_addr; // 源地址
void *dst_addr; // 目的地址
uint32_t length; // 传输长度
uint32_t flags; // 控制标志(如中断使能)
struct dma_descriptor *next; // 指向下一个描述符
};
链表模式有几个核心优势:
- 零CPU干预:配置好链表后,CPU可以去做别的,DMA自己跑完整条链路
- 灵活传输:每个描述符可以有不同的源、目的和长度,甚至可以在不同外设之间切换
- 链路中断管理:可以在关键描述符上设置中断,实现“一个中断完成多个传输”的效果
我曾经在网络协议处理中用到链表模式。数据包头部和数据部分存储在内存的不同位置,每个描述符指向一部分,DMA自动完成“组装+搬运”。这样CPU就不用做内存拷贝,吞吐率直接翻了一倍不止。
3.5 如何选择传输模式?
这个问题没有标准答案,但我可以通过几个维度来帮你做判断:
- 数据量:单次传输适合小数据(几个字节),突发传输适合中等数据块(几十到几百字节),链表模式适合大规模、不规则传输
- 实时性要求:如果中断延迟敏感(比如控制环路),尽可能降低中断频率——用突发或链表模式
- 硬件支持:不是所有芯片都支持链表模式。在资源受限的MCU上,循环模式可能是最好的选择
- 开发复杂度:链表模式配置起来最麻烦,需要有足够的时间和验证手段
我的建议:如果你刚开始接触DMA优化,先用突发模式调通基本功能。等对硬件行为有了手感,再挑战链表模式。别一口气想着最优解,先跑起来再说。
嗯,模式的选择是性能优化的第一步。实际工作中,我经常看到有人用单次模式跑高速外设,结果CPU被中断淹死。也有些场景,明明链表模式能解决,却因为怕麻烦硬扛着高中断频率。我觉得,理解这些模式的差异和代价,是成为DMA高手的必修课。
还有一个细节值得注意:无论是哪种模式,都需要关注数据对齐和描述符的内存布局。举个例子,如果描述符跨了Cache行边界,DMA控制器可能会多读一次内存,性能直接损耗。我踩过这个坑,改完描述符对齐后,吞吐率提升了将近15%。所以,配置DMA时别光盯着模式,底层的内存访问方式也很重要。
避坑指南:我曾经在一次项目中,把链表模式的描述符放在了DDR的Cacheable区域。每次DMA从描述符链表里取下一项时,Cache line刷回内存的操作搞乱了时序。解决办法很简单——把描述符放到Non-Cacheable内存区域,或者做好Cache刷新。这种问题很难复现,排查起来极其痛苦。