第4章 Sleep模式实战:进入与退出、WFI与WFE、外设状态与功耗数据

好,咱们直接进入Sleep模式。这是STM32L4所有低功耗模式里最“温柔”的一个——内核停了,但大部分外设还在干活。我刚开始做低功耗项目时,总觉得Sleep模式太简单,不就是个WFI指令嘛。结果呢?板子功耗死活降不下来。后来才发现,问题出在外设时钟没关干净。

这一章,咱们就把Sleep模式掰开揉碎。从进入退出,到WFI和WFE的区别,再到外设到底该不该关,最后给出一组实测数据。你跟着走一遍,心里就有底了。

4.1 Sleep模式的本质

Sleep模式下,CPU时钟停止,但所有外设时钟仍然运行。SRAM和寄存器内容全部保留。说白了,就是CPU睡大觉,外设继续干活。

我习惯把Sleep模式比作“待机”——手机屏幕关了,但后台还在下载。功耗降了,但响应速度极快。

特性 Sleep模式 说明
CPU时钟 停止 内核不再取指执行
外设时钟 运行 USART、SPI、定时器等继续工作
SRAM/寄存器 保持 数据不丢失
唤醒源 任意中断/事件 包括EXTI、外设中断等
唤醒时间 ~5个CPU周期 几乎无延迟

核心要点:Sleep模式是功耗与响应速度的最佳平衡点。如果你的应用需要频繁唤醒(比如1ms一次),Sleep模式是首选。

4.2 进入与退出Sleep模式

进入Sleep模式,就两条指令:WFI和WFE。但怎么用,差别很大。

4.2.1 标准进入流程

先看代码。这是最基础的Sleep模式进入方式:

// 进入Sleep模式(WFI方式)
void Enter_Sleep_Mode(void)
{
    // 1. 清除唤醒标志(如果有)
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
    
    // 2. 选择Sleep模式(默认就是Sleep)
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
    
    // 3. 唤醒后从这里继续执行
    // 注意:不会复位,直接执行下一条指令
}

嗯,这里要注意:PWR_SLEEPENTRY_WFI 和 PWR_SLEEPENTRY_WFE 的区别,咱们后面细说。

唤醒后,程序从WFI/WFE的下一条指令继续执行。不会进中断吗?会,但中断服务函数执行完后,就回到这里了。我刚开始总以为唤醒后会复位,其实不会。

4.2.2 唤醒源配置

任何中断都可以唤醒Sleep模式。但实际项目中,我建议你明确只使能需要的唤醒源。否则,一个串口空闲中断就能把芯片叫醒,功耗就上去了。

举个例子,用EXTI线0唤醒:

// 配置PA0为EXTI0唤醒源
void Configure_WakeUp_Source(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_SYSCFG_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);
}

个人经验:我习惯在进入Sleep前,先关掉所有不用的外设中断。只留唤醒源对应的中断。这样能避免“误唤醒”——有一次我的设备半夜被串口噪声唤醒,电池直接掉了一半。

4.3 WFI与WFE指令的区别

这是Sleep模式里最容易搞混的地方。WFI和WFE都能让CPU睡觉,但唤醒机制完全不同。

特性 WFI WFE
全称 Wait For Interrupt Wait For Event
唤醒条件 使能的中断被触发 事件(中断或SEV指令)
中断响应 唤醒后进入ISR 唤醒后不进入ISR(事件方式)
适用场景 需要处理中断 仅需唤醒,不处理中断
功耗 相同 相同

说白了,WFI是“有中断就醒,醒了就干活”。WFE是“有事件就醒,但我不进中断”。

你想想看,什么时候用WFE?比如你用一个定时器产生事件,只是想让CPU醒来轮询一个标志位,不需要进中断服务函数。这时候WFE就比WFI干净。

避坑指南:我曾经在项目里用WFE配合PendSV做任务切换,结果发现唤醒后事件标志位没清干净,导致CPU刚睡下又被唤醒。后来在循环里加了 __SEV() 和 __WFE() 配对使用才解决。记住:WFE的事件标志位是粘性的,不清掉会一直触发。

4.4 Sleep模式下的外设状态

这是Sleep模式最容易被忽视的地方。CPU睡了,但外设还在跑。哪些外设该关?哪些不能关?

4.4.1 外设时钟管理

我个人的习惯是:进入Sleep前,关闭所有不用的外设时钟。别指望HAL库帮你做这件事。

