1.BKP简介(了解)
BKP是RAM寄存器,如果VDD和VBAT都没电了BKP中的数据就会丢失
2.BKP基本结构(了解)
3.BKP实战代码
1.相关库函数
//配置Tamper侵入检测功能 void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);//配置Tamper引脚的有效电平 void BKP_TamperPinCmd(FunctionalState NewState);//是否开启侵入检测 //配置RTC时钟输出功能 void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource); //写入RTC时钟校准寄存器 void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue); //写备份寄存器 void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data); //读备份寄存器 uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR); //通过PWR使能对BKP和RTC的访问 void PWR_BackupAccessCmd(FunctionalState NewState);2.基本配置格式
//大致流程:直接先开启PWR和BKP的时钟,然后利用函数PWR_BackupAccessCmd()通过PWR使能对BKP和RTC的访问 //完成初始化,最后直接读写BKP寄存器就行了 //下面是初始化的格式 //开启PWR时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启BKP时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //通过PWR使能对BKP和RTC的访问 PWR_BackupAccessCmd(ENABLE);4.RTC简介
LSE振荡器时钟是RTC内部的专用时钟(只有LSE可以由VBAT供电)
HSE时钟主要是交给系统时钟的,LSI振荡器时钟主要提供给看门狗(备选RTC的时钟)
5.RTC内部基本结构
分区:左边灰色部分是分频、计数计时的部分;右边白色部分是中断输出使能和NVIC部分;上面是APB1总线读写部分;下面是和PWR关联的部分(只有灰色部分可以在掉电时继续运行)
大致流程:RTCCLK(通常为32.768)输入后在RTC_PRL(相当于ARR)和RTC_DIV(相当于CNT,但是是自减的)进行分频,得到TR_CLK为1MHz进行后续时间计算;RTC_CNT就是相当于Unix时间戳的秒计数器(就是只记秒,不进位,最后通过总秒数换算出具体时间)
细节:RTC_ALR是一个闹钟寄存器(相等时可以进入中断或退出待机模式)
RTC_Second秒中断,设置后每秒进一次中断;RTC_Overflow溢出中断;RTC_Alarm闹钟中断
6.RTC基本结构图(根据内部结构简化的流程图)
7.操作RTC时的注意事项
注意:1.要使用BKP或RTC时,都要先开启PWR和BKP的时钟,再使用PWR使能BKP和RTC的访问(初始化时特别注意)
2.刚上电时,需要等待RTCCLK直到其驱动后备区域的寄存器(因为PCLK的频率和RTCCLK的频率不同,并且刚上电时如果不等待RTCCLK那么PCLK就会读取寄存器的值导致出现错误)
8.RTC实战代码
1.基本库函数
//RCC时钟部分 //启动LSE外部时钟 void RCC_LSEConfig(uint8_t RCC_LSE); //启动LSI低速时钟 void RCC_LSICmd(FunctionalState NewState); //选择RTCCLK时钟源(就是配置RTCCLK数据选择器) void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource); //启动RTCCLK void RCC_RTCCLKCmd(FunctionalState NewState); //等待标志位(启动LSE时钟后还要等待一下标志位) FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG); //RTC部分 //进入配置模式(写寄存器前必须先进入配置模式) void RTC_EnterConfigMode(void); //退出配置模式 void RTC_ExitConfigMode(void); //获取CNT计数器值(读取时钟) uint32_t RTC_GetCounter(void); //写入CNT计数器值(设置时间) void RTC_SetCounter(uint32_t CounterValue); //写入预分频器值(配置分频系数) void RTC_SetPrescaler(uint32_t PrescalerValue); //写入闹钟值 void RTC_SetAlarm(uint32_t AlarmValue); //读取预分频器中的DIV余数寄存器(自减计数器) uint32_t RTC_GetDivider(void); //等待上次操作完成 void RTC_WaitForLastTask(void); //等待同步 void RTC_WaitForSynchro(void);2.配置的基本思路
1.开启PWR和BKP的时钟,然后利用函数PWR_BackupAccessCmd()通过PWR使能对BKP和RTC的访问(与BKP的初始化一样)
2.开启RTC的时钟(即开启LSE的时钟)
3.配置RTCCLK数据选择器,指定LSE为RTCCLK
4.等待RTC寄存器同步和前一次操作完成
5.配置分频器
6.配置CNT的值(给RTC一个初始时间)
3.基本配置格式
uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55}; void MyRTC_SetTime(void); void MyRTC_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) { //开启LSE时钟 RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);//等待启动完成 //选择LSE作为时钟源 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE);//使能时钟 //特别注意:等待同步和上次操作完成 RTC_WaitForSynchro(); RTC_WaitForLastTask(); //配置预分频器(不需要我们自己进入配置模式) RTC_SetPrescaler(32768 - 1); RTC_WaitForLastTask();//等待写入完成 //设置时间 MyRTC_SetTime(); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else { RTC_WaitForSynchro(); RTC_WaitForLastTask(); } } //如果LSE无法起振导致程序卡死在初始化函数中 //可将初始化函数替换为下述代码,使用LSI当作RTCCLK //LSI无法由备用电源供电,故主电源掉电时,RTC走时会暂停 /* void MyRTC_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) { RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); RTC_WaitForLastTask(); RTC_SetPrescaler(40000 - 1); RTC_WaitForLastTask(); MyRTC_SetTime(); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else { RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); RTC_WaitForLastTask(); } }*/ //设置时间 void MyRTC_SetTime(void) { time_t time_cnt; struct tm time_date; time_date.tm_year = MyRTC_Time[0] - 1900; time_date.tm_mon = MyRTC_Time[1] - 1; time_date.tm_mday = MyRTC_Time[2]; time_date.tm_hour = MyRTC_Time[3]; time_date.tm_min = MyRTC_Time[4]; time_date.tm_sec = MyRTC_Time[5]; time_cnt = mktime(&time_date) - 8 * 60 * 60; RTC_SetCounter(time_cnt); RTC_WaitForLastTask(); } //读取时间 void MyRTC_ReadTime(void) { time_t time_cnt; struct tm time_date; time_cnt = RTC_GetCounter() + 8 * 60 * 60; time_date = *localtime(&time_cnt); MyRTC_Time[0] = time_date.tm_year + 1900; MyRTC_Time[1] = time_date.tm_mon + 1; MyRTC_Time[2] = time_date.tm_mday; MyRTC_Time[3] = time_date.tm_hour; MyRTC_Time[4] = time_date.tm_min; MyRTC_Time[5] = time_date.tm_sec; }