2. RTC唤醒源配置:从停止模式中唤醒MCU

RTC闹钟唤醒,是低功耗项目里最常用的手段之一。说白了,就是让MCU睡大觉,到点了RTC一巴掌把它拍醒。我刚开始做低功耗时,总觉得配置RTC很麻烦,后来发现只要理清几个关键寄存器,其实就那么回事。

2.1 停止模式与唤醒路径

STM32L4的停止模式(Stop Mode),CPU停了,大部分外设也停了。但RTC还在跑——它用的是LSE或LSI时钟,功耗极低。RTC闹钟事件能直接连到EXIT线,再通过NVIC把CPU从停止模式里拉出来。

核心要点: RTC闹钟唤醒不需要CPU干预,硬件自动完成。你只管配好闹钟时间,然后调用WFI或WFE指令睡觉就行。

我画了一张流程图,帮你理清整个唤醒链路:

RTC闹钟唤醒链路 RTC闹钟事件 EXIT线18/19/20 NVIC中断控制器 CPU退出停止模式 关键寄存器 • RTC_CR: 闹钟使能位 • RTC_ALRMAR: 闹钟值 • EXTI_IMR: 中断屏蔽 注意事项 • 必须使能RTC时钟 • 闹钟匹配后自动清零 • 唤醒后需清除标志

2.2 RTC闹钟配置步骤

配置RTC闹钟,我习惯按这四步走。每一步都有坑,我一个个说。

  1. 使能RTC时钟并选择时钟源——LSE(32.768kHz)最准,LSI(约32kHz)省一颗晶振但精度差。我建议用LSE,除非你板子空间实在放不下晶振。
  2. 配置RTC预分频器——把时钟分到1Hz,这样闹钟寄存器直接写秒数就行。
  3. 设置闹钟值——RTC_ALRMAR寄存器,支持按秒、分、时、日匹配。最常用的是按秒匹配,实现周期性唤醒。
  4. 使能闹钟中断并连接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唤醒这部分就这些。配置不难,但细节多。你按我上面的步骤走,基本不会出问题。如果遇到奇怪的现象,先查时钟配置,再查中断使能,八成能解决。