当前位置:首页 > 学习资源 > 讲师博文 > DMA为什么能降低CPU占用率?STM32DMA工作原理详解

DMA为什么能降低CPU占用率?STM32DMA工作原理详解 时间:2026-09-24      来源:华清远见

导读:『用 DMA 可以降低 CPU 占用率』几乎成了口头禅,但 DMA 又不是协处理器,它凭什么让 CPU 变闲?本文先做一笔『周期账』量化 CPU 搬运的真实成本,再从总线主设备、总线矩阵仲裁的角度讲清 DMA 的硬件原理,然后给出 STM32 上串口+DMA 收不定长包、ADC+DMA 连续采样两个完整实操案例,最后总结 8 个最常见的 DMA 翻车现场。

一、先算账:CPU 搬一个字节到底贵在哪

看一段最朴素的串口收字节代码(中断方式):

▍CPU 中断搬运:每字节一次中断

每收 1 个字节,CPU 要经历:硬件入栈 8 字(约 12 周期)→ 编译器生成的 push {r4-r11, lr} → 读 DR(外设总线一次访存)→ 写内存 → 清标志 → 出栈返回(又 12 周期)。粗算每次 ISR 200 个周期并不夸张。把它乘以速率:

图 1  72MHz 内核上,不同串口速率下『CPU 逐字节中断』与『DMA 循环搬运』的占用率对比(估算)

115200bps 时 CPU 约 3% 被吃掉,似乎不多?但注意三个隐藏成本:

打断成本:每次中断都会冲刷流水线、污染 I-Cache/D-Cache,主循环里正在跑的FFT、滤波算法被反复『腰斩』,实测主任务性能下降远超 3%;

峰值成本:中断是突发的,一帧数据背靠背到达时,主任务可能连续几十微秒得不到执行,实时性(最坏情况延迟)才是嵌入式的第一指标;

扩展成本:速率上到 921600、3Mbps(蓝牙、数传电台常见),或者 ADC 1MSPS 连续采样,CPU 方式直接 30%~100% 占满,系统崩盘。

而 DMA 方式下 CPU 只在『配置』和『半传输/传输完成』两个时间点介入,搬运本身由 DMA 控制器在总线上独立完成。这就是『降低 CPU 占用率』的数学本质:把每字节 O(1) 的 CPU 开销,摊薄成每帧 O(1)。

二、DMA 的硬件本质:第二个『总线主设备』

图 2  两种搬运方式对比:CPU 方式每字节执行上百条指令;DMA 方式搬运期间 CPU 全速跑主程序

要理解 DMA,先破除一个直觉误区:内存和外设之间传数据『必须经过 CPU』是错的。在 STM32 内部,CPU 内核和 DMA 控制器都是总线矩阵(Bus Matrix)上的主设备(Master),SRAM、Flash、外设寄存器都是从设备(Slave)。CPU 之所以能搬数据,只是因为它恰好是一个『会执行 LDR/STR 指令的主设备』;而 DMA 是另一个『只会做固定搬运套路的主设备』,它有自己的地址发生器、计数器与请求仲裁器。

图 3  STM32 数据通路与循环模式缓冲区模型:DMA 写满半区触发 HT 中断,写满整区触发 TC 中断

于是『DMA 为什么快』就有了硬件答案:

零指令开销:一次搬运在 DMA 内部是『请求→仲裁→读→写→计数』的固定状态机,每字节约 2 个总线周期,而不是 CPU 的上百条指令;

并行性:总线矩阵有多层,DMA 搬外设→SRAM 的同时,CPU 可以从另一层访问Flash 取指、访问另一块 SRAM,两者真正并行;

冲突只付小代价:当 CPU 与 DMA 抢同一条总线/同一块内存时,仲裁器让一方等1~2 个周期——CPU 被『挂起』而不是被『打断』,没有上下文切换、没有 Cache 污染。

