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 Ω)串在电源上,示波器测电阻两端电压,再算电流

测量步骤:

  1. 断开所有调试接口(SWD 会额外耗电 1-2 mA)
  2. 确保所有 GPIO 要么输出低电平,要么设为模拟输入(浮空输入会漏电)
  3. 关掉不用的外设时钟
  4. 进入目标低功耗模式
  5. 等待 1-2 秒让电源稳定
  6. 记录电流值

重要:测量时一定要把调试器的 SWD 线拔掉!我刚开始做低功耗时,怎么测都是 3 mA,折腾了两天才发现是 J-Link 在供电。拔掉后电流直接掉到 2 µA——那种感觉,又气又爽。

3.7 知识结构总览

下面这张图把三种模式的关系和选择逻辑串起来了:

STM32 低功耗模式选择流程图 STM32 低功耗模式选择逻辑 系统运行中 需要多快唤醒? ~5 µs Sleep 模式 ~100 µA/MHz ~50 µs Stop 模式 ~1.5 µA ~5 ms Standby 模式 ~0.3 µA 唤醒源 任意中断 USART / I2C 唤醒源 RTC / EXTI USART / I2C 唤醒源 RTC 闹钟 WKUP 引脚 原则:唤醒越快 → 功耗越高 → 选最慢够用的那个

嗯,这张图把选择逻辑讲清楚了。你从「需要多快唤醒」出发,往左走是快但费电,往右走是慢但省电。没有绝对的好坏,只有合不合适的场景。

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 的漏电流,都会影响最终结果。我习惯在原理图阶段就把每个引脚的功耗预算算清楚——等板子回来了再调,那就晚了。