4. Stop模式实战:进入与退出、RTC唤醒、外部中断唤醒、SRAM保持策略

Stop模式,说白了就是让STM32大部分电路都歇着,但SRAM和寄存器里的数据得给我保住。我刚开始接触低功耗时,总觉得Stop模式和Sleep模式差不多,结果一测电流,差了不止一个数量级。嗯,这里面的门道,咱们今天一条条捋清楚。

核心要点:Stop模式下,内核时钟关闭,所有数字外设时钟停止,但SRAM和寄存器内容保持。唤醒后从断点继续执行,相当于打了个盹儿,醒来接着干活。

4.1 Stop模式的两种变体

STM32的Stop模式其实分两种:Stop0和Stop1(部分系列还有Stop2)。我习惯把它们比作「浅睡」和「深睡」。

特性 Stop0 Stop1
主稳压器 保持开启 关闭,切换到低功耗稳压器
典型电流 ~200 µA(视系列而定) ~20 µA
唤醒时间 ~5 µs ~10 µs
SRAM保持 全部保持 全部保持
适用场景 需要快速响应 更在意功耗

我个人习惯:如果唤醒后要立刻处理数据,用Stop0;如果只是定时醒来发个心跳包,Stop1更合适。曾经有个项目,电池供电的传感器节点,我一开始用的Stop0,功耗始终压不下去,换成Stop1后电流直接降了一个数量级。

4.2 进入Stop模式的标准流程

进入Stop模式其实就几步,但每一步都有坑。我总结了一个清单,照着做基本不会翻车。

  1. 配置唤醒源(RTC或外部中断)——先配好再睡,不然醒不来。
  2. 设置低功耗稳压器模式——通过PWR寄存器选择Stop0或Stop1。
  3. 清除唤醒标志——防止一睡就醒。
  4. 执行WFI或WFE指令——真正睡下去。
  5. 唤醒后检查唤醒源——确认是谁叫醒的。

小技巧:进入Stop前,先把不用的外设时钟关了。我见过有人进了Stop模式电流还居高不下,查了半天发现是某个GPIO没配置成模拟输入,漏电了。

4.3 RTC唤醒:定时起床的闹钟

RTC唤醒是Stop模式最常用的唤醒方式。你想想看,一个传感器节点,每隔10秒醒来采一次数据,其他时间都在睡觉,这多省电。

配置RTC唤醒其实不复杂,CubeMX里勾选RTC唤醒事件,设置好唤醒周期就行。但要注意:RTC的时钟源必须保持运行。我建议用LSE(32.768 kHz晶振),精度高,功耗也低。

// 进入Stop模式并等待RTC唤醒
void EnterStopWithRTC(void)
{
    // 设置Stop1模式,低功耗稳压器
    HAL_PWREx_EnableLowPowerRunMode();
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    // 醒来后第一件事:重新配置系统时钟
    SystemClock_Config();
    
    // 检查唤醒源
    if(__HAL_PWR_GET_FLAG(PWR_FLAG_WUF) != RESET)
    {
        // RTC唤醒,处理定时任务
        ProcessPeriodicTask();
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WUF);
    }
}

这段代码看着简单,但我曾经踩过一个坑:唤醒后系统时钟默认使用HSI,如果外设需要更高的时钟频率,必须手动重新配置。不然串口打印出来的全是乱码。

4.4 外部中断唤醒:随时待命的门铃

外部中断唤醒适合需要「随叫随到」的场景。比如一个手持设备,用户按一下按键就要立刻响应。

配置外部中断唤醒,关键点在于:

  • GPIO要配置成外部中断模式,上升沿、下降沿或双边沿触发。
  • 中断优先级可以设高一点,毕竟是从Stop模式唤醒,响应要快。
  • 唤醒后先清中断标志,再处理业务逻辑。

注意:不是所有GPIO都能唤醒Stop模式!只有那些标记为「Wake-up」的引脚才行。具体看数据手册的引脚功能表。我曾经在F103上用了PB5做唤醒,结果死活醒不来,后来才发现PB5不支持唤醒功能。

4.5 SRAM保持策略:数据不能丢

Stop模式下SRAM默认全部保持,但有些系列允许你选择保持哪一块。比如STM32L4系列,可以把SRAM分成多个区,只保留需要的那部分,其他断电。

我一般这样规划:

  • 关键数据(传感器校准值、状态机变量)放在保持区。
  • 临时数据(计算缓冲区)放在可掉电区。
  • 用__attribute__((section(".sram_keep")))指定变量存放位置。
// 指定变量放在保持区
__attribute__((section(".sram_keep"))) uint32_t sensor_calibration[4];
__attribute__((section(".sram_keep"))) uint8_t device_state;

// 临时缓冲区,掉电无所谓
uint8_t temp_buffer[256];

嗯,这里要注意:链接脚本里要定义好保持区的起始地址和大小。我习惯在CubeMX生成的链接脚本基础上,手动加一段SRAM保持区的定义。

4.6 知识结构图:Stop模式全流程

Stop模式进入与退出全流程 开始:配置唤醒源 选择Stop0或Stop1模式 清除唤醒标志 执行WFI/WFE指令 唤醒:重新配置时钟 检查唤醒源,处理任务 SRAM保持区数据在Stop模式下不会丢失,唤醒后可直接使用

4.7 避坑指南:我踩过的那些坑

做低功耗项目,坑是真的多。我挑几个典型的说说:

  • 唤醒后外设不工作——多半是时钟没重新配置。Stop模式唤醒后默认用HSI,如果外设需要HSE或PLL,必须手动恢复。
  • 电流比预期高——检查GPIO状态。未使用的GPIO要配置成模拟输入,不然会有漏电路径。
  • RTC唤醒不准确——RTC时钟源精度问题。用LSE比LSI准得多,温度稳定性也好。
  • 多次唤醒后系统异常——可能是中断标志没清干净。每次唤醒后都清一遍PWR和RTC的标志位。

调试技巧:用示波器测一个GPIO的电平变化,进入Stop前拉低,唤醒后拉高,就能精确测量唤醒时间。我每次调低功耗都用这招,比看电流表直观多了。

4.8 实战建议:从CubeMX到代码

在CubeMX里配置Stop模式,我建议按这个顺序来:

  1. 先配RTC时钟源(LSE),使能RTC唤醒事件。
  2. 配好外部中断引脚,设置触发边沿。
  3. 在「Power Consumption Calculator」里选Stop模式,看预估电流。
  4. 生成代码后,在main.c里添加进入Stop的逻辑。
  5. 实测电流,用万用表或功耗分析仪验证。

嗯,最后说一句:Stop模式不是万能的。如果系统需要保持SRAM和寄存器,但不需要快速唤醒,可以考虑Standby模式。如果连SRAM都不需要保持,那就用Shutdown模式。选哪种模式,取决于你的项目需求——功耗、唤醒时间、数据保持,这三者得权衡着来。