stm32-RTC实时时钟详解(掉电自动走时,含代码)_stm32 rtc
STM32 的 RTC 是一个独立的定时器/计数器,专门设计用于在微控制器主电源关闭(进入低功耗模式)或系统复位时,持续提供精确的日历(日期和时间)信息。它的核心价值在于:
维持时间连续性: 即使系统掉电(由备份电池供电)或深度睡眠,时间也不会丢失或停止。
低功耗运行: 设计为在极低功耗(通常由 VBAT 引脚供电)下工作,非常适合电池供电的应用。
提供日历功能: 直接提供年、月、日、星期、时、分、秒信息,简化软件处理。
产生周期性中断和闹钟: 用于唤醒系统、执行周期性任务或在特定时间点触发事件。
STM32 RTC 的关键特性与工作原理
独立供电域 (备份域 - Backup Domain):
RTC 核心、备份寄存器都位于一个独立的电源域。通常由 VBAT 引脚供电(连接纽扣电池如 CR2032)。当主电源 VDD/VDD 失效时,VBAT 自动接管供电。当 VDD/VDD 有效时,即使 VBAT 存在,RTC 也优先由 VDD/VDD 通过内部电源开关供电。该域有自己的电源控制状态寄存器 (PWR_CR 和 PWR_CSR 的 DBP 位) 控制访问权限。
独立的低速时钟源:
RTC 需要独立的时钟源以保证在主时钟停止时仍能工作。可选源:LSE (Low-Speed External) 低速外部晶振: 最常用且推荐的选择。通常是 32.768 kHz 的晶振。该频率经过 15 次分频 (2^15 = 32768) 正好得到 1 Hz 信号,精度高,功耗低。LSI (Low-Speed Internal) 低速内部 RC 振荡器: 频率约为 32 kHz (具体值因型号和温度而异,精度较低)。当不需要高精度或外部无法放置晶振时使用。HSE (High-Speed External) 高速外部晶振: 通常经过一个可编程分频器 (PREDIV_S) 分频到 1 MHz 或更低。主要用于需要更高时间分辨率(亚秒)或 LSE/LSI 不可用的场景,但功耗较高。时钟源选择通过 RTC 控制寄存器 (RTC_CR) 配置。
日历寄存器组:
这是 RTC 最核心的部分。软件可以直接读取这些寄存器来获取当前的日期和时间,而不需要手动计算计数器值。
备份寄存器 (RTC_BKPxR):
位于备份域,同样由 VBAT 维持。
用户可以在其中存储任意数据(如校准值、配置标志、序列号、闹钟设置备份等),这些数据在系统掉电或复位后不会丢失。
访问前需要使能备份域访问 (PWR_CR.DBP = 1)。
闹钟功能:
RTC 通常提供至少 1 个(常见 2 个)闹钟寄存器 (RTC_ALRMAR, RTC_ALRMBR)。
每个闹钟可以独立配置为在指定的日期、星期、小时、分钟、秒(可屏蔽某些字段)匹配时触发。
触发时会产生一个闹钟中断 (RTC_ALARM) 和/或一个闹钟输出信号(可映射到引脚),用于唤醒系统或触发外部事件。
周期性唤醒单元:
一个独立的、灵活的唤醒定时器 (RTC_WUTR)。
可以配置为每隔一段设定的时间(从几秒到几个月)产生一个唤醒中断 (RTC_WKUP) 和/或唤醒事件,用于将系统从低功耗模式(如 Stop 或 Standby)中唤醒执行任务。
时钟源通常来自预分频后的 ck_spre 或 ck_apre (通常是 RTCCLK/16 或 RTCCLK/8)。
输出功能:
RTC 可以输出不同类型的时钟信号到特定引脚 (RTC_AF1 或 RTC_AF2),如:
校准时钟输出 (RTC_CALIB): 通常是 1 Hz 或 512 Hz (可配置)。
闹钟 A 或 B 输出 (ALARM_OUT)。
唤醒输出 (WKUP)。
用于驱动外部电路或作为时钟基准。
中断与事件:
支持多种中断源:闹钟 A/B、唤醒定时器、时间戳、入侵检测、寄存器同步完成等。
除了产生中断请求到 NVIC,还可以产生独立的事件信号,用于触发其他外设(如 DMA)而无需 CPU 干预。
实时时钟:掉电自动走时功能实现
使用stm32f103c8t6主控芯片,cubemx+keil的编程环境
cubemx配置
开启外部低速时钟
配置时钟源
在Time中找到RTC并且开启,初始时间自己随便设置一个
开启中断(如果只是实现掉电走时功能可以不开)
代码部分
cubemx为了解决2038问题,他将日期中的年月日的时间并没有记录到时间戳当中,时间戳(计数器)仅仅记录了当日的小时分钟和秒数,如果我们想要实现掉电走时的功能,我们就需要使用备份寄存器记住我们当前的日期数,由于配置的内部计数器会自动将秒换算成日期并且加到存储日期时间的寄存器中,所以即使长时间掉电,只要不超过计数器计数的最大值,就会自动转化秒为年、月、日、小时、分钟。
实现两个读写备份寄存器的函数
void read_bkup(RTC_HandleTypeDef *hrtc){if(HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR1)==0){ return; } //判断寄存器是否存储日期,相当于标志位else{hrtc->DateToUpdate.Year=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR2);hrtc->DateToUpdate.Month=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR3);hrtc->DateToUpdate.Date=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR4);} //依次将年月日从备份寄存器读出}void write_bkup(RTC_HandleTypeDef *hrtc){HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR1,1); //每次写入置标志位HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR2,hrtc->DateToUpdate.Year);HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR3,hrtc->DateToUpdate.Month);HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR4,hrtc->DateToUpdate.Date); //依次写入年月日到备份寄存器}
改写rtc.c
在cubemx自动生成的rtc.c中找到这一段
改写为下图:
每次初始化会调用Init函数,这个函数会消耗很多时间,所以一但寄存器有值,就不调用这个函数
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
这三句提前到这个位置初始化RTC
判断语句
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)==1){return;}
之后判断备份寄存器是否有数据,如果有则直接退出不进行以下的初始化,主循环直接从备份寄存器读取时间数据,否则正常进行初始化,这个初始化的数据就是cubemx中设置的时间数据
主循环
RTC_TimeTypeDef time = {0}; RTC_DateTypeDef date = {0}; while (1) {read_bkup(&hrtc); //读取备份寄存器的值HAL_RTC_GetTime(&hrtc,&time,RTC_FORMAT_BIN); //获取时分秒的值HAL_RTC_GetDate(&hrtc,&date,RTC_FORMAT_BIN); //获取日期write_bkup(&hrtc); //将新的日期写入备份寄存器printf(\"20%02d:%02d:%02d:%02d:%02d:%02d \\r\\n\",date.Year,date.Month,date.Date,time.Hours,time.Minutes,time.Seconds);//打印到串口HAL_Delay(1000); }
测试
中间断电,之后再连接,断电时间正常走时.
注意注意注意:背后电源VBAT要供电,没有备用电源,备份寄存器的值也会丢失。
一般的stm32f103c8t6最小系统板没有备用电源,VBT引脚需要接3.3v为其供电。