4. 睡眠模式(Sleep Mode):进入与退出,WFI与WFE的抉择

睡眠模式,说白了就是让CPU先歇着,但外设该干活还得干活。这是STM32L4所有低功耗模式里最轻量级的一种,也是我项目里用得最多的模式——因为它进得快、出得快,功耗降得也实在。

我记得第一次用睡眠模式时,以为只要调个函数就完事了。结果发现电流死活降不下来,折腾了半天才发现是GPIO没配置好。嗯,这些坑咱们今天一个个填上。

4.1 睡眠模式到底干了什么?

进入睡眠模式后,CPU内核时钟停止,但所有外设时钟照常运行。SRAM和寄存器内容全部保持。说白了,就是CPU睡过去了,但整个芯片的“记忆”和“周边设备”都醒着。

我习惯把睡眠模式比作“值班室打盹”——人睡了,但电话、门铃、报警器都开着,一有动静立刻醒。

睡眠模式关键特征:

  • CPU时钟停止,但系统时钟(SYSCLK)仍在运行
  • 所有外设(USART、SPI、定时器、ADC等)正常工作
  • SRAM和寄存器内容完全保持
  • 唤醒延迟极短:约5-10个CPU时钟周期
  • 典型电流:约500μA - 2mA(取决于外设开启数量)

4.2 进入睡眠模式:两条指令的江湖

进入睡眠模式有两种方式:WFI和WFE。很多初学者觉得它们差不多,其实差别大了去了。

特性 WFI (Wait For Interrupt) WFE (Wait For Event)
唤醒源 任何中断(需使能) 事件(包括中断转事件)
唤醒行为 中断触发 → 进入ISR → 返回继续 事件触发 → 直接继续执行
中断挂起影响 若已有挂起中断,立即唤醒 若已有挂起事件,立即唤醒
典型场景 等待外设中断(如UART接收) 等待外设事件(如定时器更新)
功耗 相同 相同

你可能会问:那到底用哪个?我个人习惯是:如果唤醒后需要立刻处理数据,用WFI;如果只是等待某个标志位变化,用WFE。举个例子——

我的经验:在做一个低功耗传感器节点时,我用WFE等待定时器事件来触发ADC采样。因为不需要进中断,省去了入栈出栈的开销,代码也更简洁。但如果是UART接收数据,我肯定用WFI,因为数据来了必须进中断处理,否则可能丢字节。

4.3 代码实战:进入与退出睡眠模式

先看一个最简单的WFI睡眠示例:

// 进入睡眠模式(WFI方式)
void EnterSleepMode_WFI(void)
{
    // 1. 清除SLEEPDEEP位,确保进入的是睡眠而非停止模式
    CLEAR_BIT(SCB->SCR, SCB_SCR_SLEEPDEEP_Msk);
    
    // 2. 选择睡眠模式退出方式(可选)
    // 0: 中断唤醒后继续执行
    // 1: 中断唤醒后重新执行WFI后的指令
    // 这里用默认方式0
    
    // 3. 执行WFI指令
    __WFI();
    
    // 唤醒后从这里继续执行
}

再看WFE版本:

// 进入睡眠模式(WFE方式)
void EnterSleepMode_WFE(void)
{
    // 清除SLEEPDEEP位
    CLEAR_BIT(SCB->SCR, SCB_SCR_SLEEPDEEP_Msk);
    
    // 执行WFE指令
    __WFE();
    
    // 唤醒后直接继续执行
}

嗯,代码看起来很简单对吧?但实际项目中我踩过一个坑——

我曾经犯过的错:在进入睡眠前没有检查是否有中断挂起。结果一执行WFI,因为之前某个中断标志还挂着,CPU立刻被唤醒,根本睡不下去。后来我养成了习惯:进睡眠前先清一下NVIC的中断挂起位,或者用__SEV()指令先清除事件寄存器。

4.4 睡眠模式下的功耗实测

光说不练假把式。咱们用实际数据说话。以下是我用STM32L452RE开发板实测的结果:

测试条件 运行模式电流 睡眠模式电流 降幅
仅CPU运行(无外设) 2.1 mA 480 μA 77%
CPU + USART1(115200bps) 3.8 mA 1.2 mA 68%
CPU + 定时器TIM2(1kHz) 2.8 mA 720 μA 74%
CPU + ADC(连续采样) 4.5 mA 1.8 mA 60%

看到没?即使外设全开,睡眠模式也能省下60%以上的功耗。如果外设用得少,省电效果更明显。

但这里有个关键点:外设的功耗在睡眠模式下不会变。你想想看,USART还在发数据,定时器还在跑,ADC还在采样——这些外设的电流是固定的。所以睡眠模式省的是CPU那部分电。

实测技巧:我习惯用万用表串联在VDD供电线上测电流。注意要选μA档,因为睡眠模式下电流可能掉到几百μA。另外,测量前先让系统稳定运行几秒钟,避免上电瞬间的浪涌电流影响读数。

4.5 睡眠模式的核心流程

我把整个睡眠模式的进入和退出流程画了张图,方便你理解:

睡眠模式进入与退出流程 运行模式 选择进入指令:WFI 或 WFE WFI:等待中断 WFE:等待事件 睡眠模式(CPU停止) 唤醒 → 继续执行 WFI唤醒:进入ISR → 返回主程序 WFE唤醒:直接继续执行

4.6 避坑指南:睡眠模式常见问题

做项目这么多年,我在睡眠模式上栽过的跟头不少。挑几个典型的说说:

  1. GPIO浮空导致漏电——睡眠模式下,未使用的GPIO如果配置成浮空输入,会因电平不确定而产生额外漏电流。我习惯把所有不用的GPIO配置成模拟模式或上拉输出。
  2. 中断标志未清除——进睡眠前不清中断标志,WFI一执行就立刻唤醒。我曾经因为这个bug,系统功耗比预期高了3倍。
  3. 外设时钟未关闭——睡眠模式只停CPU时钟,外设时钟得你自己关。如果某个外设不需要在睡眠期间工作,记得调用__HAL_RCC_xxx_CLK_DISABLE()。
  4. 调试器影响睡眠——连接ST-Link调试时,睡眠模式可能无法正常进入。因为调试接口会阻止CPU真正停止。断开调试器再测功耗才准。

特别提醒:如果你用HAL库,注意HAL_PWR_EnterSLEEPMode()函数内部会处理SLEEPDEEP位和WFI指令。但默认情况下,它用的是WFI。如果你需要WFE,得自己调用__WFE()或者修改库函数。我一般直接操作寄存器,更可控。

4.7 什么时候用睡眠模式?

睡眠模式最适合那些“CPU大部分时间闲着,但外设一直在干活”的场景。比如:

  • UART等待接收数据,CPU不需要参与
  • 定时器周期性触发,CPU只在中断里处理
  • ADC连续采样,DMA自动搬运数据
  • SPI或I2C从机等待主机通信

说白了,只要你的外设能自己干活,CPU就可以去睡觉。等外设干完了,再叫醒CPU来处理结果。这才是低功耗设计的精髓——让CPU只在必要时工作。