STM32-RTC
一、RTC 简介
RTC 的全称是 Real Time Clock,即实时时钟。
从本质上看,RTC 是一个用于记录时间的计数器。它通常以秒为单位计数,可以在系统运行过程中提供当前时间,也可以在 MCU 掉电后依靠后备电源继续运行。
1. RTC 的作用
RTC 常用于这些场景:
-
记录当前时间。
-
生成定时唤醒事件。
-
给日志、故障记录、采样数据添加时间戳。
-
在系统掉电后继续保持时间。
-
配合后备寄存器保存少量关键数据。
2. RTC 的主要特性
STM32F1 RTC 具有以下特点:
| 特性 | 说明 |
|---|---|
| 本质 | 以固定频率计数的计数器 |
| 常用计数频率 | 1 Hz,也就是 1 秒计数一次 |
| 低功耗 | 可在低功耗场景下继续工作 |
| 掉电运行 | 配合 VBAT 和外部电池可在主电源掉电后继续计时 |
| 后备域 | RTC 和后备寄存器位于后备域中 |
| 独立复位 | RTC 核心不受普通系统复位影响 |
STM32F1 的 RTC 和后续系列差异较大。F1 的 RTC 更像是一个“秒计数器”,它没有直接提供年、月、日、时、分、秒等日历寄存器。实际使用时,通常通过软件算法把秒计数转换为日历时间。
二、F1 RTC 框图理解
STM32F1 RTC 的核心可以简化为:
RTC时钟源 -> RTC预分频器 -> 32位可编程计数器 -> 秒计数/闹钟/中断
F1 RTC 主要由以下部分组成:
| 模块 | 作用 |
|---|---|
| RTC 时钟源 | 为 RTC 提供基础时钟 |
| RTC 预分频器 | 将输入时钟分频到目标计数频率 |
| 32 位计数器 | 保存当前 RTC 计数值 |
| RTC 控制寄存器 | 配置中断、状态和访问控制 |
| APB1 接口 | CPU 通过 APB1 总线访问 RTC 寄存器 |
如果把 RTC 配置为 1 Hz 计数,那么 32 位计数器的值就可以理解为从某个起始时间开始累计的总秒数。
三、RTC 时钟源
STM32F1 RTC 可选择多个时钟源。
| 时钟源 | 说明 | 特点 |
|---|---|---|
| LSE | 外部 32.768 kHz 低速晶振 | 精度高,稳定,推荐使用 |
| LSI | 内部低速 RC,常按 40 kHz 估算 | 不需要外部晶振,但精度较低,有温漂 |
| HSE/128 | 外部高速晶振经过 128 分频 | 使用较少,依赖主时钟相关配置 |
实际项目中,RTC 通常优先选择 LSE。原因是 32.768 kHz 晶振专门用于实时时钟,容易分频得到 1 Hz,而且稳定性比 LSI 更好。
1. 为什么推荐 LSE
LSE 的典型频率为:
32768 Hz
这个频率可以方便地分频为 1 Hz:
32768 / 32768 = 1 Hz
也就是说,配置合适的预分频值后,RTC 计数器可以做到每秒加 1。
2. LSI 的问题
LSI 是芯片内部 RC 时钟,优点是不需要外部晶振,硬件更简单。
但它的缺点也很明显:
-
频率精度低。
-
容易受温度、电压影响。
-
长时间计时误差较大。
如果只是粗略计时或低成本方案,可以使用 LSI;如果需要较准确的时钟,应使用 LSE。
四、后备域与掉电保持
RTC 的一个重要特点是可以在主电源掉电后继续工作。
STM32F1 中,RTC 和后备寄存器通过一个供电开关供电:
VDD 有效 -> 使用 VDD 供电
VDD 失效 -> 使用 VBAT 引脚供电
只要 VBAT 上接有电池或备用电源,RTC 就可以在主系统掉电后继续计时。
1. 后备寄存器
后备寄存器可用于保存少量用户数据。它们和 RTC 同属于后备域,不会因为普通系统复位或电源复位而立即丢失。
常见用途:
-
标记 RTC 是否已经初始化。
-
保存设备运行状态。
-
保存少量配置参数。
-
保存异常复位标志。
2. 两类复位
F1 RTC 相关复位可以分为两类:
| 类型 | 说明 |
|---|---|
| APB1 接口复位 | 由系统复位影响 |
| RTC 核心复位 | 由后备域复位影响 |
这意味着普通系统复位后,RTC 的核心计数值仍可能保持不变。初始化 RTC 时通常需要通过后备寄存器判断是否已经配置过 RTC,避免每次复位都重新设置时间。
五、F1 RTC 关键寄存器
STM32F1 的 RTC 常见寄存器包括 RTC_PRL、RTC_CNT、RTC_DIV、RTC_CRL、RTC_ALR 等。
1. RTC_PRL 预分频装载寄存器
RTC_PRL 用于设置 RTC 的预分频值。
当使用 LSE = 32768 Hz,并希望 RTC 计数器每秒增加 1 次时,可将预分频装载值设置为:
RTC_PRL = 32768 - 1 = 32767
因为计数器通常按 PRL + 1 的方式分频。
2. RTC_CNT 计数器寄存器
RTC_CNT 是 32 位可编程计数器,用于保存当前 RTC 计数值。
如果 RTC 配置为 1 Hz,RTC_CNT 就可以保存从设定起点开始经过的总秒数。
例如:
RTC_CNT = 0 -> 起始时刻
RTC_CNT = 3600 -> 起始时刻后 1 小时
RTC_CNT = 86400 -> 起始时刻后 1 天
STM32F1 没有直接的年月日时分秒寄存器,因此需要把 RTC_CNT 与日历算法结合起来使用。
3. RTC_DIV 分频余数寄存器
RTC_DIV 用于读取当前预分频器的计数状态。
实际应用中,普通日历功能不一定需要直接使用 RTC_DIV,但它可以帮助理解 RTC 内部预分频和秒计数之间的关系。
4. RTC_CRL 控制低寄存器
RTC_CRL 是 F1 RTC 配置中非常关键的寄存器。
常见状态位包括:
| 位 | 作用 |
|---|---|
| RTOFF | RTC 操作完成标志 |
| CNF | 配置模式控制位 |
| RSF | 寄存器同步标志 |
| OWF | 溢出标志 |
| ALRF | 闹钟标志 |
| SECF | 秒标志 |
配置 RTC 寄存器前,需要等待 RTOFF = 1,然后置位 CNF 进入配置模式。配置完成后清除 CNF,再等待 RTOFF = 1 表示写操作完成。
5. RTC_ALR 闹钟寄存器
RTC_ALR 用于设置闹钟值。
当 RTC_CNT 的值与 RTC_ALR 匹配时,可以产生闹钟事件。F1 的闹钟机制同样基于计数器比较,而不是直接基于年月日时分秒字段。
六、F1 RTC 基本配置流程
STM32F1 RTC 的初始化通常按以下步骤进行。
1. 使能 PWR 和 BKP 时钟
访问 RTC 和后备寄存器前,需要先使能电源控制和备份域相关时钟:
使能 PWR 时钟
使能 BKP 时钟
HAL 宏通常包括:
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
2. 使能后备域访问
默认情况下,后备域受写保护。配置 RTC 前需要先打开访问权限:
HAL_PWR_EnableBkUpAccess();
这一步非常重要。如果没有使能后备域访问,后续 RTC 时钟源、预分频器等配置可能无法生效。
3. 配置 RTC 时钟源
常见做法是开启 LSE,并选择 LSE 作为 RTC 时钟源:
开启 LSE
等待 LSE 就绪
选择 RTC 时钟源为 LSE
使能 RTC 时钟
相关寄存器主要是 RCC_BDCR。
如果使用 HAL 库,常见函数包括:
HAL_RCC_OscConfig();
HAL_RCCEx_PeriphCLKConfig();
__HAL_RCC_RTC_ENABLE();
4. 进入 RTC 配置模式
进入配置模式前,需要等待上一次 RTC 写操作完成:
等待 RTOFF = 1
设置 CNF = 1
进入配置模式后,才能修改 RTC_PRL、RTC_CNT、RTC_ALR 等寄存器。
5. 设置预分频值和计数值
初始化时一般先设置 RTC 预分频值,使 RTC 计数频率变为 1 Hz。
使用 LSE 时:
RTC_PRL = 32767
计数值 RTC_CNT 可根据具体需求设置。如果是第一次初始化 RTC,需要把当前日期时间转换为总秒数后写入 RTC_CNT。
6. 退出配置模式
配置完成后:
清除 CNF
等待 RTOFF = 1
等待 RTOFF 的目的是确认 RTC 寄存器写入已经完成。
七、F1 RTC 与 HAL 库的关系
在 STM32F1 中,RTC 常常需要结合 HAL 库和寄存器操作来实现完整日历功能。
常见 HAL 函数或宏包括:
| 函数或宏 | 作用 |
|---|---|
HAL_RTC_Init() |
初始化 RTC 基本参数 |
HAL_RTC_MspInit() |
RTC 底层初始化回调 |
HAL_RCC_OscConfig() |
配置 LSE/LSI 等振荡器 |
HAL_RCCEx_PeriphCLKConfig() |
配置 RTC 外设时钟源 |
HAL_PWR_EnableBkUpAccess() |
使能后备域访问 |
__HAL_RCC_PWR_CLK_ENABLE() |
使能 PWR 时钟 |
__HAL_RCC_BKP_CLK_ENABLE() |
使能 BKP 时钟 |
__HAL_RCC_RTC_ENABLE() |
使能 RTC |
需要注意,F1 的日历设置和读取通常不是直接依赖 HAL_RTC_SetTime()、HAL_RTC_GetTime() 这类日期时间接口,而是通过操作 RTC 秒计数器,再用软件算法实现 rtc_set_time() 和 rtc_get_time()。
八、F1 时间设置与读取
STM32F1 RTC 的关键思想是:
设置时间:日历时间 -> 总秒数 -> 写入 RTC_CNT
读取时间:读取 RTC_CNT -> 总秒数 -> 转换为日历时间
1. 定义日历结构体
工程中通常定义一个日历结构体,用于保存当前时间信息:
typedef struct
{
uint16_t year;
uint8_t month;
uint8_t date;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint8_t week;
} rtc_calendar_t;
然后定义一个全局变量保存当前日历:
rtc_calendar_t calendar;
2. 判断闰年
计算日期时必须判断闰年。常用规则:
能被 4 整除且不能被 100 整除 -> 闰年
能被 400 整除 -> 闰年
其他情况 -> 平年
函数形式可以设计为:
uint8_t rtc_is_leap_year(uint16_t year);
3. 日历时间转总秒数
设置 RTC 时间时,需要把年月日时分秒转换为总秒数。
函数形式可以设计为:
long rtc_date2sec(uint16_t year,
uint8_t month,
uint8_t date,
uint8_t hour,
uint8_t min,
uint8_t sec);
转换思路:
-
累加从起始年份到目标年份前一年的总天数。
-
根据平年/闰年累加目标年份中目标月份之前的总天数。
-
加上当前月份已经过去的天数。
-
将天数转换为秒数。
-
加上时、分、秒对应的秒数。
4. 总秒数转日历时间
读取 RTC 时间时,需要把 RTC_CNT 中的总秒数转换为年月日时分秒。
函数形式可以设计为:
void rtc_get_time(void);
转换思路:
-
读取 RTC 当前总秒数。
-
计算已经过去的总天数和当天剩余秒数。
-
根据年天数逐年扣减,得到年份。
-
根据月份天数逐月扣减,得到月份和日期。
-
根据当天剩余秒数计算时、分、秒。
-
计算星期并更新全局日历结构体。
5. 计算星期
星期通常可以通过某个基准日期推算。
函数形式可以设计为:
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t date);
计算星期时要明确返回值定义,例如:
0 -> 星期日
1 -> 星期一
...
6 -> 星期六
如果项目中 UI 或串口输出使用 1 ~ 7 表示星期,也要统一转换,避免显示混乱。
九、RTC 初始化状态判断
RTC 的初始化不应该每次上电或复位都重新执行完整时间设置,否则会把已经保持的时间覆盖掉。
常见做法是使用后备寄存器保存一个标志值:
后备寄存器标志有效 -> RTC 已经初始化过,只同步寄存器并读取时间
后备寄存器标志无效 -> 第一次初始化 RTC,配置时钟源、预分频器并设置初始时间
这种方式可以区分普通系统复位和 RTC 第一次初始化。
需要重新初始化 RTC 的典型情况包括:
-
后备域被复位。
-
VBAT 电池断电。
-
LSE 启动失败后切换时钟源。
-
用户主动重新设置时间。
更多推荐

所有评论(0)