2. STM32与DMA概述

在嵌入式系统中,CPU 是最宝贵的计算资源。如果让 CPU 亲自搬运每一个字节,从外设到内存、从内存到外设,那么大量时钟周期会被消耗在“搬数据”这种机械劳动上,真正需要算力的事情反而被耽误。DMA(Direct Memory Access,直接存储器访问)正是为解决这一矛盾而生的硬件机制。它允许数据在外设与存储器之间、存储器与存储器之间直接传输,而无需 CPU 逐字节参与。理解 DMA 的定义、作用、在 STM32 中的定位以及典型应用场景,是后续掌握高性能采集系统的第一步。

一、DMA 的定义与核心原理

DMA 是一种由硬件控制器独立完成数据搬运的技术。它的核心思想是:把“数据从哪里来、到哪里去、搬多少、怎么搬”这些信息配置给 DMA 控制器,之后由 DMA 控制器接管总线,自动完成传输。CPU 只需在传输开始前做初始化,在传输结束后处理结果或响应中断,中间过程几乎不占用 CPU 时间。

在 STM32 中,DMA 控制器挂载在 AHB 总线上,能够访问存储器(Flash、SRAM)和外设(如 ADC、USART、SPI、I2C、定时器等)。一次典型的 DMA 传输包含四个关键要素:

  • 源地址:数据从哪里读取,例如 ADC 数据寄存器或内存缓冲区。
  • 目的地址:数据写到哪里,例如 SRAM 数组或外设发送寄存器。
  • 传输数量:本次要搬运多少个数据单元。
  • 传输模式:单次、循环、外设到内存、内存到外设、内存到内存等。

当外设产生 DMA 请求(如 ADC 转换完成、USART 接收寄存器非空),DMA 控制器便向总线仲裁器申请总线使用权,获得授权后执行一次数据搬运,并自动递减传输计数器。计数器归零时,产生传输完成中断或事件,通知 CPU 进行后续处理。

二、DMA 在 STM32 中的定位

STM32 的 DMA 并不是外设的附属功能,而是独立于内核的总线主设备。以常见的 STM32F1/F4/H7 系列为例,DMA 控制器通常分为 DMA1、DMA2,每个控制器有多个通道(或流),每个通道可绑定特定的外设请求。这种“通道—请求—外设”的映射关系,决定了哪个外设可以使用哪条 DMA 通路。

从系统架构看,DMA 位于 AHB 总线矩阵之上,与 Cortex-M 内核、SRAM、Flash、外设共同构成数据通路。CPU 通过 AHB 从接口配置 DMA 寄存器,DMA 通过 AHB 主接口访问内存和外设。二者可以并行工作:CPU 在计算,DMA 在搬运,从而形成“计算与传输重叠”的高效模式。

关键定位:DMA 是 STM32 中独立的总线主设备,是连接外设与存储器的“数据搬运引擎”,其价值在于把 CPU 从低效的数据搬移中解放出来,让 CPU 专注于算法、协议和决策。

三、DMA 的作用与典型应用场景

DMA 的作用可以概括为三点:提高数据吞吐率、降低 CPU 占用率、提升系统实时性。在高速采集、通信、显示刷新等场景中,DMA 几乎是必选项。下面通过一个表格梳理 STM32 中常见的 DMA 应用场景及其数据流向。

应用场景 数据流向 典型外设 DMA 带来的收益
高速 ADC 采集 外设 → 内存 ADC1/ADC2 连续采样不丢点,CPU 可并行处理
串口大数据收发 外设 ↔ 内存 USART1/2/3 降低中断频率,支持不定长接收
SPI/I2C 批量读写 外设 ↔ 内存 SPI1、I2C1 提高总线利用率,减少等待
DAC 波形输出 内存 → 外设 DAC1/DAC2 定时器触发,输出稳定波形
内存到内存搬运 内存 → 内存 DMA 控制器 快速数据块复制、图像缓冲切换
LCD/摄像头数据流 内存 → 外设 FSMC、DCMI 高带宽刷新,避免撕裂

以高速 ADC 采集为例,若没有 DMA,CPU 需要在每次 ADC 转换完成后进入中断读取数据寄存器,采样率越高,中断越频繁,CPU 几乎无法做其他事情。使用 DMA 后,ADC 每次转换完成自动触发 DMA 请求,数据被直接写入内存数组,CPU 只需在采集完一批数据后处理即可。这就是“高性能采集”的典型模式。

四、案例:ADC + DMA 采集流程

下面以 STM32F4 的 ADC1 配合 DMA2 Stream0 为例,展示配置思路。代码使用 HAL 库风格,重点观察 DMA 的初始化与启动方式。

// 1. 配置 DMA:外设到内存,循环模式
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_adc1);

// 2. 关联 ADC 与 DMA
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

// 3. 启动 DMA 传输,缓冲区大小 256
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 256);

// 4. 采集完成后在回调中处理
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    // adc_buffer 中已填满 256 个采样点
    process_samples(adc_buffer, 256);
}

这段代码体现了 DMA 的典型工作方式:配置一次,循环搬运,CPU 只在半传输或全传输完成时介入。对于需要连续采集的场景,循环模式可以避免反复启动 DMA,进一步降低软件开销。

五、常见误区与复盘

误区一:认为 DMA 可以完全替代 CPU。DMA 只负责搬运,不负责计算、判断和协议解析。数据搬完之后,仍需 CPU 处理。

误区二:忽略数据对齐与地址递增。外设数据宽度与内存数据宽度不一致时,必须正确设置对齐方式,否则会出现数据错位。

误区三:多个外设共用同一 DMA 通道导致冲突。STM32 的 DMA 通道与请求映射固定,配置前必须查阅参考手册,避免请求冲突。

误区四:循环模式下忘记处理半传输中断。半传输中断可用于“双缓冲”思路,若忽略可能导致数据覆盖或处理延迟。

复盘来看,DMA 的使用并不复杂,难点在于理解“谁触发、搬什么、搬多少、搬完后通知谁”。只要把源、目的、数量、模式、中断这五个要素理清,DMA 就能稳定高效地工作。对于高性能采集系统而言,DMA 不是可选项,而是基础能力。

STM32 DMA 架构与数据流 CPU 配置 DMA,DMA 独立搬运,外设与存储器之间形成高速数据通路 Cortex-M 内核 配置 / 计算 / 决策 低占用,不搬字节 DMA 控制器 通道 / 流 · 请求映射 源地址 · 目的地址 传输数量 · 循环模式 存储器 SRAM / Flash 缓冲区 · 数据数组 外设 ADC · USART · SPI I2C · DAC · 定时器 产生 DMA 请求 AHB 总线矩阵 · 总线仲裁 配置寄存器 写内存 DMA 请求 写外设 典型数据流:ADC 转换完成 → DMA 请求 → 数据写入 SRAM → 传输完成中断 → CPU 处理

通过本节的学习,应当建立起一个基本判断:当系统中存在高速、批量、周期性的数据搬运需求时,优先考虑 DMA。它不是某个外设的“附加功能”,而是 STM32 体系结构中与 CPU 并列的数据通路。理解 DMA 的定义、作用、定位和场景,才能在后继的通道配置、中断管理、双缓冲设计中做出正确选择。