BKP备份寄存器

BKP的初始化
根据上一所学习,BKP想要使能,必须开启PWR和BKP的时钟,然后使用PWR的一个函数,使能对BKP和RTC的访问。写入数据的话,有个BKP一个写入的函数,读取数据,BKP也有个读取的函数。
以上就是大体的思路,下面是BKP的库函数:
void BKP_DeInit(void);//手动清空BKP所有的数据寄存器。
下面两个函数是用来配置TAMPER侵入检测功能的。
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);//配置TAMPER引脚的有效电平,就是高电平触发还是低电平触发。
void BKP_TamperPinCmd(FunctionalState NewState);//就是是否开启侵入检测功能,如果需要的话,就要先配置TAMPER有效电平,再使能侵入检测功能。
void BKP_ITConfig(FunctionalState NewState);//中端配置,是否开启中断。
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);//时钟输出功能配置,可以选择在RTC引脚上输出时钟信号,输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲。
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);//设置RTC校准值。就是写入RTC校准寄存器。
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//写备份寄存器,第1个参数是要指定你要写在哪个DR中,第2个参数就是要写入的数据了。
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读备份寄存器,参数指定要读哪个DR。
下面这四个函数帮助管理备份寄存器的状态和中断:
FlagStatus BKP_GetFlagStatus(void);
void BKP_ClearFlag(void);
ITStatus BKP_GetITStatus(void);
void BKP_ClearITPendingBit(void);
void PWR_DeInit(void);
void PWR_BackupAccessCmd(FunctionalState NewState);//备份寄存器访问使能,设置PWR_CR寄存器里的DBP位。
void PWR_PVDCmd(FunctionalState NewState);
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);
void PWR_WakeUpPinCmd(FunctionalState NewState);
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);
void PWR_EnterSTANDBYMode(void);
FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);
void PWR_ClearFlag(uint32_t PWR_FLAG);
//写入数组
uint16_t ArrayWrite[] = {0x1234,0x5678};
//读取数组
uint16_t ArrayRead[2];
//存放键值的变量
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Key_Init();
OLED_ShowString(1,1,"W:");
OLED_ShowString(2,1,"R:");
//开启PWR和BKP的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
//使能对BKP和RTC的访问
PWR_BackupAccessCmd(ENABLE);
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);
BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);
OLED_ShowHexNum(1,3,ArrayWrite[0],4);
OLED_ShowHexNum(1,8,ArrayWrite[1],4);
ArrayWrite[0]++;
ArrayWrite[1]++;
}
ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);
ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);
OLED_ShowHexNum(2,3,ArrayRead[0],4);
OLED_ShowHexNum(2,8,ArrayRead[1],4);
}
}
RTC实时时钟

