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 怎么工作的?—— 一个标准流程
我做项目调试时,习惯先在心里跑一遍这个流程:
- CPU 配置 DMA 通道:源地址、目的地址、传输长度、传输模式、外设请求源(比如 SPI 的 TX/RX 请求)。然后使能 DMA。
- 外设(SPI)启动工作,当 SPI 发送/接收移位寄存器准备好,它会断言对应的 DMA 请求信号。
- DMA 控制器收到请求,判断优先级和总线仲裁。如果没有更高优先级任务,它就申请总线控制权。
- DMA 得到总线授权后,在一个时钟周期(或几个)内完成一次数据传:从源地址读取,写入目的地址。
- DMA 释放总线,等待下一个外设请求。
- 直到传输计数值减到 0,DMA 产生完成中断通知 CPU。
这里有个隐藏问题:步骤 3 到 4 之间的时间,就是“DMA 请求延迟”。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。