2. MCU 的休眠模式详解:STM32 的睡眠、停止与待机
做低功耗产品,说白了就是跟 MCU 的“睡法”打交道。STM32 给了我们三种睡觉姿势:睡眠、停止和待机。姿势不同,省电效果天差地别,唤醒方式也完全不一样。
我个人习惯先把这三种模式的核心差异记住,再根据项目需求去选。你想想看,一个用电池跑一年的传感器,跟一个插着电但偶尔需要低功耗的网关,选型逻辑能一样吗?
2.1 三种模式的核心差异
先看一张对比表,心里有个底。
| 特性 | 睡眠模式 (Sleep) | 停止模式 (Stop) | 待机模式 (Standby) |
|---|---|---|---|
| CPU 时钟 | 关闭 | 关闭 | 关闭 |
| Flash / SRAM | 保持 | 保持 | SRAM 丢失 |
| 寄存器 | 保持 | 保持 | 丢失(备份域除外) |
| 外设时钟 | 可配置关闭 | 可配置关闭 | 全部关闭 |
| 典型电流 (F103) | ~5 mA | ~20 μA | ~2 μA |
| 唤醒源 | 任何中断 / 事件 | EXTI / RTC / USB | WKUP 引脚 / RTC / NRST |
| 唤醒时间 | ~1 μs | ~5 μs | ~50 μs |
嗯,这里要注意:待机模式下的 SRAM 内容会丢失。这意味着你醒来后,所有变量都得重新初始化。我在项目中遇到过有人把关键数据放在全局变量里,一进待机全没了,排查了半天。
2.2 睡眠模式(Sleep Mode)
睡眠模式是最浅的休眠。CPU 内核停了,但 Flash、SRAM 和外设都还在工作。说白了,就是 CPU 歇着,外设继续跑。
唤醒速度极快,几乎感觉不到延迟。适合那种需要频繁响应中断,但又想省点电的场景。比如按键扫描、短距离无线通信的间歇监听。
关键点:睡眠模式下,任何中断都能唤醒 CPU。你不需要配置特殊的唤醒引脚,中断服务函数执行完后,程序会接着睡下去。
代码实现很简单:
// 进入睡眠模式
__WFI(); // Wait For Interrupt
或者用汇编:
__asm volatile("wfi");
我曾经在一个低功耗蓝牙项目里,用睡眠模式做广播间隔的等待。每 100ms 醒来一次,发完包继续睡。电流从 20mA 降到了 5mA 左右,效果很明显。
2.3 停止模式(Stop Mode)
停止模式就深了一层。CPU 停了,所有外设时钟也停了。但 SRAM 和寄存器内容还在。唤醒后,系统时钟需要重新配置。
为什么会有 20 μA 左右的电流?因为内部电压调节器还在工作,给 SRAM 和寄存器供电。你想想看,数据不丢,总要付出点代价。
唤醒源有限制:只能通过 EXTI(外部中断)、RTC 闹钟或 USB 唤醒。我建议你优先用 RTC 闹钟做周期性唤醒,精度高,功耗也低。
个人经验:进入停止模式前,记得把不用的外设时钟关掉。比如 ADC、SPI、USART 的时钟,哪怕它们没在工作,时钟树上的消耗也还在。我习惯在进入停止前调用一个 HAL_SuspendTick(),把 SysTick 也停了。
代码示例:
// 进入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
唤醒后需要重新配置系统时钟:
// 唤醒后重新配置时钟
SystemClock_Config();
嗯,这里有个坑:如果你用的是 HSE(外部高速晶振),唤醒后晶振起振需要时间。我建议在唤醒后加一个短暂的延时,等时钟稳定了再操作外设。
2.4 待机模式(Standby Mode)
待机模式是 STM32 最深的睡眠。除了备份域(RTC、备份寄存器),整个芯片几乎都断电了。电流可以低到 2 μA 以下。
代价是什么?SRAM 和寄存器全部丢失。醒来后,程序会像复位一样从头执行。你需要在 main() 函数开头判断一下,是上电复位还是从待机唤醒。
警告:待机模式下,WKUP 引脚(PA0)可以唤醒。但注意,WKUP 引脚是上升沿触发。如果你用的是下降沿,需要外部加反相器。我曾经在这里吃过亏,板子焊好了才发现唤醒逻辑反了。
判断唤醒源的方法:
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) {
// 从待机模式唤醒
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
} else {
// 上电复位
}
进入待机模式的代码:
// 进入待机模式
HAL_PWR_EnterSTANDBYMode();
我个人习惯在进入待机前,把 RTC 闹钟设好。这样设备可以每天固定时间醒来一次,上报数据,然后继续睡。一个 CR2032 纽扣电池,撑一年没问题。
2.5 三种模式的唤醒源对比
为了方便你选型,我把唤醒源整理成了一张清单。
| 唤醒源 | 睡眠模式 | 停止模式 | 待机模式 |
|---|---|---|---|
| 任何中断 | ✅ | ❌ | ❌ |
| EXTI (外部中断) | ✅ | ✅ | ❌ |
| RTC 闹钟 | ✅ | ✅ | ✅ |
| RTC 唤醒定时器 | ✅ | ✅ | ✅ |
| USB 唤醒 | ✅ | ✅ | ❌ |
| WKUP 引脚 (PA0) | ❌ | ❌ | ✅ |
| NRST 复位引脚 | ✅ | ✅ | ✅ |
你想想看,如果你的设备需要随时响应外部按键,睡眠模式最合适。如果只需要定时上报,停止模式就够了。如果追求极致省电,待机模式是唯一选择。
2.6 知识体系图
下面这张图帮你理清三种模式的关系和选择路径。
2.7 避坑指南
最后,分享几个我踩过的坑。
- GPIO 状态:进入休眠前,把不用的 GPIO 设为模拟输入或下拉输出。浮空引脚会漏电,我见过有人因此多耗了 10 μA。
- RTC 时钟源:待机模式下,RTC 只能用 LSI 或 LSE。如果你用的是 HSE,待机后 RTC 会停摆。
- 唤醒后时钟:停止模式唤醒后,系统时钟默认用 HSI。如果你需要高速运行,记得重新配置 PLL。
- 调试器干扰:连接调试器时,MCU 可能无法进入深度休眠。我习惯在调试时用睡眠模式,量产时再切到停止或待机。
嗯,今天就先聊到这里。这三种模式是低功耗设计的基础,后面我们会深入讲如何根据实际场景做动态切换。