STM32 RTC实时时钟深入实践:闹钟、日历与低功耗唤醒
👁 5 阅读 · 2026-08-14 · STM32 RTC
STM32内置RTC实时时钟模块不仅提供精确的日历/时间跟踪,还支持可编程闹钟和周期性唤醒定时器,是低功耗应用和定时记录的核心。本文从原理出发,讲解RTC备份域、时钟源和寄存器映射,结合STM32CubeMX与HAL库演示日历配置、闹钟中断实现以及利用RTC唤醒定时器配合STOP模式实现超低功耗极简唤醒,并给出常见坑点与校准技巧。
## RTC原理简介
STM32的RTC(Real-Time Clock)是一组独立的定时器,封装在备份域内,由Vbat引脚供电时可保证系统主电源掉电后继续走时。核心功能包括:
- 日历/时间:秒、分、时、星期、日期、月、年,以BCD格式存储。
- 闹钟A/B:比较匹配日历字段触发中断或事件。
- 唤醒定时器:一个可编程的倒数计数器,用于周期性唤醒低功耗模式。
- 校准与数字指纹:支持RC振荡器频率校准和有损/无损校准。
访问RTC寄存器前必须使能PWR时钟并打开备份域访问(`HAL_PWR_EnableBkupAccess()`),否则任何写操作都会被忽略。
## 硬件设计要点
- 使用外部32.768kHz晶振时,需匹配负载电容(通常6~12pF),走线尽量短,远离高频信号。
- 若成本敏感或精度要求不高,可选用内部LSI(约32kHz),但温度漂移较大。
- VBAT引脚应连接纽扣电池或大容量电容,保证掉电后RTC和备份寄存器继续工作。
## STM32CubeMX配置步骤
1. 使能RCC中的LSE(或LSI)。
2. 选择RTC时钟源为LSE(推荐)并激活`Calendar`。
3. 设置初始日期时间(可在代码中覆盖)。
4. 若使用闹钟:在`Alarm A`中使能中断,设置匹配条件;若使用唤醒:在`WakeUp`中设置周期,使能中断。
5. 在NVIC中使能RTC全局中断(和闹钟/唤醒子中断,注意不同系列可能共用一个IRQ)。
## 软件实现:初始化与读写时间
HAL库提供封装好的API,初始化结构体如下:
```c
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
void MX_RTC_Init(void)
{
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127; // 异步分频
hrtc.Init.SynchPrediv = 255; // 同步分频,配合LSE得到1Hz
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK) Error_Handler();
// 设置时间:2025年3月18日 10:30:00 星期三
sTime.Hours = 10;
sTime.Minutes = 30;
sTime.Seconds = 0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) Error_Handler();
sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY;
sDate.Month = RTC_MONTH_MARCH;
sDate.Date = 18;
sDate.Year = 25; // 25代表2025年
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) Error_Handler();
}
```
读取时间时注意先读日期再读时间,避免跨越进位时数据不一致。HAL库在读取时会自动处理影子寄存器。
## 闹钟中断示例
设置闹钟A在每天10:30:00触发,使用`HAL_RTC_SetAlarm_IT()`使能中断:
```c
void RTC_Alarm_Config(void)
{
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.AlarmTime.Hours = 10;
sAlarm.AlarmTime.Minutes = 30;
sAlarm.AlarmTime.Seconds = 0;
sAlarm.AlarmTime.SubSeconds = 0;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY; // 不在乎日期,只匹配时分秒
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
sAlarm.Alarm = RTC_ALARM_A;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) Error_Handler();
}
// 在void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)中处理
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
// 关闭闹钟标志(HAL库中断处理中已自动清除),此处放用户逻辑
// 例如点亮LED或打印日志
}
```
如果希望闹钟只触发一次,可在回调中调用`HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A)`。
## 低功耗唤醒示例
利用RTC唤醒定时器,配合`__WFI`进入STOP模式。唤醒定时器时钟为LSE的2kHz(默认分频),可通过`RTC_WakeUpClock_CK_SPRE_16bits`等选项调整。以下示例配置为每10秒唤醒一次:
```c
void RTC_WakeUp_Config(void)
{
// 使能唤醒定时器,周期 = 20000 / 2000 = 10s
// 使用1Hz时钟(RTC_WAKEUPCLOCK_CK_SPRE_16BITS),直接写周期秒数
if (HAL_RTC_SetWakeUpTimer_IT(&hrtc, 10, RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)
Error_Handler();
}
void Enter_StopMode(void)
{
HAL_SuspendTick(); // 暂停SysTick,避免中断唤醒
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后恢复
HAL_ResumeTick();
SystemClock_Config(); // 重新配置系统时钟(HSE/PLL可能被关闭)
}
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
// 唤醒后的处理,如采集传感器、上报数据等
}
```
注意:使用HAL库时,`HAL_PWR_EnterSTOPMode`内部会执行WFI,用户需要在整个主循环中调度低功耗逻辑,并确保唤醒后重新配置外设时钟。
## 注意事项
- 备份域复位会清空RTC配置和备份寄存器,调试时如需复位不影响RTC,可设置`DBGMCU_APB1_FZ`中的DBG_RTC_Stop调试选项。
- 所有写入RTC的时间/日期数据在读取时返回BCD格式(`RTC_FORMAT_BCD`),建议统一使用`RTC_FORMAT_BIN`并手动转换。
- 闹钟匹配掩码可精确到秒甚至亚秒,但必须注意掩码与闹钟时间字段的配合,例如`RTC_ALARMMASK_DATEWEEKDAY`表示忽略日期和星期,只匹配时分秒。
- LSI温漂较大,若产品需长期计时,优先使用LSE并定期校准。HAL库提供`HAL_RTCEx_SetSmoothCalib()`进行数字校准。
- 进入STOP模式前,务必关闭不需要的外设中断,尤其要注意待机模式(Standby)会清空RAM,而RTC仍可照常工作。
- 唤醒定时器在待机模式下也能唤醒,但待机模式不执行中断回调,只能通过复位唤醒,需根据需求选择停止模式或待机模式。
综上,STM32 RTC是低功耗和实时任务不可或缺的模块。通过CubeMX与HAL库可以快速集成,但底层原理和备份域特性是排查问题的关键。建议在实际项目中先验证晶振起振情况,再利用调试器观察RTC寄存器值变化,确保时间正常累计。