二十四、STM32的DMA
前言:现代 MCU 经常需要处理大量外设数据(ADC、UART、SPI、I²C 等),如果把这些数据搬运完全交给 CPU,会占用大量 CPU 周期,降低系统响应能力。**DMA(Direct Memory Access,直接内存访问)**就是解决这个问题的利器:它可以在不占用 CPU 的情况下,把外设数据自动搬运到内存(或把内存数据搬到外设),显著降低 CPU 负担、提升实时性与吞吐量。
目录
一、DMA 是什么?为什么要用?
二、DMA 的总体架构
三、DMA 的典型传输方向与使用场景
四、DMA 的关键配置项与概念
五、DMA 的工作时序与中断
六、常见误区与调试要点
七、性能注意事项与优化建议
八、ADC+DMA
一、DMA 是什么?为什么要用?
DMA 的本质:一个硬件控制器,负责在外设寄存器、内存和外设之间直接搬运数据,搬运过程中 CPU 不参与数据逐字节拷贝(但仍需做配置和处理完成后的逻辑)。
主要优点:
-
降低 CPU 占用:尤其适合大量或高速数据采集(如音频、传感器阵列、图像流等)。
-
更稳定的实时性:减少中断频率或避免中断上下文开销。
-
更高吞吐量:硬件搬运比软件循环拷贝更快、更节省总线带宽。
适用场景举例:ADC 多通道高速采样、UART 大量数据接收、SPI 快速数据传输、内存到内存的批量拷贝、PWM 波形输出缓冲等。
二、DMA 的总体架构
-
STM32F1 系列通常有 DMA1(个别器件还有 DMA2)。
-
DMA 被划分为 通道(Channel),每个通道映射到一个或多个外设事件(例如 ADC、USART、SPI 等)。通道数量有限(例如 DMA1 通常有 7 个通道)。
-
每个通道有自己的配置寄存器,控制源/目的地址、方向、数据宽度、优先级、传输长度、模式(循环/普通)等。
-
DMA 与外设通过**请求映射(request mapping)**关联:某个外设触发数据准备(如 ADC EOC),会产生 DMA 请求到对应通道。

三、DMA 的典型传输方向与使用场景
-
外围设备 → 内存(Peripheral-to-Memory)
场景:ADC 将转换结果推送到内存、USART RX 接收数据写内存。是最常见的用于数据采集的方向。 -
内存 → 外围设备(Memory-to-Peripheral)
场景:从一个内存缓冲区把数据写到 DAC、UART TX、SPI TX(比如发送音频或图像数据)。 -
内存 → 内存(Memory-to-Memory)
场景:大块内存数据搬运(在 F1 上支持有限,注意映射与配置)。用于缓冲区复制、DMA 驱动的块传输等。 -
双向 / 循环模式(实际是模式选择)
循环(Circular)适合连续流数据采集;普通(Normal)适合一次性传输。
四、DMA 的关键配置项与概念
-
源地址(Peripheral address)与目的地址(Memory address)
DMA 在搬运时会从源地址读取数据并写到目的地址;比如 ADC->内存: 源=ADC_data_register,目的=缓冲区首地址。 -
传输方向(Direction):见上一节。
-
传输长度(Number of Data):要搬运的数据项个数(不是字节数,依赖数据宽度)。
-
数据宽度(Peripheral & Memory Data Size):常见为 8-bit、16-bit、32-bit。ADC(12-bit)一般使用 16-bit 宽度(half-word),UART 通常用 8-bit。数据宽度必须匹配硬件与缓冲格式。
-
地址递增(Peripheral increment / Memory increment):是否在每次传输后自动增加地址。外设寄存器通常禁用地址递增(固定地址),内存缓冲通常启用递增。
-
传输模式(Normal / Circular):
-
Normal:完成指定数量传输后停止并触发完成中断。
-
Circular:完成后自动重新开始(围绕缓冲区),适合连续采样。
-
-
优先级(Priority):在多通道竞争 DMA 总线时决定抢占顺序(高优先级通道更早获得总线)。
-
中断/标志:半传中断(Half Transfer)、传输完成中断(Transfer Complete)、传输错误中断。半传在循环模式下非常有用——可以实现“前半缓冲处理同时后半缓冲接收”的方案。
五、DMA 的工作时序与中断
-
启动步骤(概念层面):
-
配置 DMA(源/目地址、长度、宽度、递增、模式、优先级)。
-
配置外设(使能外设 DMA 请求,比如 ADC_DMACmd)。
-
使能 DMA 通道与相关中断(如果需要)。
-
启动外设触发(ADC 软件触发或定时器触发),数据开始流入缓冲区。
-
-
半传中断(HT)与传输完成中断(TC):
在循环模式下,半传中断可以触发处理已填满的缓冲区一半数据,而 DMA 继续写入另一半;传输完成中断则表示一轮缓冲写满。利用这两个事件可以实现无缝“生产者-消费者”模式。 -
错误处理:DMA 也可能产生传输错误(比如总线错误),要有相应的检测与恢复策略(重启 DMA、报警等)。
六、常见误区与调试要点
-
误区:DMA 一开就万事大吉
DMA 确实减轻 CPU,但错误配置(地址错误、长度错误、宽度不匹配)会导致数据错位、溢出或硬件挂起。调试时重点检查外设数据寄存器地址、数据宽度和内存缓冲区大小。 -
误区:半传中断和全传中断二者可互相替代
半传中断用于“流式处理”更低延迟;若只用全传中断,会在缓冲写满后才处理,可能加大延迟。 -
调试技巧:
-
先用短缓冲进行功能验证(例如 4 个采样点);
-
打开半传/全传中断并在 ISR 中简单翻转 IO(示波器观察)确认触发节奏;
-
检查 DMA 优先级与竞争关系;
-
确认内存对齐(16-bit/32-bit)与 CPU 访问不会引起未对齐故障。
-
-
跨域缓存问题(高级):F1 的 Cortex-M3 没有数据缓存问题,然而在更高端系列(如 Cortex-M7)需要考虑 D-cache 与 DMA 的一致性:使用缓存刷新/失效或使用不可缓存内存区。

