4. 睡眠模式(Sleep Mode):WFI与WFE指令、唤醒源配置、实测功耗数据
好,咱们今天聊聊睡眠模式。说实话,这是STM32低功耗设计里最基础、也最常用的一种模式。我刚开始做低功耗项目那会儿,第一个接触的就是它。为什么?因为它简单、可靠,而且功耗下降的效果立竿见影。
4.1 睡眠模式到底是个啥?
说白了,睡眠模式就是让CPU核心停下来歇着,但外设该干活还干活。你想想看,CPU是芯片里最耗电的模块之一,它一停,电流立马就降下来了。
在睡眠模式下,系统时钟还在跑,SRAM和寄存器里的数据也全部保留。外设像UART、SPI、定时器、ADC这些,你想让它们继续工作,它们就能继续工作。嗯,这里要注意:CPU虽然睡了,但一旦有中断或者事件发生,它马上就能醒过来,响应速度极快。
核心特点:
- CPU时钟停止,但外设时钟继续
- SRAM和寄存器内容全部保留
- 唤醒延迟极低(通常几个时钟周期)
- 唤醒后立即执行中断服务程序或后续代码
4.2 WFI与WFE指令:两种睡觉姿势
进入睡眠模式有两种指令:WFI和WFE。我经常跟团队里的新人说,这俩就像你睡觉的两种姿势——一个等着被叫醒,一个等着被推醒。
WFI(Wait For Interrupt)
执行WFI后,CPU立刻进入睡眠状态。只有当中断发生时,它才会醒来。我个人习惯在需要精确控制唤醒时机的地方用WFI,比如等待某个外设中断完成。
// 进入睡眠模式(WFI方式)
void enter_sleep_mode_wfi(void)
{
// 清除唤醒标志
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// 设置SLEEPDEEP位为0,进入睡眠模式
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
// 醒来后从这里继续执行
// 注意:如果中断服务程序没有返回,这里不会执行
}
WFE(Wait For Event)
WFE的唤醒条件更丰富一些。除了中断,它还能被事件唤醒。事件可以是外部事件线,也可以是SEV指令。我在项目中遇到过一种场景:多个外设需要同步唤醒,用WFE配合事件就特别方便。
// 进入睡眠模式(WFE方式)
void enter_sleep_mode_wfe(void)
{
// 清除事件标志
__SEV();
__WFE();
// 进入睡眠
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFE);
// 醒来后继续执行
// WFE方式醒来后,程序会直接从这里继续
}
我的经验:WFI和WFE的选择其实有讲究。如果你希望中断服务程序执行完后自动回到主循环,用WFI。如果你希望事件发生后程序继续往下走,用WFE。我曾经在一个数据采集项目里用错了,结果程序逻辑全乱了,排查了半天才发现是唤醒方式的问题。
4.3 唤醒源配置
睡眠模式的唤醒源其实挺多的。我整理了一下,常用的有这些:
| 唤醒源 | 说明 | 典型应用 |
|---|---|---|
| 外部中断(EXTI) | GPIO上升沿/下降沿触发 | 按键唤醒、传感器触发 |
| RTC闹钟 | 定时唤醒 | 周期性数据采集 |
| USART接收中断 | 串口收到数据 | 通信唤醒 |
| 定时器中断 | 定时器溢出或比较匹配 | PWM控制、定时任务 |
| ADC转换完成 | 模拟量采集完成 | 传感器数据读取 |
配置唤醒源其实不复杂,关键是要把对应的中断使能打开。我举个例子,用外部中断唤醒:
// 配置PA0作为外部中断唤醒源
void configure_wakeup_source(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIOA时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置PA0为输入,上拉
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 使能EXTI0中断
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
// 中断服务程序
void EXTI0_IRQHandler(void)
{
// 检查中断标志
if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
{
// 清除中断标志
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
// 处理唤醒事件
// 比如设置一个标志位,主循环里判断
}
}
注意:我曾经踩过一个坑——配置了外部中断唤醒,但忘了在进入睡眠前清除中断标志。结果一执行WFI,CPU立刻被同一个中断唤醒,根本睡不进去。排查了整整一个下午才发现。所以,进入睡眠前一定要确保所有中断标志都被清除了。
4.4 实测功耗数据
光说不练假把式。我拿STM32F103C8T6做了个实测,用万用表串在电源线上测的。环境是:主频72MHz,所有外设时钟默认开启。
| 模式 | 典型电流 | 说明 |
|---|---|---|
| 正常运行(72MHz) | 约50mA | 所有外设开启,跑while循环 |
| 睡眠模式(WFI) | 约6mA | CPU停止,外设继续运行 |
| 睡眠模式(WFE) | 约6mA | 和WFI基本一致 |
| 睡眠模式+关闭不用的外设时钟 | 约2mA | 只保留必要的UART和定时器 |
看到没?从50mA降到6mA,降了将近90%。如果你再把不用的外设时钟关掉,还能再降。我有个项目,电池供电的,原来续航只有3天,优化了睡眠模式后直接撑到2周。
优化建议:进入睡眠前,把用不到的外设时钟关掉。比如你只用UART1,那就把UART2、UART3、SPI、I2C这些的时钟全关了。别小看这几毫安,积少成多。
4.5 知识体系图
我画了张图,把睡眠模式的核心逻辑串起来,方便你理解:
4.6 避坑指南
最后,我把自己踩过的坑总结一下,你遇到了能少走弯路:
- 中断优先级:睡眠模式下,只有优先级足够高的中断才能唤醒CPU。我遇到过配置了中断但优先级设得太低,结果CPU一直睡不醒。
- DMA传输:睡眠模式下DMA可以继续工作,但要注意DMA传输完成中断的优先级。我曾经用DMA+UART接收数据,结果DMA传完了CPU没醒,因为中断优先级没配好。
- 调试器连接:进入睡眠模式后,调试器可能会断开连接。我建议你在调试阶段先别进睡眠,或者加个延时,等调试器连上了再睡。
- 唤醒后的状态:WFI唤醒后会执行中断服务程序,然后返回主循环。WFE唤醒后直接继续执行下一条指令。这个区别很重要,别搞混了。
好了,睡眠模式就聊到这儿。你想想看,其实它并不复杂,关键是把进入方式、唤醒源和功耗优化这三块搞清楚。下一节咱们聊停止模式,那个功耗更低,但要注意的东西也更多。
公众号:蓝海资料掘金营,微信deep3321