目录
概述
STM32F103 的实时时钟 (RTC) 是一个独立的定时器模块,即使在主电源关闭时(通过 VBAT 电池供电)也能保持时间计数。以下是使用 HAL 库操作 RTC 的完整的使用方法。
1 RTC介绍
1.1 RTC 核心特性
独立供电:VBAT 引脚连接备份电池
32位可编程计数器:可计数秒数(约 136 年)
日历功能:自动计算年、月、日、星期、时、分、秒
闹钟功能:可配置多个闹钟
周期性唤醒:支持低功耗模式下的定时唤醒
入侵检测:防止非法篡改时间
1.2 硬件配置要点
VBAT 引脚:连接 3V 纽扣电池(CR2032)
时钟源选择:
LSE(外部晶振):高精度(±20ppm)
LSI(内部 RC):较低精度(±500ppm)
RTC在MCU中的结构:
2 使用STM32 Cube配置项目
2.1 配置参数
1) 配置时钟频率为:32768Hz
2) 配置基本参数
3) 使能中断
2.2 生成项目
1) 配置项目参数
2))点击GENERAGE CODE,生成项目文件
3 核心代码介绍
3.1 主要功能代码
1) 初始化RTC
// 启用备份域访问
HAL_PWR_EnableBkUpAccess();
// 启用后备寄存器时钟
__HAL_RCC_BKP_CLK_ENABLE();
// 配置 RTC 时钟源
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
// 选择 RTC 时钟源
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
Error_Handler();
}
// 初始化 RTC
RTC_HandleTypeDef hrtc = {0};
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = 127; // LSE 32.768kHz 分频
hrtc.Init.SynchPrediv = 255; // 最终时钟 = 32768/(128*256) = 1Hz
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK) {
Error_Handler();
}
2) 设置日期和时间
RTC_DateTypeDef sDate = {0};
RTC_TimeTypeDef sTime = {0};
// 设置日期:2023年10月15日,星期日
sDate.Year = 23; // 2023 - 2000
sDate.Month = RTC_MONTH_OCTOBER;
sDate.Date = 15;
sDate.WeekDay = RTC_WEEKDAY_SUNDAY;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
// 设置时间:14:30:00
sTime.Hours = 14;
sTime.Minutes = 30;
sTime.Seconds = 0;
sTime.SubSeconds = 0;
sTime.TimeFormat = RTC_HOURFORMAT12_AM; // 24小时制下忽略
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
3) 读取日期和时间
void Get_RTC_DateTime(RTC_DateTypeDef *date, RTC_TimeTypeDef *time)
{
// 必须先读时间再读日期(原子操作)
HAL_RTC_GetTime(&hrtc, time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, date, RTC_FORMAT_BIN);
// 处理年份(2000-2099)
date->Year += 2000;
}
4) 配置闹钟
// 配置闹钟A:每天14:45:30触发
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.Alarm = RTC_ALARM_A;
sAlarm.AlarmTime.Hours = 14;
sAlarm.AlarmTime.Minutes = 45;
sAlarm.AlarmTime.Seconds = 30;
sAlarm.AlarmMask = RTC_ALARMMASK_NONE; // 所有字段匹配
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = 0; // 忽略日期
sAlarm.Alarm = RTC_ALARMA_ENABLE;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
// 在stm32f1xx_it.c中实现中断处理
void RTC_Alarm_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler(&hrtc);
}
// 实现回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
// 闹钟触发时执行的操作
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); // 翻转LED
}
3.2 附加功能代码
1) 配置周期性唤醒
// 每60秒唤醒一次
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 59, RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK)
{
Error_Handler();
}
// 在stm32f1xx_it.c中实现中断处理
void RTC_WKUP_IRQHandler(void)
{
HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
}
// 实现回调函数
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
// 唤醒时执行的操作
printf("System woken up by RTC\n");
}
2) 备份寄存器操作
// 写入备份寄存器
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x1234);
// 读取备份寄存器
uint32_t data = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
3.3 注意事项
1) 备份域保护:
// 修改RTC配置前必须取消写保护
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_BKP_CLK_ENABLE();
// 配置完成后重新启用保护
HAL_PWR_DisableBkUpAccess();
2) 时钟源切换:
// 从LSE切换到LSI
HAL_RTC_DeInit(&hrtc);
RCC_PeriphCLKInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
HAL_RTC_Init(&hrtc);
3) 电池供电下的低功耗:
// 进入STOP模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后重新配置时钟
SystemClock_Config();
4 实践和应用
4.1 初始化检查
void set_rtc( void )
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
et_rtc_t _rtc;
build_rtc(&_rtc);
sTime.Hours = _rtc.hour;
sTime.Minutes = _rtc.minute;
sTime.Seconds = _rtc.second;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.Month = _rtc.month;
DateToUpdate.Date = _rtc.day;
DateToUpdate.Year = _rtc.year;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
}
void User_init_rtc( void )
{
// 修改RTC配置前必须取消写保护
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_BKP_CLK_ENABLE();
if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0xA5A1)
{
// 首次上电或电池耗尽,初始化RTC
set_rtc();
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xA5A1);
}
// 配置完成后重新启用保护
HAL_PWR_DisableBkUpAccess();
}
4.2 精度校准:
// 通过调整异步预分频器进行校准
uint32_t sync_prediv = 255;
uint32_t async_prediv = 127 + calibration_value; // ±30ppm 范围
HAL_RTC_Init(&hrtc);
4.3 时间戳功能:
// 记录事件时间戳
HAL_RTCEx_TimeStampIRQHandler(&hrtc);
// 获取时间戳
RTC_TimeTypeDef timestamp_time;
RTC_DateTypeDef timestamp_date;
HAL_RTCEx_GetTimeStamp(&hrtc, ×tamp_time, ×tamp_date, RTC_FORMAT_BIN);
4.4 Tamper 检测:
// 配置防篡改检测
RTC_TamperTypeDef sTamper = {0};
sTamper.Tamper = RTC_TAMPER_1;
sTamper.Trigger = RTC_TAMPERTRIGGER_RISINGEDGE;
sTamper.Filter = RTC_TAMPERFILTER_DISABLE;
sTamper.SamplingFrequency = RTC_TAMPERSAMPLINGFREQ_32768HZ;
sTamper.PreechargeDuration = RTC_TAMPERPRECHARGEDURATION_1RTCCLK;
sTamper.TamperPullUp = RTC_TAMPER_PULLUP_ENABLE;
sTamper.TimeStampOnTamperDetection = RTC_TIMESTAMPONTAMPERDETECTION_ENABLE;
HAL_RTCEx_SetTamper(&hrtc, &sTamper);
5 常见问题解决方案
1) RTC 不保持时间
检查 VBAT 电池连接(2.0-3.6V)
验证备份域访问是否启用
检查 LSE 是否正常起振
2) 日历读取错误
确保读取顺序:先时间后日期
检查时间格式(BIN/BCD)
验证 RTC 时钟配置(1Hz 输出)
3)闹钟不触发
确认闹钟中断已启用
检查 NVIC 中断优先级配置
验证闹钟掩码设置
4) 唤醒功能异常
检查唤醒定时器配置
验证低功耗模式配置
确保系统时钟在唤醒后正确初始化
6 解析编译时间的方法
void build_datatime( rtc_t *prtc )
{
u8 u8_buf[12];
memset( u8_buf, ' ', 12 );
memcpy( u8_buf, __TIME__, 9 );
prtc->second = (u8_buf[6] - 0x30)*10 + (u8_buf[7] - 0x30);
prtc->minute = (u8_buf[3] - 0x30)*10 + (u8_buf[4] - 0x30);
prtc->hour = (u8_buf[0] - 0x30)*10 + (u8_buf[1] - 0x30);
memcpy( u8_buf, __DATE__, 12 );
if(u8_buf[4] > 0x30){
prtc->day = (u8_buf[4] - 0x30)*10 + (u8_buf[5] - 0x30);
}
else{
prtc->day = (u8_buf[5] - 0x30);
}
if( strncmp( (char*)u8_buf, "Jan", 3) == 0 ) {
prtc->month = 1;
}
else if( strncmp( (char*)u8_buf, "Feb", 3) == 0 ) {
prtc->month = 2;
}
else if( strncmp( (char*)u8_buf, "Mar", 3) == 0 ) {
prtc->month = 3;
}
else if( strncmp( (char*)u8_buf, "Apr", 3) == 0 ) {
prtc->month = 4;
}
else if( strncmp( (char*)u8_buf, "May", 3) == 0 ) {
prtc->month = 5;
}
else if( strncmp( (char*)u8_buf, "Jun", 3) == 0 ) {
prtc->month = 6;
}
else if( strncmp( (char*)u8_buf, "Jul", 3) == 0 ) {
prtc->month = 7;
}
else if( strncmp( (char*)u8_buf, "Aug", 3) == 0 ) {
prtc->month = 8;
}
else if( strncmp( (char*)u8_buf, "Sep", 3) == 0 ) {
prtc->month = 9;
}
else if( strncmp( (char*)u8_buf, "Oct", 3) == 0 ) {
prtc->month = 10;
}
else if( strncmp( (char*)u8_buf, "Nov", 3) == 0 ) {
prtc->month = 11;
}
else if( strncmp( (char*)u8_buf, "Dec", 3) == 0 ) {
prtc->month = 12;
}
prtc->ex_year = (u8_buf[7] - 0x30)*1000 + (u8_buf[8] - 0x30)*100 + \
(u8_buf[9] - 0x30)*10 + (u8_buf[10] - 0x30);
prtc->year = (u8_buf[9] - 0x30)*10 + (u8_buf[10] - 0x30);
}