4. 备份域寄存器与RTC核心寄存器:BKP域的作用,RTC_TR、RTC_DR、RTC_SSR、RTC_PRER等寄存器详解
好,咱们这一章聊点硬核的——备份域和RTC的核心寄存器。说实话,我刚接触STM32L4的时候,也被备份域这个概念绕晕过。它到底备了个啥?跟RTC又是什么关系?
别急,咱们一层层剥开。
4.1 备份域(BKP Domain)到底是什么?
先看一张图,帮你建立整体印象:
说白了,备份域就是一块「永不掉电」的小天地。主电源断了,它靠VBAT引脚上的纽扣电池继续工作。RTC的计数器、预分频器、还有那20字节的备份寄存器,全待在里面。
核心结论:备份域 = RTC核心逻辑 + 备份寄存器 + 独立供电。只要VBAT有电,时间就不会停,数据就不会丢。
4.2 备份域寄存器(BKP Registers)
STM32L4的备份域里,除了RTC之外,还有一组BKP寄存器。L4系列提供了20字节的备份寄存器(5个32位寄存器)。
我个人的习惯是,把这20字节当成「系统黑匣子」。比如记录系统复位次数、保存最后一次休眠前的状态标志、存个校准参数什么的。主电源掉电再恢复,读出来还在。
实用技巧:调试低功耗问题时,我经常在进入Stop模式前写一个特定值到BKP寄存器,醒来后检查这个值是否被意外篡改。如果变了,说明备份域复位过——VBAT可能没接好。
4.3 RTC核心寄存器详解
好,重头戏来了。RTC有四个寄存器你必须搞明白:RTC_TR、RTC_DR、RTC_SSR、RTC_PRER。它们决定了RTC怎么计时、精度多高。
4.3.1 RTC_TR —— 时间寄存器
这个寄存器存的是「当前时间」——时、分、秒。格式是BCD码,不是二进制。
举个例子:下午3点25分08秒,在RTC_TR里存的是 0x152508。注意看,15是时、25是分、08是秒,每位都是BCD。
// 读取当前时间(HAL库方式)
RTC_TimeTypeDef sTime = {0};
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
// sTime.Hours = 0x15
// sTime.Minutes = 0x25
// sTime.Seconds = 0x08
注意:读取RTC_TR之前,必须先检查RTC_ISR寄存器中的RSF位(寄存器同步标志)。RTC的时钟域和APB时钟域不同步,直接读可能读到错误值。HAL库内部做了这件事,但如果你直接操作寄存器,别忘了这一步。
4.3.2 RTC_DR —— 日期寄存器
存年、月、日、星期。同样是BCD格式。
| 位域 | 名称 | 说明 |
|---|---|---|
| [31:24] | YT | 年的十位(BCD) |
| [23:20] | YU | 年的个位(BCD) |
| [19:16] | WDU | 星期几(1=周一,7=周日) |
| [15:12] | MT | 月的十位(BCD) |
| [11:8] | MU | 月的个位(BCD) |
| [7:4] | DT | 日的十位(BCD) |
| [3:0] | DU | 日的个位(BCD) |
比如2024年3月15日,存的是 0x240315。年份只存后两位,世纪由RTC_CR寄存器的BCDU位决定。
4.3.3 RTC_SSR —— 亚秒寄存器
这个寄存器是L4系列RTC的亮点。它存的是「亚秒值」,也就是比秒更精细的时间。
RTC内部有一个亚秒计数器,从 PREDIV_S 的值开始递减,每来一个异步时钟脉冲减1,减到0时秒进位。RTC_SSR读到的就是当前计数器的值。
举个例子:如果 PREDIV_S = 255,那么SSR的值从255递减到0,精度就是 1/256 秒 ≈ 3.9毫秒。
// 读取亚秒值
uint32_t subsecond = hrtc.Instance->SSR;
// 结合PREDIV_S计算实际亚秒时间
float subsecond_ms = (float)(PREDIV_S - subsecond) / (PREDIV_S + 1) * 1000.0f;
实战价值:我在做一个高精度数据采集项目时,用SSR实现了微秒级的时间戳对齐。每次外部中断触发,同时捕获SSR值,精度比单纯靠软件延时高了一个数量级。
4.3.4 RTC_PRER —— 预分频寄存器
这个寄存器决定了RTC的计时精度。它分两部分:
- PREDIV_A(异步预分频):7位,取值范围0~127。推荐值127。
- PREDIV_S(同步预分频):15位,取值范围0~32767。推荐值255。
计算公式:
RTC时钟频率 = LSE频率 / (PREDIV_A + 1) / (PREDIV_S + 1)
默认配置:32768 / (127 + 1) / (255 + 1) = 32768 / 128 / 256 = 1 Hz
也就是说,默认配置下RTC每秒走一格。如果你需要更高的分辨率,可以调整PREDIV_S的值。
| PREDIV_A | PREDIV_S | 异步时钟频率 | 同步时钟频率 | SSR精度 |
|---|---|---|---|---|
| 127 | 255 | 256 Hz | 1 Hz | 3.9 ms |
| 127 | 127 | 256 Hz | 2 Hz | 1.95 ms |
| 63 | 511 | 512 Hz | 1 Hz | 1.95 ms |
| 0 | 32767 | 32768 Hz | 1 Hz | 30.5 μs |
我曾经踩过的坑:为了追求高精度,我把PREDIV_A设成0,PREDIV_S设成32767。结果功耗直接飙了3倍!因为异步预分频器工作在32kHz,功耗比256Hz高得多。低功耗项目里,建议保持PREDIV_A=127,只调PREDIV_S。
4.4 寄存器写入保护机制
RTC的寄存器不是你想写就能写的。ST设计了写保护,防止程序跑飞时把时间改乱。
写入流程:
- 往RTC_WPR寄存器写
0xCA - 再写
0x53 - 然后才能修改RTC_TR、RTC_DR等寄存器
- 修改完成后,RTC自动重新进入写保护状态
// 手动操作示例(HAL库内部就是这么做的)
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
// 现在可以写RTC_TR、RTC_DR了
RTC->TR = 0x152508; // 设置15:25:08
// 写完后自动锁住
调试技巧:如果你发现RTC时间写不进去,先检查是不是忘了解锁。我见过有人把解锁代码放在初始化里,但后面每次写时间都忘了再解锁——结果时间永远停在初始值。
4.5 初始化配置实战
最后,给出一段完整的RTC初始化代码骨架,把上面讲的寄存器都用上:
void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
// 1. 使能RTC时钟访问
HAL_RTC_Init(&hrtc);
// 2. 配置预分频器(PRER)
// 异步127,同步255 -> 1Hz
hrtc.Instance->PRER = (127 << 16) | 255;
// 3. 设置初始时间(BCD格式)
sTime.Hours = 0x09;
sTime.Minutes = 0x30;
sTime.Seconds = 0x00;
sTime.TimeFormat = RTC_HOURFORMAT_24;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
// 4. 设置初始日期
sDate.Year = 0x24; // 2024年
sDate.Month = 0x03; // 3月
sDate.Date = 0x15; // 15日
sDate.WeekDay = 5; // 周五
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD);
// 5. 使能RTC
HAL_RTC_Enable(&hrtc);
}
嗯,到这里,备份域和RTC核心寄存器的内容就差不多了。你想想看,有了这些基础,后面咱们聊RTC的闹钟、唤醒、校准功能时,就不会觉得寄存器陌生了。