【一句话原理】DMA 降低 CPU 占用率,不是因为它『搬得魔法』,而是因为它把『每字节一次的软件循环』变成了『每字节一次的硬件总线事务』,并把 CPU 的介入频率从 N 次降到 2 次(HT/TC 中断)。

三、STM32 的 DMA 长什么样:通道、寄存器与工作模式

3.1 通道与请求映射

F1 系列有 DMA1(7 通道)+ DMA2(5 通道),外设到通道的映射是硬件固定的,查错表是日常(见表 1)。F4 系列升级为 8 条 Stream × 8 个 Channel 的交叉开关,每条 Stream 可用 Channel 选择寄存器连接多个外设之一,灵活但要查『Stream/Channel对照表』。

表 1  常用外设的 DMA 请求映射(以参考手册 DMA 章节表格为准)

3.2 一条通道运行时在做什么

CPAR/CMAR:外设地址、内存地址(源或目的由方向位 DIR 决定);

CNDTR:剩余传输计数,每完成一次搬运硬件减 1,减到 0 触发传输完成(TC);

CCR:方向、循环模式(CIRC)、外设/内存位宽(PSIZE/MSIZE)、地址自增(PINC/MINC)、内存到内存(M2M)、通道优先级、三个中断使能(TCIE/HTIE/TEIE);

运行时序:外设置位请求(如 RXNE)→ DMA 向总线矩阵申请总线 → 读外设 DR → 写内存 → CNDTR-- → 到半值触发 HT 中断 → 到 0 触发 TC 中断(循环模式则重载CNDTR 继续)。

位宽不一致时硬件会自动做打包/解包(如 PSIZE=16bit、MSIZE=8bit,一次外设读产生两次内存写),这既是便利也是 Bug 7 的温床。F4 还有 4 字 FIFO 与双缓冲(M0AR/M1AR + CT 位切换),适合高速连续流。

四、实操案例一:USART1 + 循环 DMA + IDLE 中断收不定长包

这是工程里最经典的 DMA 用法:DMA 负责『不停地把字节填入环形缓冲区』,IDLE 中断负责『一帧结束时通知我』,两者配合实现零丢包、零轮询的串口接收。

▍USART1 + DMA 循环接收 + IDLE 判帧尾

4.1 循环模式下的『回卷』问题

循环模式下 DMA 写到缓冲区尾部会自动回到 0 继续写。若一帧数据恰好跨过尾部,rx_buf[0..pos-1] 里其实是『上一帧尾巴 + 本帧开头』的拼接。工程上有两种稳妥做法:

半区法(推荐):只消费『刚被写满的半区』——HT 中断消费前半、TC 中断消费后半,把缓冲区当双缓冲用,彻底避开跨边界的帧(适合定长/流式数据,如 ADC、I2S);

软件环形法:记录 last_pos,IDLE 里若 pos<last_pos 则分两段 memcpy 拼帧(适合不定长协议包)。

无论哪种,都要保证『消费速度 ≥ 生产速度』,否则 DMA 会追上来覆盖未读数据(overrun)。缓冲区大小的经验值:≥ 2 × 最大帧长 × 安全系数。

五、实操案例二:ADC1 多通道连续扫描 + DMA

▍ADC + DMA 循环采样骨架

要点:ADC 转换完成自动触发 DMA 请求,无需 CPU;多通道扫描时 DMA 按转换顺序依次填入数组,天然对齐。若用 F7/H7(带 D-Cache),请务必看 Bug 6。

六、常用 Bug 与排查

Bug 1:DMA 完全不动——忘了开 DMA 时钟

现象:HAL_UART_Receive_DMA 返回 HAL_OK,但 CNDTR 永远不变。DMA 挂在 AHB 上,必须 __HAL_RCC_DMA1_CLK_ENABLE();UART 本体在 APB、GPIO 在 APB2,三路时钟一个都不能少。自检:读 DMA1_Channel5->CNDTR 是否在递减。

Bug 2:通道/Stream 映射张冠李戴

