3. DMA传输模式:单次传输、块传输、请求传输、链式传输
好,咱们今天来聊聊DMA的四种传输模式。说实话,这四种模式我刚开始接触时也绕了好一阵子。每种模式都有它自己的脾气,用对了地方事半功倍,用错了……嗯,我踩过的坑后面会跟大家细说。
3.1 单次传输模式
单次传输,说白了就是一次DMA请求只传输一个数据单元。这个数据单元可以是8位、16位或者32位,具体看你外设的配置。
工作流程:
- 外设发出DMA请求
- DMA控制器响应,传输一个数据单元
- 传输完成后释放总线
- 等待下一次请求
适用场景:低速外设,比如UART、I2C。数据量小,实时性要求不高。
我个人习惯在调试阶段先用单次传输模式。为什么?因为好排查问题。每次只传一个数据,出错了很容易定位是外设的问题还是DMA配置的问题。
小技巧:单次传输模式下,记得把外设的FIFO打开。我曾经遇到过UART丢数据的情况,后来发现是没开FIFO,CPU响应不过来。
3.2 块传输模式
块传输模式,一次请求传输一整块数据。块的大小可以配置,比如64字节、128字节、1024字节等等。
关键参数:
| 参数 | 说明 |
|---|---|
| 块大小 | 一次传输的数据量,通常以字节为单位 |
| 传输次数 | 可以配置为单次块传输或多次块传输 |
| 地址增量 | 源地址和目的地址是否自动递增 |
你想想看,如果每次只传一个字节,那DMA的优势根本发挥不出来。块传输模式才是真正发挥DMA性能的模式。
注意:块传输模式下,DMA会占用总线直到整个块传输完成。如果系统中有更高优先级的中断,可能会被延迟响应。我在一个音频项目中就吃过这个亏——DMA传数据时把定时器中断给堵了,导致音频播放出现卡顿。
3.3 请求传输模式
请求传输模式,也叫突发传输模式。它跟块传输有点像,但有个关键区别:每次传输前都需要外设重新发起请求。
工作流程:
- 外设发出请求
- DMA传输一个数据单元
- 释放总线
- 外设再次发出请求
- 重复直到传输完成
说白了,请求传输模式就是单次传输的升级版。它把多次单次传输组合成一个DMA事务,但每次传输之间都会释放总线。
优势:不会长时间占用总线,对系统实时性影响小。适合中等速度的外设,比如SPI、SDIO。
我记得有一次做SD卡驱动,用块传输模式总是超时。后来换成请求传输模式,问题就解决了。为什么?因为SD卡的数据准备好需要时间,块传输模式等不及就超时了。
3.4 链式传输模式
链式传输模式,这才是DMA的高级玩法。它允许你把多个DMA传输任务链接起来,形成一个链表。DMA控制器会自动执行链表中的每个任务,不需要CPU干预。
数据结构:
struct dma_descriptor {
uint32_t src_addr; // 源地址
uint32_t dst_addr; // 目的地址
uint32_t transfer_size; // 传输大小
uint32_t control; // 控制字
struct dma_descriptor *next; // 下一个描述符
};
每个描述符就是一个DMA传输任务。最后一个描述符的next指针设为NULL,表示链表结束。
链式传输的优势:
- 零CPU干预:配置好链表后,DMA自己跑完所有任务
- 灵活性强:可以动态添加、删除、修改传输任务
- 适合复杂场景:比如音频播放、视频采集、网络数据包处理
实战经验:我曾经在一个视频采集项目中,用链式传输实现了双缓冲。一个描述符指向缓冲区A,另一个指向缓冲区B。DMA自动在A和B之间切换,CPU只需要处理已经填满的缓冲区。效果非常好,CPU占用率从30%降到了5%。
避坑指南:链式传输最怕描述符链表断裂。我曾经因为描述符的内存对齐问题,导致DMA读到了错误的next指针,整个系统直接挂掉。所以,描述符的内存对齐一定要按照芯片手册的要求来,通常是4字节或8字节对齐。
3.5 四种模式对比
| 模式 | 总线占用 | 传输效率 | 实时性 | 适用场景 |
|---|---|---|---|---|
| 单次传输 | 低 | 低 | 高 | 低速外设、调试 |
| 块传输 | 高 | 高 | 低 | 高速外设、大数据量 |
| 请求传输 | 中 | 中 | 中 | 中等速度外设 |
| 链式传输 | 可配置 | 最高 | 可配置 | 复杂场景、多任务 |
选哪种模式,其实没有绝对的对错。我个人的经验是:先看外设的速度,再看系统的实时性要求,最后考虑开发复杂度。简单场景用单次或块传输,复杂场景上链式传输。
嗯,这四种模式就讲到这里。下一章咱们聊聊DMA的缓冲区管理,那可是个容易出坑的地方。