第3章 Sleep模式实战:WFI与WFE指令、唤醒源配置、代码实现与功耗实测

Sleep模式,说白了就是让CPU先歇着,但外设和时钟都还醒着。这是STM32所有低功耗模式里最轻量级的一种,也是我最早接触的省电模式。记得我第一次用Sleep模式时,以为只要调个函数就完事了,结果发现唤醒后程序跑飞了——嗯,后来才明白是中断向量表没处理好。

这一章,咱们就把Sleep模式彻底吃透。从指令差异到唤醒源配置,再到实测数据,一步到位。

3.1 WFI与WFE:两条指令的微妙差异

进入Sleep模式有两种方式:WFI(Wait For Interrupt)和WFE(Wait For Event)。它们看起来差不多,但行为逻辑完全不同。

对比项 WFI WFE
唤醒条件 必须产生中断 事件(中断或SEV指令)
唤醒后执行 进入中断服务函数 直接继续主程序
中断挂起影响 挂起的中断会立即唤醒 需清除事件寄存器
典型场景 定时器周期性唤醒 外设DMA传输完成

关键区别:WFI是"被动等待中断",WFE是"主动检查事件标志"。我习惯在需要精确控制唤醒时机时用WFI,在数据流场景用WFE——比如ADC连续采样,用WFE可以避免频繁进出中断的开销。

3.2 唤醒源配置:哪些外设能叫醒CPU?

Sleep模式下,所有外设时钟都保持运行。所以理论上,任何能产生中断或事件的外设都能作为唤醒源。但实际项目中,我常用的就这几种:

  • EXTI外部中断:GPIO引脚电平变化,最灵活
  • 定时器:TIM2/TIM3等基本定时器,周期性唤醒
  • USART:串口接收中断,常用于通信等待
  • RTC闹钟:绝对时间唤醒,适合低功耗时钟应用
  • DMA传输完成:配合外设自动处理数据

我的经验:用EXTI唤醒时,记得配置上拉/下拉电阻。我曾经在一个按键唤醒的项目里,忘了开内部上拉,结果引脚悬空,芯片在Sleep模式下被噪声反复唤醒,功耗反而比正常运行时还高。

3.3 Sleep模式代码实现:从CubeMX到HAL库

咱们以STM32F103为例,实现一个定时器每1秒唤醒一次Sleep模式的Demo。我会把CubeMX配置和手写代码都讲清楚。

3.3.1 CubeMX配置步骤

  1. 选择芯片型号,配置时钟为HSI 8MHz
  2. 使能TIM2,预分频器设为7999,自动重装载值设为999
  3. 使能TIM2中断(NVIC中勾选)
  4. Project Manager中生成代码

这里有个坑:一定要在NVIC中使能中断。我见过有人只配了定时器,忘了开中断,结果WFI永远睡不醒。

3.3.2 代码实现

/* main.c */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();
    
    /* 启动定时器 */
    HAL_TIM_Base_Start_IT(&htim2);
    
    while (1)
    {
        /* 进入Sleep模式 */
        HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
        
        /* 唤醒后从这里继续执行 */
        __NOP();  /* 空操作,方便调试断点 */
    }
}

/* 定时器中断回调 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        /* 翻转LED,指示唤醒 */
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

注意:HAL_PWR_EnterSLEEPMode的第二个参数有两种选择:PWR_SLEEPENTRY_WFI和PWR_SLEEPENTRY_WFE。别搞混了,否则你的程序可能永远睡不醒或者永远不睡。

3.4 实测功耗对比:数字会说话

我用STM32F103C8T6最小系统板做了实测,供电3.3V,外设只保留LED和定时器。结果如下:

模式 电流(mA) 说明
正常运行(72MHz) 28.5 while(1)空转
Sleep模式(WFI) 6.2 定时器1秒唤醒一次
Sleep模式(WFE) 6.1 事件唤醒,几乎无差别
Stop模式(参考) 0.8 下一章会讲

看到没?Sleep模式比正常运行省了约78%的功耗。但如果你需要更极致的省电,Stop模式还能再降一个数量级——这个咱们下一章再聊。

实测结论:Sleep模式适合"频繁唤醒、短时间工作"的场景。比如传感器数据采集,每100ms醒来一次读数据,其余时间睡觉,整体功耗能控制在很低的水平。

3.5 避坑指南:我踩过的三个坑

  • 坑一:中断优先级——如果所有中断优先级相同,WFI可能被高优先级中断"抢跑"。我建议把唤醒源的中断优先级设得比其他中断低一级。
  • 坑二:调试器干扰——用ST-Link调试时,Sleep模式可能无法正常进入。因为调试器会持续产生中断。解决办法:拔掉调试器,用串口打印看效果。
  • 坑三:外设时钟未关闭——虽然Sleep模式不关外设时钟,但如果你不需要某些外设,手动关闭它们的时钟能再省一点电。比如不用的SPI、I2C,调用__HAL_RCC_SPI1_CLK_DISABLE()。

3.6 知识结构图:Sleep模式全貌

下面这张图把Sleep模式的整体流程和关键节点串起来了,建议你对照着看代码。

Sleep模式知识结构图 进入Sleep模式 WFI指令 WFE指令 定时器中断 EXTI外部中断 USART接收 DMA完成 进入中断服务函数 继续主程序执行 注意事项:中断优先级 / 调试器干扰 / 外设时钟 说明:WFI唤醒后进入中断,WFE唤醒后继续主程序——这是两者最本质的区别

3.7 小结

Sleep模式是低功耗设计的入门必修课。它简单、可靠、功耗降低明显。你只需要记住三点:

  • WFI适合中断驱动的场景,WFE适合事件驱动的场景
  • 唤醒源配置时别忘了使能中断
  • 实测功耗时拔掉调试器

把这些基础打牢了,后面学Stop和Standby模式会轻松很多。毕竟,低功耗设计说白了就是"该睡的时候睡,该醒的时候醒"——Sleep模式就是帮你把"睡"这件事做到极致。