第一步:
在使用RTC外设之前,和BKP一样,要开启PWR和BKP的时钟,并且使能BKP和RTC的访问。
第二步:
启动RTC的时钟,我们计划使用LSE作为系统时钟,我们要使用RCC模块里的函数,开启LSE的时钟。这个LSE时钟为了省电,默认是关闭的。
第三步:
配置RTCCLK这个数据选择器,指定LSE为RTCCLK。
第四步:
调用两个等待函数,一个是等待同步,另一个是等待上一次操作完成。
第五步:
配置预分频器,给PRL重装寄存器一个合适的分频值,以确保输出给计数器的频率是1Hz。
第六步:
配置CNT的值,给RTC一个初始时间。如果你需要闹钟的话,可以配置闹钟值。
看几个RCC时钟配置函数:
void RCC_LSEConfig(uint8_t RCC_LSE);//配置LSE外部低速时钟(启动LSE)。
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);//RTCCLK配置,用来选择RTCCLK时钟源。
void RCC_RTCCLKCmd(FunctionalState NewState);//启动RTCCLK,使能RTCCLK。
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);//获取标志位。因为LSE时钟,不是你让他启动,那就能立刻启动的,调用这个启动时钟函数之后,我们还需要等待一下标志位。
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//配置中断输出
void RTC_EnterConfigMode(void);//进入配置模式
void RTC_ExitConfigMode(void);//退出配置模式
uint32_t RTC_GetCounter(void);//获取CNT计数器的值
void RTC_SetCounter(uint32_t CounterValue);//写入CNT计数器的值
void RTC_SetPrescaler(uint32_t PrescalerValue);//写入预分频器,这个值会写入到预分频器的PRL重装寄存器中,用来配置预分频器的分频系数
void RTC_SetAlarm(uint32_t AlarmValue);//写入闹钟值
uint32_t RTC_GetDivider(void);//读取预分频器中的DIV余数寄存器,余数寄存器是一个自减计数器,获取余数寄存器的值,一般是为了得到更细致的时间
void RTC_WaitForLastTask(void);//等待上次操作完成
void RTC_WaitForSynchro(void);//等待同步
下面四个函数是等待标志位的函数:
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);
void MyRTC_Init(void)
{
//在使用RTC外设之前,和BKP一样,要开启PWR和BKP的时钟,并且使能BKP和RTC的访问。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
PWR_BackupAccessCmd(ENABLE);
//开启LSE时钟,并等待LSE时钟启动完成
RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
//选择RTCCLK时钟源,使能时钟
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
//我们要调用两个等待函数,一个是等待同步,另一个是等待上一次写入操作完成。
//这两行代码是一个安全保障,防止因为时钟不同步造成的BUG。
RTC_WaitForSynchro();//等待同步,就是程序刚开始的时候调用一下。
RTC_WaitForLastTask();//等待上次操作完成
//配置预分频器
RTC_SetPrescaler(32768 - 1);//LSE的频率是32768Hz,进行32768分频就是1Hz了,减1是因为计数器的值包含0.
RTC_WaitForLastTask();//等待操作完成
//设置初始时间
RTC_SetCounter(1672588795); //2023年1月1日
RTC_WaitForLastTask();//等待操作完成
//到这里RTC的初始化步骤就完成了,CNT的值就会从1672588795秒开始,以1s的时间间隔,不断自增。我们读取CNT就能获取时间了。
}
上面是初始化的函数代码,接下来写逻辑函数:
uint16_t MyRTC_Time[] = {2023,1,1,23,59,55};
void MyRTC_SetTime()
{
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);
RTC_SetCounter(time_cnt);
RTC_WaitForLastTask();//等待操作完成
}
void MyRTC_ReadTime(void)
{
time_t time_cnt;
struct tm time_data;
time_cnt = RTC_GetCounter();//读取CNT的秒数
time_data = *localtime(&time_cnt);
MyRTC_Time[0] = time_data.tm_year+1900;
MyRTC_Time[1] = time_data.tm_mon +1;
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 ;
}
主逻辑函数:
int main(void)
{
OLED_Init();
MyRTC_Init();
OLED_ShowString(1,1,"Data:XXXX-XX-XX");
OLED_ShowString(2,1,"Time:XX:XX:XX");
OLED_ShowString(3,1,"CNT:");
while(1)
{
MyRTC_ReadTime();
OLED_ShowNum(1,6,MyRTC_Time[0],4);
OLED_ShowNum(1,11,MyRTC_Time[1],2);
OLED_ShowNum(1,14,MyRTC_Time[2],2);
OLED_ShowNum(2,6,MyRTC_Time[3],2);
OLED_ShowNum(2,9,MyRTC_Time[4],2);
OLED_ShowNum(2,12,MyRTC_Time[5],2);
OLED_ShowNum(3,5,RTC_GetCounter(),10);
}
}
这样就可以显示时间了。
到最后,我们还可以发现一个“BUG”,那就是每次复位,RTC都会重新计数,这是因为在我们的RTC初始化函数中,没有进行对应的判断,导致每一次都会重新设置RTC的数值,下面是改进后的代码:
void MyRTC_Init(void)
{
//在使用RTC外设之前,和BKP一样,要开启PWR和BKP的时钟,并且使能BKP和RTC的访问。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
PWR_BackupAccessCmd(ENABLE);
if(BKP_ReadBackupRegister(BKP_DR1) != 0XA5A5)
{
//开启LSE时钟,并等待LSE时钟启动完成
RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
//选择RTCCLK时钟源,使能时钟
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
//我们要调用两个等待函数,一个是等待同步,另一个是等待上一次写入操作完成。
//这两行代码是一个安全保障,防止因为时钟不同步造成的BUG。
RTC_WaitForSynchro();//等待同步,就是程序刚开始的时候调用一下。
RTC_WaitForLastTask();//等待上次操作完成
//配置预分频器
RTC_SetPrescaler(32768 - 1);//LSE的频率是32768Hz,进行32768分频就是1Hz了,减1是因为计数器的值包含0.
RTC_WaitForLastTask();//等待操作完成
//设置初始时间
MyRTC_SetTime();
//写入BKP_DR1的数值A5A5,使得MPU复位但不影响RTC继续计数
BKP_WriteBackupRegister(BKP_DR1,0XA5A5);
//到这里RTC的初始化步骤就完成了,CNT的值就会从1672588795秒开始,以1s的时间间隔,不断自增。我们读取CNT就能获取时间了。
}
else
{
//我们要调用两个等待函数,一个是等待同步,另一个是等待上一次写入操作完成。
//这两行代码是一个安全保障,防止因为时钟不同步造成的BUG。
RTC_WaitForSynchro();//等待同步,就是程序刚开始的时候调用一下。
RTC_WaitForLastTask();//等待上次操作完成
}
}