现象:配置看着都对,就是没数据。F1 的映射是硬件固定的(USART1_RX 只能是 DMA1_Channel5),用错通道 DMA 根本收不到请求;F4 用错 Stream 或 Channel 选择位同理。解决:永远以参考手册『DMA request mapping』表为准,CubeMX 生成的代码可以信,手写必须查表。

Bug 3:HT/TC 中断各触发了一次『多余』的进入

现象:回调函数莫名其妙多进一次,或进一次后再也不进。DMA 的 HT/TC/TE 标志是『置位后需软件清除』的;HAL 的 IRQHandler 会帮你清,但裸机写法若只清了 TC 没清 HT,HT 中断会一直挂起。裸机模板:进 ISR 先读 ISR 寄存器判断是哪个标志,处理完写 IFCR 对应位清掉。

Bug 4:循环缓冲被覆盖(overrun 丢数)

现象:偶发数据错乱,重现象消失。根因是消费太慢:DMA 写完半区后,你在 HT 回调里做了耗时操作,等回来时 DMA 已经开始写这半区了。对策:回调里只做 memcpy 到第二份缓冲或置标志;用半区法时确保单半区容量 × 2 的余量;高速流(I2S/ADC 1MSPS)直接上F4 的双缓冲模式,硬件切换 M0AR/M1AR。

Bug 5:位宽不匹配导致数据『错位』

现象:数组里隔一个错一个。外设 16 位(如 ADC)配了 MSIZE=BYTE,DMA 会做解包,每 16 位拆成 2 字节,你的 uint16_t 数组立刻错位。规则:PSIZE 跟随外设 DR 宽度,MSIZE 跟随内存数组元素宽度,两者一致最省心。

Bug 6:F7/H7 上 DMA 数据『看不见』——D-Cache 一致性

现象:M7 内核开了 D-Cache 后,CPU 读到的始终是旧数据(或外设收到旧数据)。根因:DMA 写内存不经过 CPU Cache,CPU 从 Cache 读到的是过期副本。三选一解决:

MPU 把 DMA 缓冲区配置为 Non-cacheable(最稳,推荐);

接收前 SCB_InvalidateDCache_by_Addr(),发送前 SCB_CleanDCache_by_Addr();

缓冲区 32 字节对齐且不与他人共享同一 Cache 行,避免 invalidate 误伤。

Bug 7:运行中改配置 / 重启动 DMA 后死机

DMA 通道的 EN 位必须先清零才能改配置,且硬件需要若干周期真正关掉——写 0 后要轮询确认 EN==0 再写其它寄存器;传输错误标志 TE 未清就重开也会导致立即报错。HAL 用户请走 HAL_DMA_Abort() 而不是直接戳寄存器。

Bug 8:缓冲区放在栈上 / 未对齐

函数内局部数组传进 HAL_UART_Receive_DMA,函数返回后栈帧作废,DMA 仍在往那里写——轻则数据乱、重则 HardFault(写到关键栈区)。同理 __attribute__((aligned(4))) 可避免非对齐访问在某些配置下产生的 BusFault。

七、总结

DMA 是与 CPU 并列的总线主设备,搬运走硬件状态机,每字节约 2 个总线周期;

『降低 CPU 占用率』的本质:介入次数从 N 次摊薄到 2 次,且不打断主任务流水线;

循环模式 + 半区/双缓冲是流式数据的标准姿势;IDLE 中断补上『帧尾』语义;

时钟、映射、清标志、位宽、Cache、对齐——DMA 的坑集中在『配置期』,运行期几乎零维护,这正是硬件方案的魅力。

上一篇:支持向量机(SVM)

下一篇:STM32上拉输入与下拉输入:如何做出正确选择?

戳我查看嵌入式每月就业风云榜

点我了解华清远见高校学霸学习秘籍

猜你关心企业是如何评价华清学员的

干货分享
相关新闻
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2026 北京华清远见科技发展有限公司 版权所有 ,京ICP备16055225号-5,京公海网安备11010802025203号

回到顶部