1. DMA 基础概念
1.1 DMA 的定义
DMA(Direct Memory Access,直接存储器访问) 是一种允许外设与存储器之间直接传输数据的硬件机制,无需 CPU 逐字节参与搬运。DMA 控制器作为独立的总线主机,在系统总线上与 CPU 并行工作,专门负责数据块的搬移、填充或外设轮询。
核心要点:DMA 的本质是“硬件数据通道 + 专用状态机”,CPU 只需配置传输参数(源地址、目的地址、长度、触发源),然后启动 DMA 通道,后续传输由 DMA 控制器独立完成。
1.2 DMA 在 MCU 中的作用
在嵌入式 MCU 系统中,DMA 主要解决以下三类问题:
- 降低 CPU 负载:将大量重复的数据搬运任务从 CPU 卸载到 DMA,CPU 可同时执行算法、协议栈或控制逻辑。
- 提高数据传输吞吐率:DMA 以硬件时钟周期级速度搬运数据,通常比 CPU 逐字节搬运快 3~10 倍(取决于总线宽度和 DMA 突发传输能力)。
- 支持外设实时数据流:ADC 采样、USART 接收、SPI 收发、定时器捕获等外设可连续产生数据,DMA 自动将数据搬入缓冲区,避免 CPU 频繁响应中断导致丢数。
| 应用场景 | DMA 作用 | 典型外设 |
|---|---|---|
| 高速 ADC 连续采样 | 将 ADC 结果自动搬入 RAM 数组,CPU 定期处理 | ADC、DAC |
| 串口数据收发 | 自动从发送缓冲区搬数据到 USART 数据寄存器 | USART、UART |
| SPI 外设通信 | 双缓冲环形队列,DMA 自动填充发送/接收 FIFO | SPI、I2S |
| 存储器到存储器拷贝 | 快速复制大块数据(如帧缓冲区、音频样本) | 内部 SRAM、Flash |
| 定时器触发传输 | 按精确时间间隔触发 DMA 传输(如 PWM 波形更新) | 定时器、PWM |
1.3 DMA 与 CPU 的数据搬运对比
理解 DMA 与 CPU 在数据搬运上的本质差异,是正确设计驱动架构的前提。下表从多个维度进行对比:
| 对比维度 | CPU 搬运(轮询/中断) | DMA 搬运 |
|---|---|---|
| 执行主体 | CPU 核心(取指、译码、执行) | DMA 控制器(专用状态机) |
| 总线占用 | CPU 占用系统总线,其他主机等待 | DMA 作为总线主机,与 CPU 分时共享总线 |
| 传输粒度 | 逐字节/逐字(软件循环) | 数据块(硬件突发传输) |
| CPU 利用率 | 高(搬运期间 CPU 100% 被占用) | 低(仅配置和完成中断时占用 CPU) |
| 实时性 | 受中断响应延迟影响 | 硬件触发,延迟固定(通常 2~4 个时钟周期) |
| 适用场景 | 小数据量、非周期性、简单控制 | 大数据量、周期性、高速外设流 |
| 代码复杂度 | 简单直接(循环读写寄存器) | 需配置 DMA 描述符/链表,初始化较复杂 |
经验法则:当单次传输数据量超过 16 字节,或传输频率超过 1 kHz 时,优先考虑使用 DMA。对于小于 8 字节的零星传输,CPU 直接搬运反而更高效(因为 DMA 配置开销可能超过搬运本身)。
1.4 DMA 与 CPU 协同工作模型
下图展示了典型 MCU 系统中 DMA 与 CPU 的协同架构:CPU 配置 DMA 通道后,外设数据直接通过 DMA 搬入内存,CPU 仅在传输完成时收到中断通知。
1.5 典型数据传输流程对比
以 USART 接收 256 字节数据为例,对比 CPU 轮询模式与 DMA 模式的执行流程:
CPU 轮询模式(无 DMA):
// 伪代码:CPU 逐字节轮询接收
for (i = 0; i < 256; i++) {
while (!(USART->SR & RXNE)); // 等待接收标志
buffer[i] = USART->DR; // CPU 逐字节搬运
// CPU 在此期间无法执行其他任务
}
DMA 模式(协同工作):
// 伪代码:CPU 配置 DMA 后即可处理其他任务
DMA_Config(&dma_ch1, USART->DR, buffer, 256, DMA_PERIPH_TO_MEM);
DMA_Start(&dma_ch1); // 启动 DMA,CPU 立即返回
// CPU 可执行协议解析、按键扫描等任务
while (!DMA_IsComplete(&dma_ch1)); // 仅在最后检查完成标志
// 或使用 DMA 完成中断通知 CPU
1.6 DMA 传输的关键参数
配置 DMA 传输时,需要明确以下核心参数:
- 源地址:数据来源(外设数据寄存器地址 或 内存地址)
- 目的地址:数据去向(内存地址 或 外设数据寄存器地址)
- 传输宽度:字节(8-bit)、半字(16-bit)、字(32-bit)
- 传输数量:一次 DMA 传输的数据单元个数(1~65535)
- 地址增量模式:源/目的地址是否自动递增(用于连续内存块)
- 触发源:外设请求(如 USART RXNE)、软件触发、定时器触发
- 循环模式:传输完成后是否自动重载,实现环形缓冲区
常见陷阱:配置 DMA 时务必确认源地址和目的地址的访问权限。某些 MCU 的 DMA 无法直接访问 Flash 或特定外设寄存器区域,需查阅参考手册的“DMA 映射表”。此外,DMA 传输宽度必须与外设寄存器宽度匹配,否则会导致数据错位。
1.7 何时选择 DMA?——决策树
以下决策条件可帮助判断是否启用 DMA:
- 数据量 > 16 字节? → 是则考虑 DMA;否则 CPU 直接搬运更简单。
- 传输频率 > 1 kHz? → 是则必须用 DMA,否则中断频繁导致 CPU 饥饿。
- 外设需要连续数据流? → 是则使用 DMA 循环模式,避免丢数。
- CPU 需要同时处理其他实时任务? → 是则 DMA 卸载搬运任务,提升系统实时性。
- 传输延迟要求固定? → 是则 DMA 硬件延迟优于中断响应延迟。
通过以上对比和决策条件,可以清晰理解 DMA 在 MCU 系统中的定位:它不是替代 CPU,而是与 CPU 分工协作,让 CPU 专注于控制逻辑和算法,让 DMA 专注于数据搬运,从而最大化系统整体性能。