4. 运行模式(Run Mode):动态运行下的功耗优化
各位同学,咱们今天聊点实在的。很多人一提到低功耗,脑子里全是「怎么睡得更沉」。但实际项目里,芯片大部分时间其实是在跑代码的。你想想看,如果跑的时候功耗就压不住,那睡再香也没用。
所以这一章,咱们就聚焦在动态运行下的功耗优化。说白了,就是芯片在干活的时候,怎么让它少喝点电。
4.1 动态功耗的三大来源
要优化,先得知道电都耗在哪了。STM32L4 在 Run Mode 下的功耗,主要来自三块:
- CPU 内核动态功耗:时钟翻转、指令执行,频率越高功耗越大。
- Flash 读取功耗:从 Flash 取指令和数据,这是个大头。
- 外设时钟功耗:没用的外设开着时钟,白白浪费。
我刚开始做低功耗项目时,就犯过一个低级错误——把所有外设时钟都使能了,结果待机电流死活降不下来。后来一查,原来是 SPI 和 I2C 的时钟没关。嗯,从那以后我养成了个习惯:用哪个外设开哪个时钟,用完就关。
4.2 Flash 等待周期与预取缓冲
咱们先聊聊 Flash 读取。STM32L4 的 Flash 可以跑到 80 MHz,但它的读取速度没那么快。CPU 频率越高,需要的等待周期(Wait State)就越多。
| CPU 频率 (MHz) | VOS1 范围 (1.71V-3.6V) | VOS2 范围 (1.71V-3.6V) | VOS3 范围 (1.71V-3.6V) |
|---|---|---|---|
| 0 - 16 | 0 WS | 0 WS | 0 WS |
| 16 - 26 | 1 WS | 1 WS | 1 WS |
| 26 - 36 | 2 WS | 2 WS | 2 WS |
| 36 - 48 | 3 WS | 3 WS | 3 WS |
| 48 - 72 | 4 WS | 4 WS | — |
| 72 - 80 | 5 WS | 5 WS | — |
等待周期越多,CPU 空转的时间就越长,功耗自然就上去了。那怎么办?预取缓冲(Prefetch Buffer)就是干这个的。
预取缓冲会提前把下一条指令从 Flash 里读出来,放到缓冲区里。CPU 要的时候,直接从缓冲区拿,不用等 Flash。这样一来,大部分情况下 CPU 都能零等待执行。
关键点:预取缓冲不是万能的。如果代码里有大量跳转(比如 if-else 嵌套、函数调用),预取命中率会下降。我建议把热点代码放到 RAM 里执行,或者用 ART Accelerator™ 来优化。
4.3 ART Accelerator™ 的使用
ART Accelerator™ 是 ST 搞的一个自适应实时加速器。名字听着高大上,其实原理不复杂:它把 Flash 分成了 8 个 128 位的行,每行可以存 4 条 32 位指令。预取的时候,一次读一整行,然后根据 CPU 的取指模式,智能地调整预取策略。
我个人习惯是:只要 CPU 频率超过 16 MHz,就打开 ART Accelerator™。怎么开?看代码:
/* 使能 ART Accelerator */
__HAL_FLASH_ART_ENABLE();
就这么一行。但要注意,ART 使能后,Flash 的等待周期配置也要同步更新。我建议用 HAL 库的 HAL_FLASHEx_EnableART() 函数,它会自动处理这些细节。
小技巧:如果你的代码是纯顺序执行的(比如跑一个固定的算法),ART 的预取命中率会非常高。但如果是随机跳转很多的应用(比如状态机),可以考虑把状态表放到 RAM 里,或者用 __attribute__((section(".ramfunc"))) 把关键函数放到 RAM 里执行。
4.4 动态电压与频率调节(DVFS)
STM32L4 支持三种电压调节范围:VOS1(高性能)、VOS2(平衡)、VOS3(低功耗)。电压越低,动态功耗越小,但最高频率也受限。
| 电压范围 | 最高频率 | 典型功耗 | 适用场景 |
|---|---|---|---|
| VOS1 | 80 MHz | 高 | 高性能计算、实时控制 |
| VOS2 | 48 MHz | 中 | 日常任务、通信处理 |
| VOS3 | 26 MHz | 低 | 低负载、间歇性工作 |
我在一个电池供电的传感器项目里,就用了 DVFS 动态切换。平时跑 VOS3,频率 16 MHz,功耗只有 1.2 mA。当需要采集数据并做 FFT 时,临时切到 VOS1,频率拉到 80 MHz,算完再切回来。整个切换过程不到 10 微秒,用户完全感觉不到。
切换代码也很简单:
/* 切换到 VOS1 */
HAL_PWREx_EnableVOS1();
/* 等待电压稳定 */
while (__HAL_PWR_GET_FLAG(PWR_FLAG_VOS) != PWR_REGULATOR_VOS1);
/* 更新 Flash 等待周期 */
__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_5);
/* 切换系统时钟到 80 MHz */
// ... 配置 PLL 和时钟树 ...
注意:切换电压范围时,一定要先等电压稳定,再改 Flash 等待周期,最后才能提频率。顺序搞反了,轻则跑飞,重则 Flash 读取错误导致死机。我曾经就因为这个顺序问题,调试了整整一个下午……
4.5 知识结构总览
下面这张图,把 Run Mode 下的功耗优化要点串起来了。你可以把它当作一个检查清单:
4.6 实战建议
说了这么多,总结几条我自己的经验:
- 先定频率,再配等待周期:改时钟前,先把 Flash 等待周期设好,否则必死。
- ART 能开就开:16 MHz 以上就开,几乎没有副作用。
- DVFS 动态切换:别一直跑 VOS1,根据负载切到 VOS2 或 VOS3,能省 30%-50% 的功耗。
- 外设时钟按需使能:用 RCC 的
__HAL_RCC_xxx_CLK_ENABLE()和__HAL_RCC_xxx_CLK_DISABLE()精确控制。 - 热点代码放 RAM:用
__attribute__((section(".ramfunc")))把关键函数放到 RAM 里,减少 Flash 访问次数。
最后一个小提醒:调试的时候,可以用 STM32CubeMonitor 或者串口打印出当前的功耗值。我习惯在关键节点加一个 GPIO 翻转,用示波器看执行时间,再结合电流探头算功耗。这样优化起来,心里才有底。