DMA 基本原理:从“CPU 亲力亲为”到“硬件自动搬运”

大家好,我是你们这堂课的讲师。

要讲清 DMA 请求延迟对 SPI 吞吐率的影响,咱们得先聊聊 DMA 到底是什么。

说白了,DMA 就是 Direct Memory Access,直接存储器访问。它的核心想法很简单:让数据在外设和内存之间自动搬运,别再劳烦 CPU 亲自一条条指令地搬。

你们想想看,如果没有 DMA,CPU 要怎么做 SPI 数据收发?它得执行一个循环:读 SPI 数据寄存器,存到内存;或者从内存拿数据,写到 SPI 数据寄存器。一次一个字节,来来回回。我早期做项目时,就见过主频 72 MHz 的 MCU,SPI 时钟只跑 8 MHz,CPU 就被这简单的数据搬运占用了超过 30% 的算力。这太浪费了。

DMA 控制器的核心功能

DMA 控制器相当于一个专用的硬核搬运工。它有几项关键能力:

  • 自主传输:它接管总线控制权,不经过 CPU 执行指令,直接在源地址和目的地址间移动数据。
  • 请求响应:外设(比如 SPI)准备好数据后,会发一个硬件请求信号给 DMA。DMA 收到就执行一次传输。
  • 多通道并行:一个 DMA 控制器通常有多个通道,可以同时为多个外设服务。当然,同一时刻只能有一个通道占用总线。
  • 传输模式灵活:支持单次传输、循环传输、内存到内存、内存到外设、外设到内存等模式。
我的一个习惯:在开始分析延迟影响前,先确认 DMA 的传输宽度是否与外设数据寄存器宽度匹配。如果 SPI 是 8 位,DMA 也配成 8 位,别自动扩展成 16 位,否则会多搬无用数据。

DMA 的压倒性优势

它解决了什么痛点?我总结了几点最直接的:

对比项 传统 CPU 轮询/中断搬运 DMA 硬件搬运
CPU 占用率 极高,可能占 30%–80% 极低,仅起始终结需干预
数据吞吐率 受 CPU 主频和指令周期限制 接近总线峰值带宽
响应确定性 受中断优先级、上下文切换影响 硬件信号响应,抖动小
代码复杂度 需要手动管理缓冲区、中断服务程序 配置好后自动运行,代码简洁
功耗 CPU 高频运转,功耗大 CPU 可进入低功耗模式,DMA 单独工作

你看,差距很明显。尤其在高速 SPI 场景下,CPU 根本跟不上硬件自然节奏。

关键认知:DMA 不是“魔法”,它只是把数据搬移从 CPU 指令流水线里剥离出来,交给专有硬件完成。但它的请求延迟,会直接影响外设的连续性。这正是本课程的核心关注点。

DMA 怎么工作的?—— 一个标准流程

我做项目调试时,习惯先在心里跑一遍这个流程:

  1. CPU 配置 DMA 通道:源地址、目的地址、传输长度、传输模式、外设请求源(比如 SPI 的 TX/RX 请求)。然后使能 DMA。
  2. 外设(SPI)启动工作,当 SPI 发送/接收移位寄存器准备好,它会断言对应的 DMA 请求信号。
  3. DMA 控制器收到请求,判断优先级和总线仲裁。如果没有更高优先级任务,它就申请总线控制权。
  4. DMA 得到总线授权后,在一个时钟周期(或几个)内完成一次数据传:从源地址读取,写入目的地址。
  5. DMA 释放总线,等待下一个外设请求。
  6. 直到传输计数值减到 0,DMA 产生完成中断通知 CPU。

这里有个隐藏问题:步骤 3 到 4 之间的时间,就是“DMA 请求延迟”。CPU 访问内存、其他通道并发、总线仲裁等因素都会拉长它。后面我们会用数据说话。

DMA 数据流与 SPI 交互逻辑 CPU 核心 (配置 & 响应完成中断) 总线仲裁器 (优先级 & 竞争处理) DMA 控制器 (通道0/1/2/3) SPI 外设 (TX/RX FIFO) 系统内存 (数据缓冲区) 总线授权 请求 数据总线 DMA 读/写 SPI 数据寄存器 DMA 请求延迟产生点 1. SPI 发出请求 → DMA 控制器收到:传递延迟(布线 + 同步器) 2. DMA 控制器内部仲裁:别的通道正占用总线?优先级排队 3. 总线仲裁:CPU 或其他主设备正在访问总线?等待释放

上面这张图展示了 DMA 与 SPI、CPU 的关系。注意看红色虚线是请求路径,灰色实线是数据路径。我习惯把“请求到达 DMA”到“DMA 开始执行传输”之间的时间差叫做 DMA 请求延迟。这个指标在高速 SPI 下非常敏感。

什么场景适合用 DMA?

当然,DMA 不是万能的。我一般这样判断:

  • 适合:
    • 数据量大,比如 SPI 刷 LCD、读取 SD 卡、高速 ADC 采集。
    • 数据频率固定,外设请求信号规整。SPI、I2S、UART 都是好搭档。
    • CPU 有更重要的任务要做,比如跑算法、处理网络协议。
  • 不适合:
    • 偶尔才来一个数据,比如按键中断。用 DMA 反而浪费配置开销。
    • 数据量极小(比如每次传输就 1-2 个字节)。
    • 需要每字节都做复杂判断或变换(DMA 只负责搬,不能算)。
我曾经踩过一个坑:在一个低功耗项目中,我给 SPI 屏幕刷图启用了 DMA,结果每次刷屏前 DMA 配置和使能耗时反而比 CPU 直接搬还多。后来我做了基准测试,发现块大小超过 128 字节后 DMA 才真正开始省时。所以,小颗粒度的传输里别无脑用 DMA。