2. RTC唤醒源配置:从停止模式中唤醒MCU
RTC闹钟唤醒,是低功耗项目里最常用的手段之一。说白了,就是让MCU睡大觉,到点了RTC一巴掌把它拍醒。我刚开始做低功耗时,总觉得配置RTC很麻烦,后来发现只要理清几个关键寄存器,其实就那么回事。
2.1 停止模式与唤醒路径
STM32L4的停止模式(Stop Mode),CPU停了,大部分外设也停了。但RTC还在跑——它用的是LSE或LSI时钟,功耗极低。RTC闹钟事件能直接连到EXIT线,再通过NVIC把CPU从停止模式里拉出来。
核心要点: RTC闹钟唤醒不需要CPU干预,硬件自动完成。你只管配好闹钟时间,然后调用WFI或WFE指令睡觉就行。
我画了一张流程图,帮你理清整个唤醒链路:
2.2 RTC闹钟配置步骤
配置RTC闹钟,我习惯按这四步走。每一步都有坑,我一个个说。
- 使能RTC时钟并选择时钟源——LSE(32.768kHz)最准,LSI(约32kHz)省一颗晶振但精度差。我建议用LSE,除非你板子空间实在放不下晶振。
- 配置RTC预分频器——把时钟分到1Hz,这样闹钟寄存器直接写秒数就行。
- 设置闹钟值——RTC_ALRMAR寄存器,支持按秒、分、时、日匹配。最常用的是按秒匹配,实现周期性唤醒。
- 使能闹钟中断并连接EXIT——RTC_CR里的ALRAIE位,加上EXIT配置,缺一不可。
我的习惯: 配置完闹钟后,先读一遍RTC_ISR里的ALRAF标志,确认闹钟确实被设置了。这一步能帮你快速定位问题,省得后面查半天。
2.3 代码实现:周期性唤醒
下面这段代码,是我从项目里扒出来的。实现的功能很简单:每10秒唤醒一次,执行完任务继续睡。
// RTC闹钟初始化 - 每10秒唤醒一次
void RTC_Alarm_Init(void)
{
// 1. 使能RTC时钟
__HAL_RCC_RTC_ENABLE();
// 2. 选择LSE作为时钟源
HAL_RTC_MspInit(&hrtc);
// 3. 配置RTC预分频器,得到1Hz
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127; // 异步分频
hrtc.Init.SynchPrediv = 255; // 同步分频
HAL_RTC_Init(&hrtc);
// 4. 设置闹钟:当前时间 + 10秒
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.AlarmTime.Seconds = 10; // 10秒后触发
sAlarm.AlarmMask = RTC_ALARMMASK_NONE; // 精确匹配
sAlarm.Alarm = RTC_ALARM_A;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
// 闹钟中断回调
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
// 清除闹钟标志
__HAL_RTC_ALARM_CLEAR_FLAG(hrtc, RTC_FLAG_ALRAF);
// 重新设置闹钟,实现周期性
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.AlarmTime.Seconds = 10;
sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
sAlarm.Alarm = RTC_ALARM_A;
HAL_RTC_SetAlarm_IT(hrtc, &sAlarm, RTC_FORMAT_BIN);
// 执行你的任务
Do_Something();
}
// 主循环 - 进入停止模式
int main(void)
{
HAL_Init();
SystemClock_Config();
RTC_Alarm_Init();
while(1)
{
// 进入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后重新配置系统时钟
SystemClock_Config();
// 继续执行...
}
}
我曾经踩过的坑: 唤醒后忘记重新配置系统时钟!停止模式下HSE和PLL都停了,唤醒后默认用MSI,频率只有几百kHz。如果你不重新配时钟,程序跑得比蜗牛还慢,串口打印都乱码。切记:唤醒后第一件事就是SystemClock_Config()。
2.4 关键参数对比
配置RTC时,有几个参数容易搞混。我整理了一张表,你对照着看:
| 参数 | 作用 | 典型值 | 注意事项 |
|---|---|---|---|
| AsynchPrediv | 异步预分频 | 127 | 与同步分频配合,得到1Hz |
| SynchPrediv | 同步预分频 | 255 | 两者乘积 + 1 = 时钟频率 |
| AlarmMask | 闹钟匹配掩码 | RTC_ALARMMASK_NONE | 设为NONE则精确匹配 |
| Alarm | 闹钟通道 | RTC_ALARM_A | L4有两个闹钟,A和B |
2.5 调试技巧:如何确认唤醒成功
配置完了,怎么知道MCU真的被唤醒了?我分享三个方法:
- GPIO翻转法: 在中断回调里翻转一个GPIO,用示波器看波形。周期对不对,一眼就知道。
- 电流测量法: 停止模式电流几个μA,唤醒后几mA。用万用表看电流跳变,最直观。
- 串口打印法: 唤醒后打印一条消息。注意:串口初始化必须在唤醒后重新做,否则打印不出来。
我的经验: 调试阶段我习惯用GPIO翻转法。在中断入口和出口各翻转一次,能同时看到唤醒是否及时、中断处理是否耗时。有一次我发现中断处理花了2ms,就是因为忘了关RTC写保护,每次写寄存器都要等同步。
2.6 避坑指南
最后,我把这些年遇到的RTC唤醒问题总结一下:
- 闹钟不触发: 检查RTC_CR里的ALRAIE位有没有使能。我犯过这个错,配了半天发现中断没开。
- 唤醒后死机: 大概率是中断优先级没配好。RTC中断优先级不能太高,否则会打断系统时钟重新配置。
- 周期性不准: 检查闹钟重设逻辑。如果你在中断里重新设置闹钟,要确保设置的是“当前时间+10秒”,而不是固定值。
- 功耗偏高: 停止模式下如果RTC还在用HSE,功耗会多几百μA。务必切到LSE或LSI。
嗯,RTC唤醒这部分就这些。配置不难,但细节多。你按我上面的步骤走,基本不会出问题。如果遇到奇怪的现象,先查时钟配置,再查中断使能,八成能解决。