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 知识体系图

下面这张图帮你理清三种模式的关系和选择路径。

STM32 休眠模式选择流程图 STM32 休眠模式选择流程 需要休眠 是否需要快速唤醒? 是 睡眠模式 否 需要保留数据? 是 停止模式 否 待机模式 说明: • 睡眠模式:唤醒最快(~1μs),适合频繁中断场景 • 停止模式:保留 SRAM,适合需要数据保持的周期性任务 • 待机模式:功耗最低(~2μA),但数据丢失,适合超低功耗场景

2.7 避坑指南

最后,分享几个我踩过的坑。

  • GPIO 状态:进入休眠前,把不用的 GPIO 设为模拟输入或下拉输出。浮空引脚会漏电,我见过有人因此多耗了 10 μA。
  • RTC 时钟源:待机模式下,RTC 只能用 LSI 或 LSE。如果你用的是 HSE,待机后 RTC 会停摆。
  • 唤醒后时钟:停止模式唤醒后,系统时钟默认用 HSI。如果你需要高速运行,记得重新配置 PLL。
  • 调试器干扰:连接调试器时,MCU 可能无法进入深度休眠。我习惯在调试时用睡眠模式,量产时再切到停止或待机。

嗯,今天就先聊到这里。这三种模式是低功耗设计的基础,后面我们会深入讲如何根据实际场景做动态切换。