4、协同痛点分析:DMA传输被低功耗打断的典型场景与后果
好,咱们进入正题。前面几章我们把DMA和低功耗模式单独讲了一遍,看起来各自都挺美好。但把它们放在一起用,问题就来了。
说白了,DMA想要的是“总线时钟一直跑,我好搬运数据”。低功耗想要的是“能睡就睡,最好连时钟都停了”。这两个家伙的目标,天然就是冲突的。
我在项目中遇到过好几次这种“打架”的情况。有一次产品做功耗测试,明明进了Stop模式,电流却比预期高了两个数量级。查了半天,发现是DMA还没来得及传完数据,CPU就下令睡觉了。DMA被强行挂起,外设的FIFO直接溢出,数据全丢了。
这一章,我们就来扒一扒这些典型的“打架”场景,看看后果有多严重。
4.1 场景一:进入低功耗模式时,DMA传输尚未完成
这是最常见、也最容易踩的坑。
你的代码逻辑可能是这样的:先启动DMA传输,然后立即调用WFI或WFE指令进入睡眠模式。你想着“DMA是硬件自动工作的,我CPU先睡,它自己传完就行了”。
嗯,想法很好,但现实很骨感。
我见过一个做音频采集的同事,用DMA把I2S数据搬到内存,循环缓冲。他在主循环里判断缓冲半满后,就进Stop模式等下一次唤醒。结果呢?每次唤醒后,音频数据都有几毫秒的“咔哒”声。原因就是DMA在进入Stop的瞬间被截停了,最后一个采样点没搬完。
后果清单:
- 数据丢失:DMA当前正在搬运的那一笔数据,直接丢掉。
- 指针错位:DMA的CNDTR寄存器停在非预期值,下次启动时地址对不齐。
- 外设FIFO溢出:比如USART、SPI、I2S这些带FIFO的外设,DMA不搬了,FIFO满了就溢出。
- 总线死锁:极少数情况下,DMA和CPU同时请求总线,进入低功耗时仲裁逻辑混乱,导致系统卡死。
4.2 场景二:低功耗定时器唤醒后,DMA配置被复位
这个坑,我估计踩过的人不少。
有些低功耗模式(比如Stop模式),会把大部分外设的寄存器内容都丢光。唤醒后,系统需要重新初始化。但很多人只重新配了GPIO和时钟,忘了DMA。
你想想看:DMA的CCR(配置寄存器)、CNDTR(传输数量寄存器)、CMAR(内存地址寄存器)、CPAR(外设地址寄存器),在Stop模式下可能全部复位成默认值。唤醒后,DMA通道处于禁用状态,地址指针全乱套了。
我曾经在一个电池供电的传感器节点项目里,用LPTIM(低功耗定时器)定期唤醒STM32L4,采集ADC数据。每次唤醒后,DMA传输的数据都是错的。折腾了两天,最后用调试器一看——唤醒后DMA的CNDTR寄存器是0x0000,根本没配置传输长度。
后果清单:
- 传输长度错误:CNDTR复位为0,DMA认为不需要传输任何数据。
- 地址指针丢失:CMAR和CPAR复位,DMA不知道从哪里搬数据。
- 通道禁用:CCR的EN位被清零,DMA通道根本不工作。
- 中断标志混乱:唤醒后可能残留了虚假的中断标志,导致误触发。
4.3 场景三:DMA正在传输时,系统进入低功耗模式
这个场景和第一个有点像,但更隐蔽。它不是“进入前没检查”,而是“进入的时机刚好撞上DMA传输”。
举个例子:你的系统用DMA循环搬运ADC数据。主循环里,你判断某个条件满足后,就调用HAL_PWR_EnterSTOPMode()。但就在你调用这个函数的瞬间,DMA刚好在搬运最后一笔数据。CPU的指令执行和DMA的传输是并行的,你根本不知道谁先谁后。
为什么会这样?因为STM32的Cortex-M内核是哈佛架构,指令总线和数据总线分开。CPU执行WFI指令走的是指令总线,DMA搬运数据走的是数据总线。两者互不干扰,但进入低功耗的硬件逻辑会在下一个时钟周期检查总线状态。如果DMA还在占用总线,系统会等待——但等待多久?数据手册上写的是“直到当前总线传输完成”。这个“当前传输”可能是一个字节,也可能是一个突发传输(Burst Transfer),时间不确定。
后果清单:
- 进入延迟:系统无法立即进入低功耗,功耗比预期高。
- 数据不完整:如果强行切断时钟,DMA的最后一笔数据只搬了一半。
- 外设状态异常:比如DAC正在输出波形,DMA突然停了,输出引脚可能卡在中间电平。
- 调试困难:这种问题不是每次都能复现,跟时序强相关,排查起来很头疼。
4.4 核心冲突:时钟域与总线仲裁
说了这么多场景,其实根源就一个:时钟域冲突。
DMA需要AHB/APB总线时钟才能工作。低功耗模式(尤其是Stop和Standby)会把系统时钟(HCLK、PCLK)关掉。DMA没了时钟,就像汽车没了油,动不了。
但外设呢?有些外设在低功耗模式下还能继续工作——比如LPTIM、RTC、LPUART。它们有自己的独立时钟(LSI、LSE)。这些外设如果配置了DMA请求,就会在唤醒后立即触发DMA。但DMA的时钟还没恢复,请求就丢了。
我画了一张图,帮你理清这个关系:
4.5 后果总结:一张表看清
我把上面提到的场景和后果整理了一下,方便你对照排查:
| 场景 | 触发条件 | 直接后果 | 排查难度 |
|---|---|---|---|
| 进入低功耗时DMA未完成 | WFI/WFE前未检查DMA状态 | 数据丢失、指针错位 | ⭐⭐⭐ 中等 |
| 唤醒后DMA配置复位 | Stop模式未保存DMA寄存器 | 传输长度错误、通道禁用 | ⭐⭐ 较低 |
| DMA传输中进入低功耗 | 时序巧合,DMA和CPU并行 | 数据不完整、外设异常 | ⭐⭐⭐⭐ 高 |
| 外设唤醒后DMA时钟未恢复 | LPTIM/RTC触发DMA请求 | 请求丢失、传输失败 | ⭐⭐⭐⭐⭐ 极高 |