4. 低功耗模式基础(二):停止模式
停止模式,说白了就是让内核睡大觉,但保留SRAM和寄存器里的东西。跟睡眠模式不一样,停止模式下CPU时钟和大部分外设时钟都停了,但SRAM和寄存器内容不掉。我刚开始用STM32L4的时候,总觉得停止模式就是“深度睡眠”,其实没那么简单——它分了三个等级:Stop0、Stop1、Stop2。
4.1 Stop0 / Stop1 / Stop2 到底差在哪?
这三个模式的核心区别,在于唤醒速度和功耗之间的取舍。你想想看,唤醒越快,意味着内部稳压器必须保持更高的“待命”状态,自然就更耗电。
| 特性 | Stop0 | Stop1 | Stop2 |
|---|---|---|---|
| 稳压器模式 | MR(主稳压器) | LPR(低功耗稳压器) | LPR(低功耗稳压器) |
| 唤醒时间 | 约 5 µs | 约 10 µs | 约 15 µs |
| 典型功耗 | ~ 100 µA | ~ 10 µA | ~ 3 µA |
| SRAM保持 | 全部保持 | 全部保持 | 可配置(部分掉电) |
| 适用场景 | 需要快速响应 | 平衡模式 | 超低功耗待机 |
我个人习惯是:如果项目对响应速度要求高,比如按键唤醒后要立刻显示,我会选Stop0。如果是电池供电的传感器节点,几秒才上报一次数据,那Stop2是首选。
4.2 SRAM保持策略——别让你的数据“蒸发”
进入停止模式前,你得想清楚:哪些数据必须活着?哪些可以丢?
STM32L4的SRAM分成了多个区域。在Stop2模式下,你可以通过配置SRAM2的保持位,让关键数据留在SRAM2里,而SRAM1可以完全掉电。我在项目中遇到过一个问题:一个温湿度采集节点,每次唤醒后都要重新初始化传感器,但校准参数存在SRAM1里,结果每次唤醒都丢了——后来我把校准参数挪到SRAM2,问题就解决了。
关键配置:
PWR_CR1寄存器中的MRUDS和LPUDS位控制稳压器模式PWR_CR2寄存器中的SRAM2PDS位控制SRAM2是否掉电- 使用
HAL_PWR_EnterSTOPMode()时传入对应参数
// 进入 Stop2 模式,保持 SRAM2
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 或者更精细的控制——手动配置
PWR->CR1 |= PWR_CR1_LPUDS; // 低功耗稳压器
PWR->CR1 |= PWR_CR1_FPDS; // 闪存掉电
PWR->CR2 |= PWR_CR2_SRAM2PDS; // SRAM2 保持,SRAM1 可掉电
注意:如果你在Stop2模式下把SRAM2也掉电了,那唤醒后所有数据都会丢失。我曾经调试一个设备,发现每次唤醒后系统时间都归零了,查了半天才发现是SRAM2配置错了——嗯,从那以后我每次都会检查SRAM保持配置。
4.3 RTC唤醒——最常用的定时唤醒方式
RTC(实时时钟)在停止模式下依然可以运行。它靠的是LSE(32.768 kHz)或LSI(约32 kHz)时钟,功耗极低。你可以设置RTC闹钟或周期性唤醒定时器,让系统在指定时间醒来。
我个人习惯用RTC的周期性唤醒定时器(WUT),因为它不需要设置绝对时间,只需要配置唤醒间隔。比如一个温度采集节点,每10秒采集一次,用WUT就非常方便。
// 配置 RTC 周期性唤醒,每 10 秒唤醒一次
RTC_WakeUpCmd(DISABLE);
RTC_SetWakeUpCounter(10 * 32768 / 16); // 假设 LSE = 32.768 kHz,WUT 时钟分频 16
RTC_WakeUpCmd(ENABLE);
// 进入停止模式,等待 RTC 唤醒
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
小技巧:RTC唤醒中断的优先级建议设得比普通外设中断低一些,避免在唤醒瞬间被其他中断“抢跑”,导致系统状态混乱。
4.4 外部中断唤醒——让外部事件叫醒系统
除了定时唤醒,外部中断也是常用的唤醒方式。STM32L4的所有GPIO都可以配置为外部中断源。你只需要把对应的引脚配置为中断模式,然后在停止模式下,只要该引脚出现指定的电平变化,系统就会醒来。
我做过一个手持设备,用户按一下按键就唤醒屏幕。按键接在PA0上,配置为下降沿触发。进入停止模式后,按下按键,系统在几微秒内就恢复了运行。
// 配置 PA0 为外部中断,下降沿触发
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 使能中断
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 进入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
避坑指南:我曾经犯过一个错误——在进入停止模式前没有把外部中断的触发条件配置好,结果系统一进入停止模式就被“假唤醒”,功耗根本降不下来。后来我加了一个延时去抖,并且在中断服务函数里清除了挂起标志位,问题才解决。
4.5 实测停止模式功耗——数据说话
理论归理论,实际测一下才知道差距。我用STM32L476RG Nucleo板,配合一个精密电阻(10Ω)串联在电源路径上,用示波器测电压降,换算成电流。下面是我实测的数据:
| 模式 | 理论值 | 实测值 | 备注 |
|---|---|---|---|
| Stop0 | ~ 100 µA | 112 µA | 所有外设时钟关闭 |
| Stop1 | ~ 10 µA | 9.8 µA | RTC 运行,LSE 开启 |
| Stop2 | ~ 3 µA | 2.7 µA | SRAM2 保持,SRAM1 掉电 |
你会发现,实测值和理论值基本吻合。但要注意,如果板子上有额外的上拉电阻、LED指示灯或者调试接口,功耗会明显偏高。我建议你在做低功耗设计时,先把所有不用的外设都关掉,包括GPIO的上拉/下拉。
实测要点:
- 使用精密电阻(1%精度)串联在电源路径上
- 示波器设置为DC耦合,垂直分辨率调到mV级
- 测量前先让系统稳定运行几秒钟,避免瞬态电流干扰
- 多次测量取平均值,减少误差
4.6 停止模式的核心逻辑
下面这张图帮你理清停止模式的选择逻辑:
选择停止模式时,我建议你先评估两个指标:唤醒时间和平均功耗。如果系统大部分时间都在睡觉,偶尔醒来干点活,那Stop2是最佳选择。如果系统需要频繁响应外部事件,比如按键、通信中断,那Stop0或Stop1更合适。
个人经验:我做过一个智能门锁项目,平时用Stop2模式,功耗只有2.8 µA。用户刷卡时,通过外部中断唤醒,系统在15 µs内完成身份验证并开锁。如果当时选了Stop0,功耗会高出几十倍,电池寿命从两年缩水到几个月。
好了,停止模式的内容就这些。记住:选对模式,配好SRAM保持策略,再结合RTC或外部中断唤醒,你的低功耗设计就成功了一大半。