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,初始化结构体如下:

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()使能中断:

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秒唤醒一次:

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寄存器值变化,确保时间正常累计。