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模式其实就几步,但每一步都有坑。我总结了一个清单,照着做基本不会翻车。
- 配置唤醒源(RTC或外部中断)——先配好再睡,不然醒不来。
- 设置低功耗稳压器模式——通过PWR寄存器选择Stop0或Stop1。
- 清除唤醒标志——防止一睡就醒。
- 执行WFI或WFE指令——真正睡下去。
- 唤醒后检查唤醒源——确认是谁叫醒的。
小技巧:进入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模式全流程
4.7 避坑指南:我踩过的那些坑
做低功耗项目,坑是真的多。我挑几个典型的说说:
- 唤醒后外设不工作——多半是时钟没重新配置。Stop模式唤醒后默认用HSI,如果外设需要HSE或PLL,必须手动恢复。
- 电流比预期高——检查GPIO状态。未使用的GPIO要配置成模拟输入,不然会有漏电路径。
- RTC唤醒不准确——RTC时钟源精度问题。用LSE比LSI准得多,温度稳定性也好。
- 多次唤醒后系统异常——可能是中断标志没清干净。每次唤醒后都清一遍PWR和RTC的标志位。
调试技巧:用示波器测一个GPIO的电平变化,进入Stop前拉低,唤醒后拉高,就能精确测量唤醒时间。我每次调低功耗都用这招,比看电流表直观多了。
4.8 实战建议:从CubeMX到代码
在CubeMX里配置Stop模式,我建议按这个顺序来:
- 先配RTC时钟源(LSE),使能RTC唤醒事件。
- 配好外部中断引脚,设置触发边沿。
- 在「Power Consumption Calculator」里选Stop模式,看预估电流。
- 生成代码后,在
main.c里添加进入Stop的逻辑。 - 实测电流,用万用表或功耗分析仪验证。
嗯,最后说一句:Stop模式不是万能的。如果系统需要保持SRAM和寄存器,但不需要快速唤醒,可以考虑Standby模式。如果连SRAM都不需要保持,那就用Shutdown模式。选哪种模式,取决于你的项目需求——功耗、唤醒时间、数据保持,这三者得权衡着来。