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 请求传输模式

请求传输模式,也叫需求传输模式。它跟块传输有点像,但有一个关键区别:传输的连续性由外设的请求信号控制。

工作机制:

  1. 外设发出请求信号
  2. DMA开始传输数据
  3. 只要请求信号保持有效,DMA就持续传输
  4. 请求信号撤销,DMA立即暂停
  5. 请求信号再次有效,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的请求和应答信号,确认时序没问题,再去看数据对不对。硬件问题排除了,剩下的就是软件配置的事了。