DMA 数据传输
判别规则(核心,唯一要记的)
- 该不该用 DMA:大量/连续数据 且 CPU 要同时干别的 → DMA;数据少/低频 → 中断/轮询(配置开销不划算)
- 普通 vs 循环:传完一次就结束(一次性任务/固定采样)→ 普通(NORMAL);需要"永不停"的流(音频/连续采样/不定长串口接收)→ 循环(CIRCULAR),CNDTR 到 0 自动重装
- 方向看"谁流向谁":内存→外设 = M2P(发送);外设→内存 = P2M(接收/采样);内存→内存 = M2M(搬运,免外设请求,配置完置 EN 立即启动)
一句话:"量大且 CPU 要干别的才用 DMA;传完就停用普通、永不停用循环;方向看数据从哪流向哪,M2M 免请求。"
抽象描述(一句话本质)
把 DMA 想成专职工人搬货:CPU 只"下单"(配置)不"搬货",DMA 把外设和内存之间的数据直接搬好,搬完发 1 次完成中断喊一声。搬数据有三种干法:轮询 = CPU 站在那儿干等;中断 = CPU 每搬一字节被打断一次;DMA = CPU 全程不用管。
三种搬运方式对比
| 方式 |
CPU 参与 |
100 字节代价 |
适用 |
| 轮询 |
死等 |
CPU 全程被绑住,啥也干不了 |
极简单/调试 |
| 中断 |
每字节打断一次 |
进 100 次中断,每次现场保存/恢复 |
数据少/低频/需即时处理 |
| DMA |
零参与 |
配置一次,搬完只进 1 次完成中断 |
大量/连续 + CPU 要干别的 |
传输方向三选一
| 方向 |
谁流向谁 |
典型场景 |
谁启动 |
| M2P |
内存 → 外设寄存器(如 USART1->DR) |
串口发一批数据 |
等外设请求(如开 `USART1->CR3 |
| P2M |
外设寄存器 → 内存 |
串口接收 / ADC 连续采样 |
等外设请求(RXNE) |
| M2M |
内存 → 内存 |
Flash→SRAM、拷数组 |
免请求,置 EN 立即搬完(MEM2MEM 位) |
普通 vs 循环(核心)
|
普通 NORMAL |
循环 CIRCULAR |
| CNDTR 到 0 |
停止 + 完成中断 |
自动重装回初始值,继续搬 |
| 适用 |
一次性任务(发一批字符串、固定 1000 次采样) |
持续流(音频、连续采样、不定长串口接收) |
| 生活类比 |
送完这趟就下班 |
送完一趟接下一单,永远不停 |
| 配错的后果 |
该循环配普通 → 传完一批就断流 |
该普通配循环 → 数据反复重发/覆盖 |
配置要点与坑
- 顺序:参数全配好(CPAR/CMAR/CNDTR/CCR)→ 最后才置 EN
- CNDTR:16 位,上限 65535;超过分段或循环
- 地址递增:外设寄存器地址固定 → PINC 关;内存缓冲区连续 → MINC 开
- 通道冲突:同一通道同一时刻只能服务一个外设
- 外设请求:M2P/P2M 必须开外设的 DMA 请求位,否则通道配了也不动
- Cache 一致性:Cortex-M7 需注意;F103 无 Cache 不用担心
- M2M 方向反直觉:MEM2MEM 模式下 CPAR 放目的、CMAR 放源
正反例(建立直觉)
- 正例1:串口一次发 1KB,CPU 同时跑业务 → 用 DMA(大量 + CPU 要干别的)
- 正例2:ADC 连续采样 1000 次存内存 → 用 DMA(P2M,量大持续)
- 反例1:每秒收 1~2 个命令字节 → 中断就够(数据太少,DMA 配置开销 > 收益)
- 反例2:每次搬完都要立刻处理该字节 → 用中断(DMA 攒一批才通知,实时响应反而慢)
易混对比
DMA vs 中断
|
DMA |
中断 |
| 搬数据 |
DMA 控制器搬,CPU 零参与 |
CPU 每字节进 ISR 搬 |
| 中断次数 |
搬完只 1 次 |
每字节 1 次 |
| 适用 |
量大/连续 |
量少/低频/需即时处理 |
外设请求 vs 启动(M2P/P2M vs M2M)
|
M2P/P2M |
M2M |
| 要不要外设请求 |
要(TXE/RXNE 决定每步何时搬) |
不要,置 EN 立即搬完 |
| 生活类比 |
货现产现送,货到了才搬 |
货全在仓库,直接搬 |
变体验证(3 题,全过=学会)
- 串口每秒只收 1~2 字节命令,主循环跑 UI → 用中断(数据少,DMA 配置开销远大于收益)
- 陷阱题:ADC 连续采样固定 1000 次采完一次性处理 → 普通模式(固定次数、采完就停;循环模式用于永不停流,会不断覆盖缓冲)
- 陷阱题:"内存→内存 DMA 和内存→外设一样,都要等外设请求才启动" → 错,M2M 免请求立即启动,只有 M2P/P2M 等外设请求
口述要点(面试怎么讲)
- 结论先行:量大且 CPU 要干别的 → DMA;传完就停用普通、永不停用循环;方向看谁流向谁
- DMA 省在哪:中断搬 100 字节 = 100 次现场保存/恢复、CPU 每字节被打断;DMA = 配置一次零参与,只进 1 次完成中断
- 为什么不能全用 DMA:启动有配置开销,数据少/低频收益低;"攒一批才通知"在需即时处理场景反而实时性差
- 为什么方向/模式要在启动前想清楚:方向决定"要不要外设请求(TXE/RXNE)、谁的地址递增(外设固定/内存递增)";普通/循环决定"CNDTR 到 0 停不停(自动重装)"。三者构成传输闭环。类比:方向 = 取货送货路线,普通/循环 = 送完下班还是等下一单,外设请求 = 货现产现送,M2M = 货全在仓库不用等
- 易错点:忘开外设 DMA 请求;方向配错;该循环配普通断流;忘配 NVIC(完成中断不来);CNDTR 超 65535
关系网络
| 相邻概念 |
关系 |
孤立理解会犯的错 |
| 中断 |
DMA 每字节不打断 CPU,搬完才发 1 次完成中断 |
以为 DMA 不产生中断 |
| NVIC |
DMA 完成中断同样要配优先级/使能 |
只配 DMA 不配 NVIC,中断永远不来 |
| 串口/ADC |
典型应用 HAL_UART_Transmit_DMA / Receive_DMA;开 CR3.DMAT |
忘开外设 DMA 请求,通道配了也不动 |
| 内存(Cache) |
M7 需 Cache 一致性;F103 无 Cache |
移植 M7 忽略 Cache,搬完读到旧值 |
| 中断优先级(主题 32) |
DMA 完成中断走 NVIC 优先级体系 |
优先级方向与 FreeRTOS 混淆 |
学习日期
2026-08-18
每主题一页,复习时只翻本目录。