3. 休眠模式分类:待机、停止、睡眠

好,咱们直接切入正题。嵌入式系统的低功耗管理,说白了就是一场「功耗 vs 唤醒延迟」的博弈。你想想看,想要省电,就得把芯片里大部分模块都关掉;但关得越彻底,唤醒时重新初始化需要的时间就越长。

我个人习惯把这三种模式看作一个「深度递增」的阶梯。从睡眠到停止,再到待机,功耗越来越低,但唤醒代价也越来越大。下面这张图能帮你快速建立整体认知。

三种休眠模式核心对比 睡眠模式 CPU 时钟停,外设可运行 停止模式 所有时钟停,SRAM 保持 待机模式 主电源断,仅备份域供电 功耗 ~10 mA ~1 mA ~1 µA 唤醒延迟 ~1 µs ~10 µs ~ms 级 趋势:功耗越低 → 唤醒延迟越长,选择需权衡应用场景

3.1 睡眠模式(Sleep Mode)

睡眠模式是最「轻量级」的休眠。它只停了 CPU 的时钟,但外设、SRAM、寄存器全部保持原样。这意味着什么?你从中断唤醒后,代码可以接着往下执行,连上下文都不用保存。

我在项目中遇到过这样一个场景:一个传感器数据采集节点,每 100ms 醒来读一次数据。如果用睡眠模式,从唤醒到拿到数据只需要 1-2 微秒。说白了,这就是「秒醒」级别的响应。

核心特征:

  • CPU 时钟停止,但内核电源不断
  • 所有外设时钟可继续运行(如定时器、UART、ADC)
  • SRAM 和寄存器内容完全保持
  • 唤醒源:任何中断(外部中断、定时器、UART 等)
  • 典型功耗:几十 µA 到几 mA(取决于外设开启数量)

嗯,这里要注意:睡眠模式下外设虽然能跑,但 CPU 不参与处理。比如你用 UART 接收数据,数据会堆积在 FIFO 里,直到 CPU 醒来才能处理。所以如果你的外设中断频率很高,CPU 可能根本「睡不踏实」——频繁进出中断反而更耗电。

3.2 停止模式(Stop Mode)

停止模式比睡眠深了一层。它把系统里所有时钟都停了——包括外设时钟。但 SRAM 和寄存器仍然保持供电。唤醒后需要重新配置时钟树,但代码和数据都在,可以直接继续跑。

我记得有一次做电池供电的温湿度记录仪,要求每 5 分钟记录一次数据,电池要用一年。睡眠模式功耗还是太高,待机模式又太麻烦(每次唤醒要重新初始化外设)。最后选了停止模式——完美平衡。

我的经验:停止模式下,RTC(实时时钟)通常还能跑。你可以用 RTC 闹钟定时唤醒,这是最常用的方案。但要注意,有些 MCU 的 RTC 在停止模式下需要独立的 LSI 或 LSE 时钟源,别用错了。

唤醒延迟方面,停止模式一般在 10 µs 左右。这个时间主要花在时钟重新稳定上——PLL 重新锁定、HSE 起振都需要时间。我建议你在唤醒后先检查时钟标志位,确认稳定了再操作外设。

避坑指南:我曾经在一个项目中,停止模式唤醒后直接去读 ADC,结果数据全是乱的。查了半天才发现是 ADC 时钟还没稳定。从那以后,我每次唤醒后都会加一个时钟就绪等待循环——虽然多花几微秒,但稳如老狗。

3.3 待机模式(Standby Mode)

待机模式是「终极省电方案」。它把主电源都断了——VDD 域断电,SRAM 和寄存器内容全部丢失。只有备份域(Backup Domain)还在供电,里面通常只保留 RTC 和几个备份寄存器。

说白了,待机模式相当于「关机重启」。唤醒后芯片会从头执行启动代码,就像按了复位键一样。所以你的程序必须能判断是从待机唤醒的,还是上电复位的——通常通过检查复位状态寄存器(RCC_CSR)来实现。

特性 睡眠模式 停止模式 待机模式
CPU 时钟 停止 停止 断电
外设时钟 可运行 停止 断电
SRAM 保持 是 是 否
寄存器保持 是 是 否
唤醒源 任意中断 特定中断(RTC、EXTI) RTC 闹钟、WKUP 引脚、复位
典型功耗 ~10 mA ~1 mA ~1 µA
唤醒延迟 ~1 µs ~10 µs ~ms 级
代码执行 继续执行 继续执行 从头开始

你想想看,待机模式功耗能低到 1 µA 以下,但代价是唤醒后所有外设都要重新初始化。如果你的设备需要频繁唤醒(比如每秒一次),待机模式反而不划算——初始化外设的功耗可能比睡眠模式还高。

选型建议:

  • 需要快速响应(< 10 µs) → 睡眠模式
  • 需要低功耗 + 数据保持 → 停止模式
  • 需要极低功耗(µA 级) → 待机模式

3.4 三种模式的代码骨架

下面给出一段伪代码,展示三种模式的进入和唤醒处理。实际使用时,具体寄存器名称请参考对应 MCU 手册。

// 睡眠模式
void enter_sleep_mode(void) {
    // 设置 SLEEPDEEP 位为 0
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    // 执行 WFI 指令
    __WFI();
    // 唤醒后从这里继续执行
}

// 停止模式
void enter_stop_mode(void) {
    // 设置 SLEEPDEEP 位为 1
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    // 选择停止模式(PDDS = 0)
    PWR->CR &= ~PWR_CR_PDDS;
    // 执行 WFI
    __WFI();
    // 唤醒后重新配置时钟
    system_clock_config();
}

// 待机模式
void enter_standby_mode(void) {
    // 设置 SLEEPDEEP 位为 1
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    // 选择待机模式(PDDS = 1)
    PWR->CR |= PWR_CR_PDDS;
    // 清除 WUF 标志
    PWR->CR |= PWR_CR_CWUF;
    // 执行 WFI
    __WFI();
    // 不会执行到这里——芯片已复位
}

// 启动代码中判断唤醒源
void check_wakeup_reason(void) {
    if (RCC->CSR & RCC_CSR_SFTRSTF) {
        // 软件复位
    } else if (RCC->CSR & RCC_CSR_PORRSTF) {
        // 上电复位
    } else if (RCC->CSR & RCC_CSR_WDGRSTF) {
        // 看门狗复位
    } else if (PWR->CSR & PWR_CSR_WUF) {
        // 从待机模式唤醒
    }
}

最后说一句:这三种模式不是互斥的。实际项目中,我经常在同一个产品里同时用它们——空闲时进待机省电,但需要快速响应某个事件时切到睡眠模式。关键是要理解每种模式的「代价」是什么,然后根据你的业务场景做取舍。