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