STM32 DMA — 直接存储区访问(存储器到存储器)
开发板:野火 EBF STM32F103 Mini
固件库:HAL 库
对应例程:15-DMA—直接存储区访问 / DMA—存储器到存储器模式(并补充"存储器到外设"思路)
参考源码:User/main.c(存储器到存储器)
DMA(Direct Memory Access,直接存储器访问)允许外设与内存、内存与内存之间直接搬运数据,全程无需 CPU 参与。CPU 只需"下发搬运指令",搬运由 DMA 控制器在后台完成,大幅解放内核。本文从最直观的"内存→内存"搬运入手,再延伸到"内存→外设"。
一、功能简介
例程准备两个数组:
- 源数组
aSRC_Const_Buffer:32 个 32 位常量(已初始化); - 目标数组
aDST_Buffer:32 个 32 位空位。
用 DMA 把源数组整块搬到目标数组,搬运完成后比较两数组是否一致:一致则点亮 LED2(成功),否则点亮 LED1(失败)。
核心知识点:
- DMA 的方向、地址递增、数据宽度、传输模式、优先级;
- STM32F1 的 DMA 通道与触发源(存储器到存储器必须用 DMA1);
- 用标志位轮询传输完成。
二、硬件准备
| 项目 | 说明 |
|---|---|
| 开发板 | 野火 EBF STM32F103 Mini |
| DMA 控制器 | DMA1(F103 仅 1 个 DMA 控制器,7 个通道) |
| 本例通道 | DMA1_Channel6(软件触发,M2M 模式专用) |
| 指示 | LED1 → PC2(失败)、LED2 → PC3(成功) |
存储器到存储器(M2M)模式是 DMA 的特例:它不依赖外设硬件请求,而是软件触发一次就自动连续搬完,非常适合做 memcpy 加速。
三、完整代码(带中文注释)
1. main.c(M2M 搬运 + 校验)
|
|
四、代码分段讲解
1. DMA 到底省了什么?
普通 for 循环拷贝数组,每个字节都要 CPU 取指、读源、写目标——CPU 全程被占。DMA 接管总线后,CPU 在 HAL_DMA_Start 之后就可以去干别的事(本例只是 while 等标志,实际项目里可以并行运算)。对大块数据 / 高速外设(ADC、SPI、USART、LCD)而言,DMA 是性能关键。
2. 四个关键配置
| 字段 | 本例 | 含义 |
|---|---|---|
Direction |
DMA_MEMORY_TO_MEMORY |
存储器→存储器(软件触发) |
PeriphInc / MemInc |
ENABLE |
源、目标地址都递增(否则会反复写同一格) |
PeriphDataAlignment / MemDataAlignment |
WORD(32位) |
数据宽度要一致,避免错位 |
Mode |
DMA_NORMAL |
普通模式:搬完一次就停(对比循环 DMA_CIRCULAR) |
注意:这里"Periph"一方是源数组,在 M2M 模式下 HAL 把源/目标统称为外设/内存,只需保证两者都递增、宽度相同即可。
3. 通道与触发源
F103 的 DMA1 有 7 个通道,每个通道固定对应某些外设请求(如 ADC1→CH1、SPI1→CH3、USART1_TX→CH4)。只有 DMA1 支持 M2M 软件触发,所以本例用 DMA1_Channel6。换成"内存→USART"时,就要选 USART 对应的专属通道,并由外设硬件触发。
4. 传输完成怎么知道?
__HAL_DMA_GET_FLAG(&DMA_Handle, DMA_FLAG_TC6) 读取传输完成标志(TCIF)。本例用轮询等待;实际中更常用传输完成中断(DMA_IT_TC),在 HAL_DMA_XferCpltCallback 里接着处理,彻底不阻塞 CPU。
5. 补充:存储器到外设(如 USART 发大报)
野火例程还有"DMA—存储器到外设模式":把 Direction 设为 DMA_MEMORY_TO_PERIPH,把外设地址填成 &USART1->DR,源填发送缓冲区,再 HAL_UART_Transmit_DMA()。这样 CPU 一次性把几百字节交给 DMA,串口慢慢发、CPU 同时翻转 LED——直观展示了"DMA 让 CPU 腾出手"。
五、实验现象
- 下载程序,DMA 把 32 个常量从源数组搬进目标数组;
- 比较两数组完全一致 → LED2 点亮(成功灯);
- 若把源/目标数据宽度设错(比如一方 WORD 一方 BYTE),拷贝错位导致比较失败 → LED1 点亮。
六、常见问题
Q1:传输后数据错位/不一致?
- 检查源和目标数据宽度是否一致(都 WORD 或都 HALFWORD);
- 检查
PeriphInc/MemInc是否都ENABLE(单地址重复写会出错)。
Q2:DMA 一直不完成(标志位不来)?
- M2M 模式确认用的是 DMA1;
- 确认
HAL_DMA_Start返回的DMA_status == HAL_OK,且传输长度不为 0。
Q3:为什么我的外设 DMA 没动?
- 外设 DMA 需要对应的专属通道和外设硬件触发,不能随便选通道;
- 外设自身要先使能(如 USART 要先
HAL_UART_Init)。
Q4:NORMAL 和 CIRCULAR 怎么选?
- 单次传输(如发一帧数据)用
NORMAL; - 连续流(如 ADC 持续采集、DAC 持续输出波形)用
CIRCULAR,搬完自动从头再来。
七、小结
- DMA 是"数据搬运工",让 CPU 从繁琐的拷数据中解放;
- M2M 配置要点:方向 M2M、双端递增、宽度一致、软件触发;
- 掌握 DMA 后,ADC、SPI、USART、DAC 等高速外设才能真正发挥性能。
八、拓展应用
- 大数组 memcpy 加速:把
for循环拷贝换成 DMA M2M,批量搬运 SRAM/Flash 数据更快。 - ADC + DMA 连续采样:如本博客 ADC 章节,DMA 把转换结果循环搬进数组,零 CPU 占用。
- USART/SPI 大块发送:用
HAL_UART_Transmit_DMA/HAL_SPI_Transmit_DMA,发送几百 KB 时 CPU 仍可并行处理。 - 双缓冲(Double Buffer):配合 DMA 半传输中断,实现"一边 DMA 填半区、CPU 处理另半区"的无缝流水线,适合音频、图像流。