DMA 基础概念:它到底在解决什么问题?

各位同学,咱们今天聊 DMA。很多初学者觉得 DMA 是个黑盒子,配置完就不管了。其实没那么玄乎。DMA 的全称是 Direct Memory Access,直接存储器访问。说白了,就是让数据在内存和外设之间自己搬,不用 CPU 一趟一趟跑腿。

我刚开始做嵌入式那会儿,有个项目要用 ADC 采集音频数据,CPU 每采一个点就得进一次中断,把数据从 ADC 数据寄存器搬到内存数组里。采样率一高,CPU 几乎啥也干不了,全在搬数据。后来换成 DMA,CPU 彻底解放了,该干啥干啥。嗯,这就是 DMA 的核心价值——把 CPU 从重复的数据搬运中解放出来。

DMA 工作原理:谁在干活?

DMA 传输的本质,是 DMA 控制器接管了总线控制权。CPU 把传输任务交代给 DMA 控制器,然后 DMA 控制器自己去完成数据搬运,搬运完了再通知 CPU。

我习惯把 DMA 传输拆成三个角色:

  • 源地址:数据从哪来。可以是外设的数据寄存器,也可以是内存数组。
  • 目的地址:数据搬到哪去。可以是内存,也可以是外设的发送寄存器。
  • 传输长度:搬多少数据。单位可以是字节、半字或字。

举个例子,你要把 ADC 采集的 1024 个数据搬到内存数组 adc_buffer[1024] 里。传统做法是 CPU 每次进中断读一个数据,循环 1024 次。用 DMA 呢?你只需要配置好源地址(ADC->DR)、目的地址(adc_buffer)、传输长度(1024),然后启动 DMA。剩下的活,DMA 控制器自己干。

关键点:DMA 传输期间,CPU 可以继续执行其他代码,或者进入睡眠模式降低功耗。这是低功耗设计的核心手段之一。

我曾经在一个电池供电的传感器节点上,用 DMA + 睡眠模式把系统功耗从 5mA 降到了 200μA。怎么做到的?ADC 采集用 DMA 自动搬运,CPU 全程睡觉,等 DMA 传输完成中断再唤醒 CPU 处理数据。

DMA 传输模式:三种常见玩法

STM32 的 DMA 支持几种传输模式,我挑最常用的三种讲:

传输模式 特点 典型应用场景
单次传输 传输一次就停,需要重新配置才能再次启动 一次性数据搬移,比如把数据从 Flash 搬到 RAM
循环传输 传输完成后自动从头开始,无限循环 ADC 连续采样、音频数据流、串口连续收发
突发传输 一次请求传输多个数据单元(突发长度可配) 高速外设(如 SDIO、FSMC)批量数据传输

单次传输最简单。配置好源、目的、长度,启动,传输完成就停。我早期做的一个项目,需要把传感器校准参数从 Flash 搬到 RAM 里加速访问,用的就是单次传输。一次搞定,不用操心。

循环传输我用的最多。比如做音频播放,DMA 循环把音频数据从内存搬到 DAC,CPU 只需要偶尔更新一下缓冲区内容。你想想看,如果没有循环模式,每次传输完都得重新配置 DMA,那多麻烦。

突发传输稍微复杂一点。它允许 DMA 在一次总线仲裁中获得多个传输周期的权限,连续搬好几个数据。适合高速外设,但要注意总线冲突。我记得有个项目用 SDIO 读写 SD 卡,突发传输没配好,导致 DMA 和 CPU 抢总线,系统卡顿。后来把突发长度调小,问题解决了。

个人建议:初学者先从单次传输入手,跑通了再试循环模式。突发传输等你对 DMA 比较熟悉了再碰,不然踩坑了不好排查。

DMA 请求与响应机制:谁叫谁?

DMA 不是自己主动干活的,它需要被触发。触发源就是外设的 DMA 请求信号。

流程是这样的:

  1. 外设(比如 USART、ADC、SPI)准备好数据了,或者需要数据了,就向 DMA 控制器发一个请求信号。
  2. DMA 控制器收到请求,检查当前通道是否空闲、优先级是否够。
  3. 如果条件满足,DMA 控制器接管总线,执行一次数据传输。
  4. 传输完成后,DMA 控制器释放总线,等待下一个请求。

这里有个容易混淆的点:DMA 请求和 DMA 中断是两码事。请求是外设发给 DMA 控制器的,告诉它“有活干了”。中断是 DMA 控制器发给 CPU 的,告诉它“活干完了”。

避坑指南:我曾经在一个项目里,把 DMA 请求和 DMA 中断搞混了。配置 USART 的 DMA 接收时,忘了使能 USART 的 DMA 请求,结果数据死活不往内存里搬。查了两天才发现,是 USART 的 DMA 发送/接收使能位没置上。嗯,这种低级错误,犯过一次就记住了。

STM32 的 DMA 控制器有多个通道,每个通道可以响应多个外设的请求。但同一时间,一个通道只能服务一个外设。你需要通过配置寄存器,把外设的 DMA 请求映射到对应的通道上。

DMA 传输的核心流程

我把 DMA 一次完整传输的流程画成了 SVG 图,方便你理解:

DMA 一次完整传输流程 外设产生数据 外设发出 DMA 请求 DMA 控制器响应 DMA 接管总线 执行数据传输 传输完成 DMA 释放总线 发送传输完成中断 CPU 处理数据 说明: 整个过程中,CPU 只在最后一步处理数据时被唤醒。数据传输阶段 CPU 可以睡觉或执行其他任务。 这就是 DMA 降低系统功耗、提升 CPU 利用率的核心原理。

DMA 传输配置的关键参数

配置 DMA 时,有几个参数必须搞清楚。我列了个表:

参数 说明 常见取值
源地址 数据来源的地址 外设寄存器地址、内存数组地址
目的地址 数据要去的地址 内存数组地址、外设寄存器地址
传输长度 要传输的数据单元个数 1 ~ 65535
数据宽度 每次传输的数据大小 字节(8bit)、半字(16bit)、字(32bit)
地址增量 每次传输后地址是否自动增加 使能/禁止
传输模式 单次、循环、突发 根据场景选择
优先级 多个 DMA 通道同时请求时的仲裁 低/中/高/最高

经验之谈:配置地址增量时要注意。如果源地址是外设数据寄存器(固定地址),源地址增量要禁止。如果目的地址是内存数组(需要递增),目的地址增量要使能。反过来也一样。我见过有人把这两个搞反了,数据全写到了同一个地址上。

DMA 与 CPU 的协作关系

DMA 不是取代 CPU,而是帮 CPU 分担重复劳动。CPU 负责决策和控制,DMA 负责数据搬运。两者通过中断和标志位来同步。

典型的协作流程:

  1. CPU 配置 DMA 参数,启动传输。
  2. CPU 继续执行其他代码,或者进入低功耗模式。
  3. DMA 完成传输后,触发中断。
  4. CPU 响应中断,处理搬运完的数据。

这种模式在低功耗设计中特别有用。CPU 大部分时间在睡觉,DMA 默默干活。等数据准备好了,再叫醒 CPU 处理。功耗能降一个数量级。