4、睡眠模式深度解析:待机、停止、休眠模式的选择与切换代价

说到低功耗,很多人第一反应就是「让MCU睡觉」。但睡多深、怎么睡、醒来要花多少力气,这里面的门道可不少。我这些年调试过的低功耗项目,十有八九的坑都出在睡眠模式选型上。

今天咱们就把三种最常见的睡眠模式——待机、停止、休眠,掰开揉碎了讲清楚。

4.1 三种睡眠模式,到底有啥区别?

先给个直观的对比。你想想看,MCU就像一栋办公楼:

  • 休眠模式:关掉大部分灯,但保安室还亮着,门卫还在值班。CPU停了,但外设和时钟还在跑。
  • 停止模式:整栋楼断电,只留一个应急电源给闹钟。CPU、外设、大部分时钟都停了,但SRAM和寄存器内容还在。
  • 待机模式:楼都拆了,只留地基。除了唤醒引脚和RTC,其他全部断电,连SRAM内容都丢了。

说白了,深度越深,功耗越低,但唤醒代价越大。

特性 休眠模式 停止模式 待机模式
CPU时钟 停止 停止 停止
外设时钟 可选保持 停止 停止
SRAM内容 保持 保持 丢失
典型电流 1-10 mA 10-100 μA 0.1-2 μA
唤醒时间 几μs 几十μs 几ms
唤醒源 任意中断 特定外设/RTC RTC/唤醒引脚

核心结论:功耗每降一个数量级,唤醒时间就涨一个数量级。没有免费的午餐。

4.2 休眠模式:浅睡,随叫随到

休眠模式是功耗和响应速度的折中方案。CPU停了,但外设还在跑。比如你让UART在收数据,或者定时器在计数,一旦有事件发生,MCU立刻醒来。

我个人习惯在需要频繁响应的场景用休眠模式。比如一个传感器采集系统,每10ms采集一次数据,中间CPU闲着也是闲着,不如睡个浅觉。

// STM32 休眠模式示例
void enter_sleep_mode(void) {
    // 关闭不用的外设时钟
    __HAL_RCC_GPIOB_CLK_DISABLE();
    __HAL_RCC_GPIOC_CLK_DISABLE();
    
    // 进入休眠模式
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
    
    // 醒来后继续执行
    // 注意:这里不会复位,直接从下一行开始
}

小技巧:进入休眠前,把不用的GPIO设为模拟输入或上拉/下拉固定电平,能再省几十μA。我在项目中遇到过GPIO浮空导致漏电的坑,查了两天才发现。

4.3 停止模式:深度睡眠,保留现场

停止模式就狠多了。CPU停了,外设停了,大部分时钟也停了。但SRAM和寄存器内容还在,醒来后可以无缝恢复。

我曾经在一个电池供电的温湿度记录仪里用过停止模式。设备每5分钟醒来一次,采集数据存到Flash,然后继续睡。用两节AA电池撑了将近一年。

// STM32 停止模式示例
void enter_stop_mode(void) {
    // 配置RTC为唤醒源
    HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
    
    // 进入停止模式
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    // 醒来后需要重新配置系统时钟
    SystemClock_Config();
    // 注意:外设需要重新初始化
}

注意:停止模式醒来后,系统时钟需要重新配置。很多新手在这里翻车——醒来后外设不工作,因为时钟没恢复。我刚开始做的时候也犯过这个错,调试器一挂上去就正常,脱机就跑飞,后来才发现是时钟恢复的问题。

4.4 待机模式:最深睡眠,一切归零

待机模式是最狠的。除了RTC和几个唤醒引脚,整个芯片几乎完全断电。SRAM内容丢失,醒来后相当于复位重跑。

这种模式适合什么场景?超低功耗的间歇性工作设备。比如一个水表,每天上报一次数据,其他时间都在待机。功耗可以做到1μA以下。

// STM32 待机模式示例
void enter_standby_mode(void) {
    // 清除唤醒标志
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
    
    // 使能RTC唤醒
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 4000, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
    
    // 进入待机模式
    HAL_PWR_EnterSTANDBYMode();
    
    // 注意:这里永远不会执行到
    // 醒来后从main函数开始执行
}

关键点:待机模式醒来后,代码从main函数重新开始。所以你要在初始化代码里判断是冷启动还是唤醒启动。我一般会在RTC备份寄存器里存一个标志位。

4.5 切换代价:不只是时间

很多人只关注唤醒时间,忽略了另一个代价——代码复杂度

休眠模式最简单,醒来后继续执行,什么都不用管。停止模式麻烦一点,要重新配时钟和外设。待机模式最麻烦,整个状态机都要重新初始化。

我建议你按这个思路选型:

  1. 先看唤醒频率:每秒唤醒多次 → 休眠模式;每分钟唤醒一次 → 停止模式;每小时唤醒一次 → 待机模式
  2. 再看数据保持需求:需要保持运行状态 → 停止模式;可以接受复位重来 → 待机模式
  3. 最后看功耗预算:平均电流要求 < 10μA → 必须待机模式;10-100μA → 停止模式;> 1mA → 休眠模式

避坑指南:我曾经在一个项目里,为了省那几μA,强行用了待机模式。结果每次唤醒都要重新初始化传感器,初始化时间比工作还长,反而更耗电。后来换成停止模式,总功耗反而降了30%。

4.6 实战建议:怎么选?

说了这么多,给你一个我自己的决策流程:

  • 需要快速响应(< 10μs) → 休眠模式。比如按键扫描、通信协议处理。
  • 需要中等响应(10-100μs) → 停止模式。比如周期性传感器采集。
  • 可以接受慢响应(> 1ms) → 待机模式。比如每天上报一次的设备。

嗯,这里要注意一点:不同厂家的MCU,这三种模式的实现细节差别很大。比如有些MCU的停止模式还能保留部分外设时钟,有些则全部停掉。一定要看数据手册的电气特性表,别光看宣传页上的数字。

最后说一句:低功耗设计不是一味追求最低功耗,而是在功耗、响应速度、代码复杂度之间找到平衡点。这个平衡点,只有你自己能把握。