3. 休眠模式分类:待机、停止、睡眠
好,咱们直接切入正题。嵌入式系统的低功耗管理,说白了就是一场「功耗 vs 唤醒延迟」的博弈。你想想看,想要省电,就得把芯片里大部分模块都关掉;但关得越彻底,唤醒时重新初始化需要的时间就越长。
我个人习惯把这三种模式看作一个「深度递增」的阶梯。从睡眠到停止,再到待机,功耗越来越低,但唤醒代价也越来越大。下面这张图能帮你快速建立整体认知。
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) {
// 从待机模式唤醒
}
}
最后说一句:这三种模式不是互斥的。实际项目中,我经常在同一个产品里同时用它们——空闲时进待机省电,但需要快速响应某个事件时切到睡眠模式。关键是要理解每种模式的「代价」是什么,然后根据你的业务场景做取舍。