七、性能注意事项与优化建议
-
选择合适的数据宽度:尽量让外设与内存宽度匹配,减少额外的对齐/移动操作(例如 ADC 用 16-bit 存储)。
-
优先级与通道分配:把关键通道设高优先级;避免多个高频外设共享同一 DMA 通道。
-
中断开销最小化:把处理尽量放到任务或主循环,不在 ISR 中做大量计算;使用半传中断只做标记并在主循环处理。
-
使用循环模式处理持续数据流:比如 ADC 连续采样或串口持续接收,循环模式可以避免频繁重启 DMA。
-
考虑缓冲大小权衡:小缓冲降低延迟但增加中断频率;大缓冲降低中断负担但增加响应延迟。
八、ADC+DMA
-
当需要实时采集多通道数据(如传感器阵列、音频采样、振动分析等)时,CPU 去轮询或处理中断会消耗大量计算资源,甚至跟不上数据速率。
-
ADC + DMA 可以实现:ADC 将转换结果自动写入内存缓冲区,CPU 仅在缓冲半满或满时处理数据,极大提高效率与实时性。
配置流程:
-
设计缓冲区:为采样结果准备好连续的内存数组(例如
uint16_t buffer[NUM_SAMPLES];)。通常选择 16-bit 单元来保持 ADC 的 12-bit 数据。 -
配置 ADC:
-
打开 ADC 时钟,设置采样时间、分辨率(12-bit)、触发模式(软件或定时器触发)、扫描模式(若为多通道自动扫描)。
-
对于多通道自动采样,启用扫描模式并在序列寄存器中设置通道顺序。
-
-
配置 DMA(外设 → 内存):
-
源地址:ADC 数据寄存器地址(固定);目的地址:缓冲区首地址;
-
数据宽度:一般选择 16-bit(half-word);
-
内存地址递增:启用;外设地址递增:禁用;
-
模式:如果是持续采样(例如定时器触发连续采样),选择 Circular(循环) 模式;若是一次性采集选择 Normal。
-
启用半传/全传中断(根据需要)。
-
-
把 ADC 的 DMA 请求打开(告诉 ADC 在 EOC 时发出 DMA 请求,让 DMA 自动搬运数据)。
-
启动 ADC(与触发器)与 DMA 通道:在正确的时序下启动 DMA,然后启动 ADC & 触发(如果用定时器触发,启动定时器)。
-
在中断/任务中处理数据:
-
如果使用循环模式并启用了半传/全传中断:在半传中断中处理缓冲区前半,在全传中断中处理后半;处理完后继续让 DMA 循环写入。
-
如果使用普通模式:在传输完成中断中处理整个缓冲区并可重新配置/重启 DMA。
-
下一章节将详细讲解代码部分。

