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

请求传输模式,也叫突发传输模式。它跟块传输有点像,但有个关键区别:每次传输前都需要外设重新发起请求。

工作流程:

  1. 外设发出请求
  2. DMA传输一个数据单元
  3. 释放总线
  4. 外设再次发出请求
  5. 重复直到传输完成

说白了,请求传输模式就是单次传输的升级版。它把多次单次传输组合成一个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的缓冲区管理,那可是个容易出坑的地方。