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 仅在传输完成时收到中断通知。

DMA 与 CPU 协同工作模型 CPU 核心 配置 DMA 通道 DMA 控制器 独立搬运数据 内存(RAM) 数据缓冲区 外设(USART/ADC/SPI) 数据源/目的地 配置参数 数据写入 DMA 请求 完成中断 协同流程:CPU 配置 → DMA 独立搬运 → 传输完成中断通知 CPU CPU 在 DMA 搬运期间可执行其他任务,实现真正的并行处理

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:

  1. 数据量 > 16 字节? → 是则考虑 DMA;否则 CPU 直接搬运更简单。
  2. 传输频率 > 1 kHz? → 是则必须用 DMA,否则中断频繁导致 CPU 饥饿。
  3. 外设需要连续数据流? → 是则使用 DMA 循环模式,避免丢数。
  4. CPU 需要同时处理其他实时任务? → 是则 DMA 卸载搬运任务,提升系统实时性。
  5. 传输延迟要求固定? → 是则 DMA 硬件延迟优于中断响应延迟。

通过以上对比和决策条件,可以清晰理解 DMA 在 MCU 系统中的定位:它不是替代 CPU,而是与 CPU 分工协作,让 CPU 专注于控制逻辑和算法,让 DMA 专注于数据搬运,从而最大化系统整体性能。