3. 低功耗模式详解:Sleep、Stop、Standby 与唤醒源配置
做低功耗嵌入式产品,说白了就是跟电池容量较劲。STM32 给了我们三种睡眠模式,就像手机有「锁屏」、「待机」、「关机」三个档位。我刚开始做物联网节点时,总觉得功耗降不下来,后来才发现——不是芯片不行,是我没选对模式。
这一章,咱们就把 Sleep、Stop、Standby 这三个模式掰开揉碎,看看它们到底省了多少电,怎么唤醒,以及——怎么测才准。
3.1 三种低功耗模式的核心差异
先看一张对比表,心里有个底:
| 特性 | Sleep (睡眠) | Stop (停止) | Standby (待机) |
|---|---|---|---|
| CPU 时钟 | 关闭 | 关闭 | 关闭 |
| SRAM 与寄存器 | 保持 | 保持 | 丢失 |
| 外设时钟 | 可配置 | 关闭 | 关闭 |
| 典型电流 (STM32L4) | ~100 µA/MHz | ~1.5 µA | ~0.3 µA |
| 唤醒时间 | ~5 µs | ~50 µs | ~5 ms |
| 唤醒源 | 任意中断 | 特定外设 + RTC | WKUP 引脚 + RTC |
你看,Sleep 模式唤醒最快,但功耗也最高;Standby 最省电,但醒来跟重启差不多。Stop 模式是中间态——我大部分项目都优先用 Stop,除非电池真的撑不住。
关键原则:选模式不是越低越好,而是「够用就行」。如果你的产品每 1 秒醒来一次干活,Sleep 就够了;如果每 10 分钟才醒一次,那必须上 Standby。
3.2 Sleep 模式:最轻量的睡眠
Sleep 模式只停了 CPU 的时钟,其他外设该怎么跑还怎么跑。你想想看,如果只是等一个串口数据,完全没必要进 Stop。
进入方式很简单:
// 进入 Sleep 模式(WFI 指令)
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
// 或者用 WFE 版本(适合事件驱动的场景)
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFE);
这里有个坑——WFI 和 WFE 的区别。WFI 是等中断来了才醒,WFE 是等事件来了就醒。我习惯用 WFI,因为中断模型更可控。但如果你用 DMA 或者外设事件来唤醒,WFE 反而更干净。
我的经验:在 Sleep 模式下,记得把不用的外设时钟关掉。很多人只调了模式,忘了关外设时钟,结果电流还是几百 µA。用 __HAL_RCC_xxx_CLK_DISABLE() 逐个关掉,能再省 30% 的功耗。
3.3 Stop 模式:最常用的低功耗模式
Stop 模式是我用得最多的。它把主时钟全停了,但 SRAM 和寄存器内容还在。唤醒后从断点继续执行,就像什么都没发生过。
进入 Stop 模式有两种变体:
- Stop 0:主稳压器保持开启,唤醒快(~5 µs),但功耗稍高(~5 µA)
- Stop 1:主稳压器关闭,用低功耗稳压器,唤醒慢(~50 µs),但功耗极低(~1.5 µA)
- Stop 2:部分外设的 SRAM 也断电,功耗更低(~0.8 µA),但可用外设更少
代码示例:
// 进入 Stop 1 模式,用 RTC 闹钟唤醒
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后需要重新配置系统时钟
SystemClock_Config();
注意:从 Stop 模式唤醒后,HSE、PLL 这些时钟源都停了。你必须手动调用 SystemClock_Config() 重新初始化时钟树。我见过有人忘了这一步,结果唤醒后串口乱码、定时器不准——全是时钟没配好。
3.4 Standby 模式:极致省电
Standby 模式是最狠的——除了 RTC 和备份域,整个芯片几乎断电。SRAM 里的数据全没了,唤醒后从 main() 重新开始跑。
进入 Standby 的代码:
// 清除唤醒标志
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// 进入 Standby 模式
HAL_PWR_EnterSTANDBYMode();
唤醒后怎么判断是上电复位还是 Standby 唤醒?看标志位:
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) {
// 这是从 Standby 唤醒的
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
// 做唤醒后的特殊处理
} else {
// 这是正常上电复位
}
我曾经在一个电池供电的温湿度记录仪上用过 Standby。设备每 5 分钟醒来一次,采样 10 ms 后又睡过去。一颗 CR2032 电池撑了 2 年——这就是 Standby 的威力。
3.5 唤醒源配置
不同模式支持的唤醒源不一样,我整理了一张表:
| 唤醒源 | Sleep | Stop | Standby |
|---|---|---|---|
| 任意 EXTI 中断 | ✓ | ✓ | ✗ |
| RTC 闹钟 / 唤醒定时器 | ✓ | ✓ | ✓ |
| USART 接收 | ✓ | ✓ | ✗ |
| WKUP 引脚 (PA0/PC13) | ✗ | ✗ | ✓ |
| I2C 地址匹配 | ✓ | ✓ | ✗ |
| USB 唤醒 | ✓ | ✓ | ✗ |
配置 RTC 唤醒定时器是最常见的做法:
// 配置 RTC 每 10 秒唤醒一次
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 10 * 40, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
// 40 是时钟分频后的计数值,具体看 RTC 时钟频率
避坑指南:我曾经在 Stop 模式下用 USART 唤醒,结果发现只有第一个字节能唤醒,后面的数据全丢了。原因是唤醒后 UART 还没准备好接收。解决办法是在中断里加一个 100 µs 的延时,等时钟稳定后再读数据。
3.6 功耗测量方法
测功耗这件事,比你想象的要讲究。我见过有人用万用表直接串在电源上测,结果测出来的值根本不准——因为万用表的采样率跟不上芯片的电流跳变。
我的测量工具链:
- 静态电流:用 6.5 位万用表(如 Keysight 34465A)串在 VDD 上,等芯片稳定后读数
- 动态电流:用示波器 + 电流探头(如 TCP0020),看唤醒瞬间的电流尖峰
- 平均功耗:用精密电阻(10 Ω)串在电源上,示波器测电阻两端电压,再算电流
测量步骤:
- 断开所有调试接口(SWD 会额外耗电 1-2 mA)
- 确保所有 GPIO 要么输出低电平,要么设为模拟输入(浮空输入会漏电)
- 关掉不用的外设时钟
- 进入目标低功耗模式
- 等待 1-2 秒让电源稳定
- 记录电流值
重要:测量时一定要把调试器的 SWD 线拔掉!我刚开始做低功耗时,怎么测都是 3 mA,折腾了两天才发现是 J-Link 在供电。拔掉后电流直接掉到 2 µA——那种感觉,又气又爽。
3.7 知识结构总览
下面这张图把三种模式的关系和选择逻辑串起来了:
嗯,这张图把选择逻辑讲清楚了。你从「需要多快唤醒」出发,往左走是快但费电,往右走是慢但省电。没有绝对的好坏,只有合不合适的场景。
3.8 实测数据参考
最后给一组我在 STM32L476 开发板上实测的数据,供你对比:
| 模式 | 配置 | 实测电流 | 唤醒时间 |
|---|---|---|---|
| Sleep | 所有外设时钟关闭,仅 SysTick 运行 | 180 µA | 4.2 µs |
| Stop 0 | 主稳压器开启,RTC 运行 | 4.8 µA | 6.1 µs |
| Stop 1 | 低功耗稳压器,RTC 运行 | 1.6 µA | 48 µs |
| Stop 2 | 低功耗稳压器,部分 SRAM 断电 | 0.9 µA | 52 µs |
| Standby | RTC 运行,备份域保持 | 0.35 µA | 4.8 ms |
看到没?从 Sleep 到 Standby,电流差了三个数量级。但代价是唤醒时间从 4 µs 变成了 5 ms。如果你的应用能接受 5 ms 的唤醒延迟,那 Standby 就是你的最佳选择。
最后一句经验之谈:低功耗设计不是调一个模式就完事了。GPIO 的电平、外设的时钟、甚至 PCB 的漏电流,都会影响最终结果。我习惯在原理图阶段就把每个引脚的功耗预算算清楚——等板子回来了再调,那就晚了。