STM32 RTC 实时时钟详解:从概念到预分频器
1. RTC 概念与宏观理解
1.1 什么是 RTC
RTC(Real-Time Clock,实时时钟):
独立于 CPU 运行、带独立供电域的"秒计数器"。
主电源断电后,只要 VBAT 引脚上的纽扣电池还有电,它就继续走时;CPU 复位、死机、重新上电,它都不受影响。
生活类比:
手腕上的手表。
你的大脑(CPU)可以睡觉(低功耗)、昏迷(复位)、心跳停止(掉电),但手表的秒针永远自己在走。RTC 就是这颗芯片里的"手表"。
1.2 为什么需要 RTC
需求 用定时器行不行 用 RTC 行不行 掉电后继续走时 不行,掉电清零 行,VBAT 供电 电子钟/日历 要软件维护,麻烦 秒计数器天然适合 低功耗定时唤醒 待机模式下定时器不工作 RTC 闹钟可唤醒待机 时间戳/日志 要记录开机累计时间 秒数即时间戳 一句话:定时器管"短时间精确",RTC 管"长时间不断"。
1.3 宏观理解:RTC 的三个身份
- 一个走时器:每秒计一次数,从不断电;
- 一个闹钟:计数到设定值就触发中断/唤醒;
- 一个电池供电的存储器伴侣:和 BKP 寄存器共享备份域,掉电不丢。
1.4 备份域:RTC 不掉电的物理基础
VDD(主电源 3.3V)────┐ ├──► 备份域(RTC + BKP + 复位逻辑) VBAT(纽扣电池 3V)───┘
- 主电源正常 → VDD 供电;
- 主电源断电 → 自动切 VBAT 纽扣电池;
- 备份域内容:RTC 外设、42 个 BKP 数据寄存器、备份域复位逻辑。
两种复位必须分清:
复位类型 触发方式 影响 系统复位 复位键/看门狗/上电 只复位 CPU 和外设,不动备份域,RTC 继续走 备份域复位 BDRST 置 1 / VDD+VBAT 都断电 RTC 清零、BKP 清空、配置丢失 工程惯例:
用 BKP_DR1 存"魔法数"(如 0xA5A5)判断备份域是否被清过------魔法数在,说明 RTC 已在走,跳过配置;不在,说明首次上电,完整配置一次。
2. RTC 框图原理与时钟树
2.1 RTC 内部框图(简化)
cpp┌──────────────────────────── 备份域 ───────────────────────────┐ │ │ LSE 32.768kHz ──► │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ (或 LSI/HSE/128)─► │ │ 预分频器 │──► │ 1Hz 秒脉冲 │──►│ CNT 秒计数 │──► 秒中断 SECF │ │ │ PRL+1 │ │ │ │ 32位,每秒+1 │ │ │ └──────────┘ └──────────┘ └─────┬─────┘ │ │ ▲ │ │ │ ┌──────────┐ ▼ │ │ │ DIV 余数 │ ┌───────────┐ CNT==ALR │ │ │ 20位,-1 │ │ 闹钟比较器 │──► 闹钟中断 ALRF │ │ └──────────┘ └───────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘信号链一句话**:**
外部低速时钟进来 → 预分频器把它变成 1Hz → 每 1 秒 CNT 加 1 → CNT 与 ALR 比较产生闹钟 → 秒/闹钟/溢出三种中断。
2.2 时钟树视角:RTC 的时钟从哪来
RTC 的时钟不走主 PLL 链路,它有三种独立来源(后面 14.3 细讲):
cpp┌─ LSE:OSC32_IN/OUT 外部晶振 32.768kHz RTC 时钟源 RTCSEL ──┼─ LSI:内部 RC 40kHz └─ HSE/128:主晶振 8MHz 分频 ≈ 62.5kHz │ ▼ RCC_BDCR.RTCEN 使能后 │ ▼ 预分频器 → 秒脉冲 → CNT2.3 RTC 的"总线身份"(重要理解)
F1 的 RTC 有个容易混淆的点------它有两个时钟:
时钟 用途 来源 计数时钟 驱动秒计数器走时 LSE/LSI/HSE/128(RCC_RTCCLK 使能) APB 接口时钟 CPU 读写 RTC 寄存器用 APB1(RCC_APB1Periph_PWR/BKP)
- RTC 计数不依赖 APB1------所以 APB1 关了它照样走(低功耗关键);
- CPU 要读写 RTC 寄存器,需要 APB1 的 PWR/BKP 时钟 + 备份域访问使能。
这就是为什么低功耗模式下定时器全停了,RTC 还在走------它的"发动机"是 LSE 晶振,不是系统时钟。
3. RTC 确认 LSE 地位
3.1 三大时钟源全对比
对比项 LSE(外部低速晶振) LSI(内部低速 RC) HSE/128 频率 32.768kHz 40kHz 约 62.5kHz 精度 高(晶振,约 20ppm) 低(RC,温漂大) 依赖主晶振 成本 多一颗晶振 零成本 零成本 起振时间 慢(约 1~2 秒) 快 快 掉电后 VBAT 供电,继续振荡 主电源断电就停 主电源断电就停 适合 电子钟/日历(首选) 精度要求低的唤醒 基本不用 3.2 为什么 LSE 的地位不可动摇
**理由一:**掉电走时。
RTC 最大的卖点就是掉电不断。只有 LSE 是外部晶振,由 VBAT 单独供电,主电源断了它照样振荡。LSI 和 HSE/128 都依赖主电源,断电即停------用它们做"掉电走时"就失去了 RTC 的意义。
理由二:精度。
32.768kHz 晶振精度约 20ppm(一天误差约 1.7 秒),LSI 内部 RC 温漂可能到几十 ppm 甚至上百 ppm,且频率随温度明显变化,做电子钟会越走越偏。
理由三:2 的整数次幂。
32768 = 2^15经过 15 级二进制分频器(÷2 连除 15 次),恰好得到精确的 1Hz。用纯硬件计数器就能分频,不需要任何补偿。
结论:
做"真正的实时时钟",LSE 是唯一正确选择
3.3 如何配置 LSE(BDCR 寄存器)
LSE 的控制位全在 RCC_BDCR(备份域控制寄存器):
cpp位 15 BDRST 备份域软件复位 位 9 LSEON LSE 使能(1=开) 位 8 LSERDY LSE 就绪标志(1=晶振起振稳定) 位 3:2 RTCSEL RTC 时钟源:00=无 01=LSE 10=LSI 11=HSE/128 位 0 RTCEN RTC 时钟使能标准库配置:
cpp// 1. 开 PWR/BKP 时钟,允许访问备份域(写 BDCR 的前提) RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); // 2. 使能 LSE RCC_LSEConfig(RCC_LSE_ON); // 3. 等 LSE 就绪(起振慢,要有超时保护!) while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 4. 选择 RTC 时钟源 = LSE,并使能 RTC 时钟 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE);3.4 LSE 的坑:起振慢
LSE 是低速晶振,起振要 1~2 秒。
配置时如果死等
LSERDY,看似没问题,但在"上电即配置"的场景,这 1~2 秒的等待可能影响启动速度;更糟的是如果晶振没焊好/没起振,会死循环卡死在等待里。工程解法:加超时计数,等不到就跳到错误处理或改用 LSI 兜底。
uint32_t timeout = 0xFFFFFF; while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) { if (--timeout == 0) break; // 超时退出,防止死等 }
4. RTC 和 NVIC 联动
4.1 RTC 的三种中断
中断 标志位 使能位 触发条件 典型应用 秒中断 SECF SECIE 每 1 秒 电子钟刷新、心跳 闹钟中断 ALRF ALRIE CNT == ALR 定时任务、待机唤醒 溢出中断 OWF OWIE CNT 数满 2^32 回 0 几乎不用 注意:标志位在
RTC_CRL,使能位在RTC_CRH------两个寄存器都要配。
RTC_CRH:位3 OWIE | 位2 ALRIE | 位1 SECIE RTC_CRL:位5 RTOFF | 位4 CNF | 位3 RSF | 位2 OWF | 位1 ALRF | 位0 SECF4.2 中断联动全流程
cppRTC 事件发生(秒到/闹钟到) │ ▼ 硬件置位 SECF/ALRF(CRL 中) │ ▼ 硬件检查对应使能位 SECIE/ALRIE(CRH 中) │ ├── 使能了 → 向 NVIC 发出 RTC_IRQn 中断请求 │ │ │ ▼ │ NVIC 按优先级响应 │ │ │ ▼ │ CPU 跳 RTC_IRQHandler() │ │ │ ├── ① 读 CRH 确认使能、读 CRL 确认标志 │ ├── ② 清标志(写 0) │ └── ③ 读 CNT,刷新显示/触发业务 │ └── 没使能 → 标志挂起,不打扰 CPU(可轮询读)4.3 软件配置(两步)
第一步:RTC 内部使能中断
cppRTC_WaitForLastTask(); // 等 RTOFF=1 RTC_ITConfig(RTC_IT_SEC, ENABLE); // 使能秒中断 RTC_WaitForLastTask();第二步:NVIC 使能 RTC 全局中断
cppNVIC_InitTypeDef nvic; nvic.NVIC_IRQChannel = RTC_IRQn; // RTC 中断号 nvic.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 nvic.NVIC_IRQChannelSubPriority = 0; // 子优先级 nvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic);4.4 中断服务函数(秒中断做电子钟)
void RTC_IRQHandler(void) { // ① 确认:是秒中断(读 CRH 的 SECIE + CRL 的 SECF) if (RTC_GetITStatus(RTC_IT_SEC) != RESET) { // ② 清标志(写 0 清除 SECF) RTC_ClearITPendingBit(RTC_IT_SEC); // ③ 读秒数并显示 uint32_t sec = RTC_GetCounter(); // 换算时分秒... } }为什么中断里要先"确认再清"?
RTC 的三个中断共用同一个
RTC_IRQHandler,不确认就会"一个中断来了,三个处理都跑一遍",还可能误清别的标志。4.5 闹钟唤醒待机(低功耗组合拳)
主循环:设 ALR = CNT + 30 → PWR_EnterSTANDBYMode() 进入待机(uA 级) → 30 秒后 RTC 闹钟事件 → 唤醒,系统从 main 重新执行(备份域保留)关键认知:
待机唤醒 = 系统复位 + 重跑 main,不是从原处继续。
代码靠 BKP 魔法数区分"冷启动"和"待机唤醒"。
5. RTC 预分频器原理
5.1 预分频器在 RTC 中的位置
LSE 32.768kHz ──► 预分频器 ──► 1Hz 秒脉冲 ──► CNT +1 PRL 决定 ÷ 多少预分频器把 32768Hz 的晶振频率"数着除",降到 1Hz------这是整个 RTC 走时的发动机减速器。
5.2 公式推导
f_sec = CK_RTC / (PRL + 1)代入:CK_RTC = 32768Hz,PRL = 32767
f_sec = 32768 / (32767 + 1) = 32768 / 32768 = 1 Hz ← 每秒一个脉冲为什么是 PRL+1 而不是 PRL?
预分频器从 PRL 数到 0,一共数了 PRL+1 个时钟周期才产生一个输出脉冲。
所以分频因子 = PRL+1。
5.3 预分频寄存器细节
RTC_PRLH[3:0] + RTC_PRLL[15:0] = 20 位预分频值 复位值 = 0x8000 = 32768
- 配 32767 → 精确 1Hz;
- 复位值 32768 → 32768/(32768+1) ≈ 0.99997Hz------不配置也接近 1 秒,但做电子钟必须配成 32767,否则一天会差约 2.6 秒(0.99997Hz × 86400s ≈ 86397 拍,少 3 拍/天)。
5.4 分频时序推演
LSE 时钟(32768Hz): __|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__ 1 2 3 4 5 6 7 8 ... 32767 32768(即PRL+1) 预分频器计数: 32767 → 32766 → ... → 1 → 0 →(重载)32767 │ ▼ 秒脉冲: ________________________________|‾‾‾‾ ← 每 32768 拍出 1 个5.5 DIV 余数寄存器:预分频器的"进度条"
RTC_DIVH[3:0] + RTC_DIVL[15:0] = 20 位只读余数
备份域复位后,DIV 自动装载 PRL 的值;
每个 LSE 时钟沿(1/32768s)DIV 减 1;
DIV 减到 0 的那一拍:① 产生 1Hz 秒脉冲(CNT+1);② DIV 重新装载 PRL。
DIV: 32767 → 32766 → ... → 1 → 0 →(重载)32767 → ...
↑ 每 1/32768 秒减一次,数完一整圈正好 1 秒DIV 的价值:它告诉你"当前这一秒已经走了几分之几",分辨率高达 1/32768 秒 ≈ 30.5us。用途:
- 亚秒时间戳:日志精确到 30us 级;
- 晶振校准:测量实际走一圈的耗时,推算频率偏差。
5.6 预分频器与"秒计数"的整体配合
LSE 32768Hz ──每拍──► DIV-1 │ 减到 0 ▼ 秒脉冲 ──► CNT+1(32 位秒计数器) │ ▼ CNT==ALR → 闹钟一条链全部讲清:
晶振快慢由 DIV 消化,1 秒的节奏由 PRL 决定,累计时间由 CNT 记录,闹钟比较 CNT 与 ALR。
六、寄存器全解析(STM32F103)
RTC 的寄存器不多,一共 6 组。
关键点:
RTC 寄存器在备份域里,只能通过备份域访问使能(PWR_BackupAccessCmd (ENABLE))之后写。
6.1 RTC_CRH(控制寄存器高,偏移 0x00)
位 3 2 1 0 ┌──────┬──────┬──────┬──────┐ │ OWIE │ALRIE │SECIE │ - │ └──────┴──────┴──────┴──────┘
位 名称 作用 位 3 OWIE 溢出中断使能(CNT 数满 2^32 溢出时) 位 2 ALRIE 闹钟中断使能(CNT==ALR 时) 位 1 SECIE 秒中断使能(每秒进一次中断,做电子钟就靠它) 6.2 RTC_CRL(控制寄存器低,偏移 0x04)------ 状态和配置门
位 5 4 3 2 1 0 ┌──────┬──────┬──────┬──────┬──────┬──────┐ │RTOFF │ CNF │ RSF │ OWF │ ALRF │ SECF │ └──────┴──────┴──────┴──────┴──────┴──────┘
位 名称 作用 读写 位 5 RTOFF RTC 操作完成标志,写操作进行中自动清 0 只读 位 4 CNF 配置标志,写配置前置 1,写完后清 0 读写 位 3 RSF 寄存器同步标志,上电后第一次读时间前必须等它为 1 软件清 位 2 OWF 溢出中断标志 写 0 清 位 1 ALRF 闹钟中断标志 写 0 清 位 0 SECF 秒中断标志 写 0 清 重要:
中断标志(OWF/ALRF/SECF)在 CRL 里,使能位(OWIE/ALRIE/SECIE)在 CRH 里。进中断后要读 CRH 确认使能 + 写 0 清 CRL 标志。
6.3 RTC_PRLH / RTC_PRLL(预分频装载寄存器,偏移 0x08/0x0C)
PRLH[3:0] + PRLL[15:0] = 20 位预分频值,复位值 0x8000 = 32768
- 分频因子 = PRL + 1;
- 配 32767 → 精确 1Hz;
- 复位值是 32768(≈0.99997Hz),所以不配置时它也接近 1 秒,但要做电子钟必须配成 32767。
6.4 RTC_DIVH / RTC_DIVL(余数寄存器,偏移 0x10/0x14)------ 只读
20 位只读余数,复位后自动装载 PRL 值,每时钟沿减 1,减到 0 重载读它可获得亚秒精度(1/32768s ≈ 30.5us)。
6.5 RTC_CNTH / RTC_CNTL(秒计数器,偏移 0x18/0x1C)
32 位秒计数器,每秒 +1,复位值 0读的一致性:
硬件规定 ------先读 CNTL 会把 CNTH 锁存,所以正确读法是先读 CNTL 再读 CNTH(标准库
RTC_GetCounter()内部就是这样保证 32 位读数一致的)。6.6 RTC_ALRH / RTC_ALRL(闹钟寄存器,偏移 0x20/0x24)
32 位闹钟值,复位值 0xFFFFFFFF(全 1 = 永不触发) CNT == ALR 时:置 ALRF →(若 ALRIE=1)产生闹钟中断闹钟本质是秒级比较器:
闹钟值 = 当前秒数 + 想要的秒数。
比如现在 CNT=100,想 30 秒后闹,就设 ALR=130。
6.7 RCC_BDCR(备份域控制寄存器)------RTC 的 "总开关"
RTC 时钟的使能、时钟源选择都在这,它本身也在备份域里:
位 15 BDRST 备份域软件复位(置1清空整个备份域) 位 9 LSEON LSE 使能 位 8 LSERDY LSE 就绪标志 位 3:2 RTCSEL RTC 时钟源:00=无 01=LSE 10=LSI 11=HSE/128 位 0 RTCEN RTC 时钟使能BDCR 只能在允许备份域访问后写,且系统复位不清它(只有备份域复位才清)------ 这保证 RTC 配置在系统复位后仍然有效。
面试题
一、概念与框图
Q1. RTC 的本质是什么?
A:
一个带独立备份域供电的 32 位秒计数器。
LSE 晶振经预分频成 1Hz,每秒 CNT+1;CNT 与 ALR 比较产生闹钟。掉电(VBAT 供电)继续走,系统复位不影响。
Q2. RTC 和普通定时器的本质区别?
A:
① 供电域:定时器用主电源,掉电清零;RTC 在备份域,VBAT 供电掉电不断。
② 时钟源:定时器用 APB 时钟,RTC 用 LSE/LSI。
③ 计数目的:定时器精确计时到 us,RTC 秒级长计时。
Q3. 备份域被清空的两种情况?
A:
① 软件置 RCC_BDCR 的 BDRST=1;
② VDD 和 VBAT 同时断电。系统复位、看门狗复位都不清备份域。
Q4. RTC 的两个时钟分别是什么?
A:
计数时钟(LSE/LSI,驱动走时)和 APB1 接口时钟(CPU 读写寄存器用)。
计数不依赖 APB1,所以 APB1 关掉 RTC 照走------这是低功耗的关键。
二、LSE 与时钟源
Q5. 为什么选 LSE 而不是 LSI?
A:
① LSE 外部晶振由 VBAT 供电,掉电继续振荡,能做"掉电走时";LSI 断电即停。
② LSE 精度约 20ppm,LSI 温漂大。
③ 32768=2^15,二进制分频器直接出 1Hz。
Q6. 32768 这个数字特殊在哪?
A:
32768 = 2^15,是 2 的整数次幂。
15 级二分频恰好得到 1Hz,纯硬件实现,无需补偿。
换成非 2 的幂频率,分频要乘除法且不精确。
Q7. LSE 配置要注意什么?
A:
① 必须先开 PWR/BKP 时钟并 PWR_BackupAccessCmd(ENABLE) 才能写 BDCR;
② LSE 起振慢(1~2 秒),等 LSERDY 要有超时保护,否则晶振故障会死等。
三、NVIC 联动
Q8. RTC 有哪些中断?怎么配置?
A:
秒中断(SECF/SECIE)、闹钟中断(ALRF/ALRIE)、溢出中断(OWF/OWIE)。
配置两步:RTC_ITConfig 使能外设中断 + NVIC 配置 RTC_IRQn 使能全局中断。
Q9. 中断服务函数里做什么?为什么先确认再清标志?
A:
① 读 CRH 确认对应使能、读 CRL 确认标志置位;
② 写 0 清标志;
③ 读 CNT 做业务。
三个中断共用一个 RTC_IRQHandler,不确认会误处理、误清标志。
Q10. 待机模式怎么用 RTC 唤醒?
A:
设 ALR=CNT+N,PWR_EnterSTANDBYMode() 进入待机,N 秒后 RTC 闹钟唤醒。注意待机唤醒=复位重跑 main,靠 BKP 魔法数区分冷启动与唤醒。
四、预分频器
Q11. 预分频公式?为什么 PRL+1?
A:
f_sec = CK_RTC/(PRL+1)。
配 32767 → 32768/32768 = 1Hz。因为预分频器从 PRL 数到 0,共 PRL+1 个时钟周期,所以分频因子是 PRL+1。
Q12. PRL 复位值是多少?不配置会怎样?
A:
复位值 0x8000 = 32768。
不配置时分频因子 32769,输出约 0.99997Hz,一天差约 3 个计数拍(约 2.6 秒),所以电子钟必须配 32767。
Q13. DIV 余数寄存器干什么用?
A:
20 位只读,复位后装载 PRL,每 LSE 时钟沿减 1,减到 0 重载并出秒脉冲。
读 DIV 可得亚秒精度(1/32768s≈30.5us),用于亚秒时间戳和晶振校准。
Q14. 秒信号产生的完整链路?
A:
LSE 32.768kHz → 预分频器(PRL+1=32768)→ 1Hz 秒脉冲 → CNT 32 位秒计数器 +1 → CNT==ALR 触发闹钟。
DIV 在中间做余数计数。
五、综合
Q15. 怎么用 BKP 判断"首次上电还是待机唤醒"?
A:
BKP_DR1 存魔法数 0xA5A5。
备份域被清(首次上电)魔法数消失→完整配置 RTC 并写回魔法数;
系统复位/待机唤醒备份域保留→跳过配置直接读时间。
待机唤醒本质也是复位,只能靠备份域区分。
Q16. 读 CNT 怎么保证 32 位一致?
A:
硬件规定先读 CNTL 会把 CNTH 锁存,标准库 RTC_GetCounter() 先读低 16 位再读高 16 位,保证两次读属于同一时刻。
避免进位瞬间读到"高低位拼接错乱"。
Q17. 如何用 RTC 秒数显示"2026-10-18 12:00:00"?
A:
F1 RTC 只存秒数。
raw = CNT + 起点秒数(如 2000-01-01 对应 946684800),localtime(&raw)展开 struct tm,再格式化打印。时分秒也可直接算:时=(s/3600)%24,分=(s/60)%60,秒=s%60。
Q18. RTC 闹钟是秒级比较还是时分秒比较?
A:
秒级。ALR 是 32 位秒值,与 CNT 直接比较,相等即触发。
设"30 秒后闹"就是 RTC_SetAlarm(RTC_GetCounter()+30)。
Q19. 写 RTC 寄存器为什么必须走 CNF 流程?
A:
RTC 在备份域,与 CPU 主频不同步。
写前必须:等 RTOFF=1 → CNF=1 开配置窗口 → 写 → CNF=0 关窗口 → 等 RTOFF=1 确认写入完成。否则写入可能丢失或冲突。
Q20. 20ppm 的 LSE 一天误差多少?
A:
86400s × 20/1,000,000 ≈ 1.73 秒/天。
校准方法:
换温补晶振(TCXO)或软件跳秒------定时对时测出每天快/慢量,每天整点补偿。
【STM32】 RTC 实时时钟详解:从概念到预分频器
Brilliantwxx2026-10-19 6:06
相关推荐
lhbweilai4 小时前
分析FreeRTOS内核对象API的源码实现(四)inrichtt7 小时前
第八课时:SPI总线揭秘:四线全双工通信的奥半杯咖啡半行码8 小时前
Linux字符设备驱动开发基础:从设备号到ioctl的完整实战碧海银沙音频科技研究院9 小时前
基于炬芯ATS3089X的usb U盘/usb audio声卡/usb cdc虚拟串口/录音dump到本地功能实现@松9 小时前
学习stm32cudemx +vs 创建hex文件,刷机CC1802539448610 小时前
具有强大的图像和语音处理能力的高性能 MCU芯片ESP32-P4NRW32X小范的技术工坊10 小时前
Linux应用开发(文件IO)科芯创展12 小时前
XZ1824 宽电压150V 10A开关降压DC-DC控制器技术解析恒锐丰科技林技术员12 小时前
EG1416 低侧栅极驱动芯片:小封装下的功率驱动利器




