4、DMA传输模式:单次传输模式、块传输模式、请求传输模式、链式传输模式(Scatter-Gather)
DMA的传输模式,说白了就是CPU怎么跟DMA控制器说「你帮我搬一下数据」。不同的模式,对应着不同的应用场景。我这些年调过的驱动里,选错模式导致性能上不去的案例,还真不少见。
4.1 单次传输模式
这是最基础的模式。每次DMA请求,只传输一个数据单元。这个单元可以是8位、16位或者32位,取决于你的硬件配置。
工作流程:
- 外设发出DMA请求
- DMA控制器响应,传输一个数据单元
- 传输完成后,释放总线控制权
- 等待下一次请求
适用场景:低速外设,比如UART、I2C。数据是一个字节一个字节来的,没必要一次搬一大堆。
我记得有一次调试一个老款MCU的UART驱动,用的就是单次传输模式。每次收到一个字节,DMA就搬一次。虽然效率不高,但胜在简单可靠,CPU几乎不用操心。
注意:单次传输模式下,每次传输都有总线仲裁的开销。如果外设速度很快(比如高速ADC),这种模式会成为瓶颈。
4.2 块传输模式
块传输模式,也叫突发传输模式。它允许DMA在一次请求中,连续传输多个数据单元。传输完成后才释放总线。
关键参数:
- 块大小:一次突发传输的数据单元个数
- 传输宽度:每个数据单元的位宽
- 地址增量:源地址和目的地址的更新方式
// 伪代码示例:配置块传输
dma_config.block_size = 16; // 每次突发传16个单元
dma_config.src_width = 32; // 32位数据宽度
dma_config.dst_width = 32;
dma_config.src_addr_inc = 4; // 地址递增4字节
dma_config.dst_addr_inc = 4;
你想想看,如果外设的FIFO深度是16,你用单次传输模式,每次只读一个数,那CPU得频繁处理中断。用块传输模式,一次把FIFO搬空,效率高得多。
我的经验:块大小不是越大越好。太大的块会导致DMA长时间占用总线,其他高优先级的外设可能会饿死。我一般建议块大小不超过外设FIFO深度的一半。
4.3 请求传输模式
请求传输模式,也叫需求传输模式。它跟块传输有点像,但有一个关键区别:传输的连续性由外设的请求信号控制。
工作机制:
- 外设发出请求信号
- DMA开始传输数据
- 只要请求信号保持有效,DMA就持续传输
- 请求信号撤销,DMA立即暂停
- 请求信号再次有效,DMA从断点继续
我曾经在一个音频采集项目里用过这种模式。音频编解码器的数据准备好后,会拉高请求线。DMA就一直搬数据,直到FIFO空了请求线拉低。这种模式特别适合数据产生速率不稳定的外设。
核心优势:传输粒度由外设决定,DMA控制器完全跟随外设的节奏。不会多传,也不会少传。
4.4 链式传输模式(Scatter-Gather)
这个模式,我个人认为是DMA最强大的功能。它允许你预先定义多个传输任务,形成一个链表。DMA控制器会自动执行链表中的每个任务,执行完一个自动跳到下一个。
为什么需要链式传输?
想象一下,你要从三个不连续的物理地址读取数据,然后写入一个连续的缓冲区。或者反过来,从一个连续缓冲区读取数据,分散写入三个不同的外设寄存器。用普通的DMA模式,你得配置三次,每次都要CPU介入。用链式传输,一次配置,DMA自己搞定。
// 链式传输描述符结构
struct dma_descriptor {
uint32_t src_addr; // 源地址
uint32_t dst_addr; // 目的地址
uint32_t transfer_size; // 传输大小
uint32_t next_desc; // 下一个描述符地址
uint32_t control; // 控制标志
};
// 配置三个描述符
struct dma_descriptor desc1 = {0x1000, 0x2000, 64, &desc2, 0};
struct dma_descriptor desc2 = {0x3000, 0x2000 + 64, 128, &desc3, 0};
struct dma_descriptor desc3 = {0x5000, 0x2000 + 192, 32, NULL, DMA_CTRL_LAST};
避坑指南:我曾经在调试链式传输时,发现DMA执行完第一个描述符后卡住了。查了半天,原来是描述符的next_desc指针没有正确对齐。很多DMA控制器要求描述符地址按8字节或16字节对齐,这个细节很容易忽略。
Scatter-Gather的典型应用:
- 网络数据包处理:接收到的数据包可能分散在多个缓冲区,用SG可以一次性收集
- 视频帧处理:YUV数据可能分三个平面存储,SG可以分别搬运
- 文件系统缓存:文件数据在内存中可能不连续,SG能高效处理
4.5 四种模式的对比
| 模式 | 传输粒度 | CPU干预 | 适用场景 | 总线占用 |
|---|---|---|---|---|
| 单次传输 | 1个数据单元 | 每次传输后 | 低速外设(UART、I2C) | 低 |
| 块传输 | 固定块大小 | 每块传输后 | 高速外设(ADC、SPI) | 中 |
| 请求传输 | 由外设决定 | 请求撤销时 | 速率不稳定的外设 | 中 |
| 链式传输 | 多个任务链 | 整个链完成后 | 复杂内存操作、SG | 高 |
嗯,这里要注意一点:不是所有DMA控制器都支持全部四种模式。有些低端MCU的DMA只有单次和块传输,链式传输通常出现在高性能处理器上。选型的时候一定要看数据手册。
我的建议:如果你刚开始接触DMA,先从单次传输模式入手。把基础搞懂了,再逐步尝试块传输和链式传输。一口吃不成胖子,DMA配置错了,轻则数据错乱,重则系统死机。
最后说一句,链式传输的调试是最痛苦的。我建议先用逻辑分析仪抓一下DMA的请求和应答信号,确认时序没问题,再去看数据对不对。硬件问题排除了,剩下的就是软件配置的事了。