// 进入Sleep前关闭不用的外设时钟
void Sleep_Preprocess(void)
{
    // 关闭SPI、I2C、USART等(如果当前不用)
    __HAL_RCC_SPI1_CLK_DISABLE();
    __HAL_RCC_I2C1_CLK_DISABLE();
    __HAL_RCC_USART2_CLK_DISABLE();
    
    // 保留唤醒源对应的外设时钟
    // 比如用RTC唤醒,就保留RTC时钟
    // __HAL_RCC_RTC_CLK_ENABLE(); // 如果RTC已经在跑,不用动
    
    // 关闭ADC、DAC等模拟外设
    HAL_ADC_DeInit(&hadc1);
    HAL_DAC_Stop(&hdac1);
}

为什么要手动关?因为HAL_PWR_EnterSLEEPMode不会帮你关外设时钟。你不关,外设就在那耗电。虽然Sleep模式下外设时钟还在,但你不使能它,它就不工作。

4.4.2 必须保持运行的外设

有些外设在Sleep模式下必须保持运行,否则你没法唤醒:

  • RTC:定时唤醒的标配
  • LPTIM:低功耗定时器,可以在Sleep模式下产生事件
  • USART:如果要用串口唤醒,必须保持时钟
  • EXTI:外部中断控制器,依赖系统时钟吗?不依赖,但GPIO模块的时钟要开着

关键点:Sleep模式下,外设的功耗取决于它的时钟源。如果外设用的是HSI16或MSI,功耗会比用LSE高很多。我建议:能用LSE的尽量用LSE,比如RTC和LPTIM。

4.5 典型功耗数据

光说不练假把式。下面是我在STM32L496ZG开发板上实测的数据。环境:VDD=3.3V,25°C,所有外设默认配置。

模式 配置 典型电流 说明
Run模式 HSI16, 所有外设开 ~2.5 mA 正常跑代码
Sleep模式 HSI16, 外设全关 ~450 µA 只保留内核供电
Sleep模式 MSI 1MHz, 外设全关 ~180 µA 降低系统时钟频率
Sleep模式 MSI 1MHz, RTC运行 ~220 µA RTC用LSE
Sleep模式 MSI 1MHz, USART运行 ~350 µA USART用HSI16

看到没?同样是Sleep模式,电流能从450µA降到180µA。差别在哪?系统时钟和外设时钟。

我建议你:进入Sleep前,把系统时钟切换到低速振荡器。比如从HSI16切到MSI 1MHz。这样即使有外设还在跑,功耗也能降一大截。

实测技巧:用MSI 1MHz跑Sleep模式,配合RTC每秒唤醒一次,平均功耗能做到~200µA。如果再加上Stop模式的配合(后面章节会讲),可以做到10µA以下。但那是后话了。

4.6 知识结构图

下面这张图,把Sleep模式的核心逻辑串起来了。你一看就明白:

Sleep模式知识结构图 Sleep模式核心知识结构 Sleep模式 进入与退出 WFI / WFE 指令 唤醒源配置(EXTI等) 唤醒后继续执行 WFI vs WFE WFI:唤醒后进ISR WFE:唤醒后不进ISR 事件标志位粘性 外设状态管理 关闭不用的外设时钟 保留唤醒源时钟 降低系统时钟频率 典型功耗:180 µA ~ 450 µA(取决于配置)

4.7 实战建议

最后,给你几条我在项目中踩出来的建议:

  1. 别偷懒用HAL_Delay:Sleep模式下,SysTick中断会唤醒CPU。如果你用HAL_Delay做延时,CPU会频繁醒来,功耗根本降不下去。
  2. 测量功耗时用万用表串联:别信仿真器里的电流读数。我吃过亏,仿真器显示的电流比实际低30%。
  3. 先测裸机,再上RTOS:FreeRTOS的Tickless模式依赖Sleep模式。但如果你连裸机的Sleep功耗都调不好,上了RTOS只会更乱。
  4. 注意GPIO浮空:进入Sleep前,把不用的GPIO设为模拟模式或上拉/下拉输出。浮空引脚在Sleep模式下会漏电,我见过一个浮空引脚多耗20µA。

重要提醒:Sleep模式不是万能的。如果你的目标功耗是10µA以下,Sleep模式做不到。那是Stop模式和Standby模式的领域。但如果你需要快速响应(唤醒时间<10µs),Sleep模式是唯一选择。