嵌入式第六天---BKP读写备份寄存器&RTC实时时钟
BKP
BKP简介
BKP(Backup Registers)备份寄存器
BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位
TAMPER引脚产生的侵入事件将所有备份寄存器内容清除
RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲 存储RTC时钟校准寄存器
用户数据存储容量:
20字节(中容量和小容量)/ 84字节(大容量和互联型)
BKP基本结构

RTC
RTC简介
RTC(Real Time Clock)实时时钟 RTC是一个独立的定时器,可为系统提供时钟和日历的功能 RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时
32位的可编程计数器,可对应Unix时间戳的秒计数器
20位的可编程预分频器,可适配不同频率的输入时钟
可选择三种RTC时钟源:
HSE时钟除以128(通常为8MHz/128)
LSE振荡器时钟(通常为32.768KHz)
LSI振荡器时钟(40KHz)
RCT时钟树

简化的RTC框图

APB1 总线(上层):用于 CPU 通过软件读写 RTC 的寄存器(如设置时间、读取闹钟值)。
后备区域(Back-up Domain):图中灰色高亮部分,这是 RTC 的核心供电域。
- 特性:当系统主电源掉电时,VBAT 引脚(电池供电)会继续为该区域供电。因此,**RTC_CNT(计数器)、RTC_ALR(闹钟)、RTC_PRL(预分频)** 等关键寄存器的值在断电后仍能保持,实现了精准计时
待机时不供电:标注了 “待机时不供电” 的模块(如 APB1 接口、RTC_CR 控制寄存器、NVIC),说明这些寄存器仅在系统运行时有效,进入待机模式后会丢失数据。
RTC 预分频器 (RTC_PRL / RTC_DIV):
RTC_PRL 重装载值,重装载值赋给RTC_DIV 余数计数器 当递减到0时,RTCCLK再产生一个1HZ秒脉冲并且RTC_CNT+1
32 位可编程计数器 (RTC_CNT):秒脉冲每来一次,计数器就加 1。
溢出(Overflow):当 32 位计数器从最大值(0xFFFFFFFF)计数到下一个数时变为 0,会产生RTC_Overflow信号。
闹钟寄存器 (RTC_ALR):存放用户设定的闹钟时间。
当RTC_CNT(当前时间)与RTC_ALR(设定时间)相等时,产生RTC_Alarm信号并进行中断,可用于定时采集数据等
基本结构

RTC操作注意事项
执行以下操作将使能对BKP和RTC的访问:
设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟
设置PWR_CR的DBP,使能对BKP和RTC的访问
若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1
必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器
BKP读写备份寄存器相关代码
1.初始化(PWR和BKP)
注意:必须先开 PWR/BKP 时钟,再解锁后备区域,顺序不能反
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
//解锁 STM32 的 “后备区域(Backup Domain)” 访问权限。
PWR_BackupAccessCmd(ENABLE);
2.写BKP的DR寄存器
// BKP_DRx where x:[1, 42]:寄存器编号
// Data 要写入的 16 位数据 0x0000 ~ 0xFFFF
BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
RTC时钟相关代码
1.开启PWR和BKP时钟再解锁后备区域
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
PWR_BackupAccessCmd(ENABLE);
2.开启LSE时钟,并等待LSE时钟启动完成
RCC_LSEConfig( RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);
3.选择RTCCLK时钟源,并使能时钟
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
4.等待同步和等待上一次操作完成(防止时钟不同步,造成BUG)
RTC_WaitForSynchro();
RTC_WaitForLastTask();
5.配置预分频器再调用一次等待上一次操作完成函数
RTC_SetPrescaler(32768-1);//时钟源频率(32768) - 1
RTC_WaitForLastTask();
6.设置初始时间(可以不写,会使用默认时间1970)再调用一次等待上一次操作完成函数
RTC_SetCounter(1771946317);
RTC_WaitForLastTask();
7.写两个函数设置和获取时间
设置时间函数
//将自定义的时间数组 MyRTC_Time 转换为时间戳,写入 RTC 计数器
void MyRTC_SetTime(void){
time_t time_cnt;//秒时间戳
struct tm time_data;
time_data.tm_year = MyRTC_Time[0]-1900;
time_data.tm_mon = MyRTC_Time[1]-1;
time_data.tm_mday = MyRTC_Time[2];
time_data.tm_hour = MyRTC_Time[3];
time_data.tm_min = MyRTC_Time[4];
time_data.tm_sec = MyRTC_Time[5];
time_cnt = mktime(&time_data)-8*60*60;//将日期时间转换成秒数
RTC_SetCounter(time_cnt);
RTC_WaitForLastTask();
}
读取时间函数
//从 RTC 计数器读取时间戳,转换为日期时间,存回 MyRTC_Time 数组
void MyRTC_ReadTime(void){
time_t time_cnt;//秒时间戳
struct tm time_data;
time_cnt = RTC_GetCounter()+8*60*60;
time_data = *localtime(&time_cnt);//将时间戳转换为tm结构体
MyRTC_Time[0] = time_data.tm_year+1900;//还原为实际年份(如126→2026)
MyRTC_Time[1] =time_data.tm_mon +1;//还原为实际月份(如1→2月)
MyRTC_Time[2] =time_data.tm_mday ;//日期
MyRTC_Time[3] =time_data.tm_hour ;
MyRTC_Time[4] =time_data.tm_min ;
MyRTC_Time[5] =time_data.tm_sec ;
}
更多推荐
所有评论(0)