2.42 读写备份寄存器&实时时钟

BKP备份寄存器

BKP的初始化

根据上一所学习,BKP想要使能,必须开启PWR和BKP的时钟,然后使用PWR的一个函数,使能对BKP和RTC的访问。写入数据的话,有个BKP一个写入的函数,读取数据,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);

PWR库函数

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库函数

看几个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时钟,不是你让他启动,那就能立刻启动的,调用这个启动时钟函数之后,我们还需要等待一下标志位。

RTC库函数

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();//等待上次操作完成
	}
	
	
}