江科大STM32入门——读写备份寄存器(BKP)实时时钟(RTC)笔记整理
wx:嵌入式工程师成长日记
https://mp.weixin.qq.com/s/hDk7QaXP8yfYIj1gUhtMrw?token=1051786482&lang=zh_CNhttps://mp.weixin.qq.com/s/hDk7QaXP8yfYIj1gUhtMrw?token=1051786482&lang=zh_CN
RTC是一个独立的定时器,BKP并不能完全掉电不丢失,其可以完成一些主电源掉电时,保存少量数据的任务。而RTC在主电源掉电的时候保证掉电不丢失的关键就是BKP。
(一)时间戳
1、简介
- Unix时间戳(UnixTimestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数(只用秒来计数,永不进位)
- 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量(32位2038年到头,无符号是2106年)
- 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间
对于计算器来说一个永不进位的数据,无论是存储还是计算,都是非常方便的,因此在计算程序的底层,应用非常广泛。需要显示当前时间时,直接转换成年月日时分秒这个的格式就行了。
【使用好处】:
①简化硬件电路:在设计RTC硬件电路的时候,直接弄一个很大的秒寄存器就行了,不需要考虑年月日进位大小月平年论润,非常友好。
②进行时间间隔的计算非常方便。
③存储方便,只需要一个很大的变量表示秒数。
2、GMT/UTC
3、时间戳转换
C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换
(二)BKP外设
-
BKP(备份寄存器)
-
BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
-
(VBAT:当使用电池或其他电源连接到VBAT脚上时,当VDD断电时,可以保存备份寄存器的内容和维持RTC的功能。如果应用中没有使用外部电池,VBAT引脚应接到VDD引脚上)
-
TAMPER引脚产生的侵入事件将所有备份寄存器内容清除
-
TAMPER引脚是用于引入检测信号(可以是或上升沿/下降沿)的,当发生入侵时,将清除BKP所有内容,并申请中断。
-
并且是由备用电源供电,主电源断电后侵入检测仍然有效,以保证数据安全
-
-
RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
-
存储RTC时钟校准寄存器
-
用户数据存储容量:20字节(中容量和小容量)/84字节(大容量和互联型)
-
如果备用电源VBAT和主电源VCC都断电了,就会清除数据,因为BKP本质是RAM存储器,掉电丢失数据
2、BKP基本结构
橙色的是后背区域,除了BKP还有RTC电路。STM32后备区的特性是的那个VDD主电源掉电时,后备区仍然可以由VBAT的备用电池供电。当VDD主电源上电时候,后背区域会由VBAT切换到VDD,可以节省电池电量。
(三)RTC外设
-
RTC(Real Time Clock)实时时钟
-
RTC是一个独立的定时器,可为系统提供时钟和日历的功能
-
RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时。
-
32位的可编程计数器
-
20位的可编程预分频器,可适配不同频率的输入时钟(确保给到计数器的是1Hz的频率)
- 可选择三种RTC时钟源:(LSE(低速外部时钟)主要就是供RTC的,只有这一路时钟可以通过VBAT备用电池供电)
-
-
HSE时钟除以128(通常为8MHz/128)
-
LSE振荡器时钟(通常为32.768KHz)
-
LSI振荡器时钟(40KHz)
-
2、RTC框图
3、RTC基本结构
4、硬件电路
如果没有外部电池,建议VBAT引脚接到VDD,就是VBAT和主电源接到一起,并且再连接一个100nF的滤波电容
(四)RTC常用函数
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
//RTC 中断使能:通过传入指定的中断类型 RTC_IT 和状态 NewState,实现对 RTC 中断的使能或失能控制。
void RTC_EnterConfigMode(void);
//进入 RTC 配置模式:用于进入 RTC 的配置状态,以便进行相关参数的修改。
void RTC_ExitConfigMode(void);
//退出 RTC 配置模式:在完成 RTC 配置操作后,使用此函数退出配置模式。
uint32_t RTC_GetCounter(void);
//获取 RTC 计数器的值:返回 RTC 计数器的当前数值。
void RTC_SetCounter(uint32_t CounterValue);
//设置 RTC 计数器的值:将 RTC 计数器设置为指定的数值 CounterValue。
void RTC_SetPrescaler(uint32_t PrescalerValue);
//设置 RTC 预分频的值:为 RTC 预分频设置特定的值 PrescalerValue。
void RTC_SetAlarm(uint32_t AlarmValue);
//设置 RTC 闹钟的值:设定 RTC 闹钟的触发值为 AlarmValue。
uint32_t RTC_GetDivider(void);
//获取 RTC 预分频分频因子的值:获取当前 RTC 预分频分频因子的数值。
void RTC_WaitForLastTask(void);
//等待最近一次对 RTC 寄存器的写操作完成:确保之前对 RTC 寄存器的写入操作已经完成。
void RTC_WaitForSynchro(void);
//等待 RTC 寄存器与 RTC 的 APB 时钟同步:等待 RTC 相关寄存器(如 RTC_CNT、RTC_ALR 和 RTC_PRL)与 APB 时钟完成同步。
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
//检查指定的 RTC 标志位设置与否:通过传入标志位 RTC_FLAG,返回其状态。
void RTC_ClearFlag(uint16_t RTC_FLAG);
//清除 RTC 的待处理标志位:清除指定的 RTC 标志位。
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
//检查指定的 RTC 中断发生与否:根据传入的中断类型 RTC_IT,判断中断是否发生。
void RTC_ClearITPendingBit(uint16_t RTC_IT);
//清除 RTC 的中断待处理位:清除指定 RTC 中断的待处理位。
读写RTC实时时钟步骤
1.开启PWR和BKP的时钟
PWR的开启函数为RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); 开启后备电源的时钟
BKP的开启函数RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); 开启BKP外设的时钟
2.使能备份区域的访问
在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。
函数:PWR_BackupAccessCmd(ENABLE);
3.判断是否需要初始化RTC
4.开启LSE时钟
开启LSE时钟 RCC_LSEConfig(RCC_LSE_ON);
等待LSE时钟开启完毕 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
选择RTCCLK时钟为LSERCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
使能RTCCLK时钟 RCC_RTCCLKCmd(ENABLE);
5.等待时钟同步
因为可能在恢复主电源之后,APB1总线刚刚恢复震荡频率,但是RTCCLK需要经过外部震荡源分频后才能有一次输出。
如果直接读取,会导致读取不准确。
所以需要等待RTCCLK产生上升沿来激活更新一下时间戳计数器,这时APB1直接读取。 所以软件读取时必须等待RTCCLK来一个上升沿。
函数:RTC_WaitForSynchro(); (等待时钟同步)
6.等待写入完成
对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。
可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。 当RTOFF状态位是1时,才可以写入RTC寄存器
函数:RTC_WaitForLastTask();
7.设置RTC预分频器
设置预分频器RTC_SetPrescaler(32768 - 1);
等待写入完成RTC_WaitForLastTask();
8.写入前其实是需要设置RTC_CRL寄存器中的CNF位
RTC进入配置模式后,才能写入RTC_PRL(预分频器)、RTC_CNT(时间戳计数器)、RTC_ALR(闹钟寄存器)寄存器
9.设置CNT时间戳计数器时间
利用C语言中time.h来转换时间戳并写入
在BKP备份寄存器中写入特定数据。为下次复位或仅主电源断电后上电时判断是否初始化打下基础
初始RTC:
void MyRTC_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启PWR的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //开启BKP的时钟
/*备份寄存器、RTC访问使能*/
PWR_BackupAccessCmd(ENABLE); //使用PWR开启对备份寄存器和RTC的访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) //通过写入备份寄存器的标志位,判断RTC是否是第一次配置
//if成立则执行第一次的RTC初始化
{
RCC_LSEConfig(RCC_LSE_ON); //开启LSE时钟
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //等待LSE准备就绪
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择RTCCLK来源为LSE
RCC_RTCCLKCmd(ENABLE); //RTCCLK使能
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上一次操作完成
RTC_SetPrescaler(32768 - 1); //设置RTC预分频器,预分频后的计数频率为1Hz
RTC_WaitForLastTask(); //等待上一次操作完成
MyRTC_SetTime(); //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置
}
else //RTC不是第一次配置
{
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上一次操作完成
}
}
时钟设置:
void MyRTC_SetTime(void)
{
time_t time_cnt = 0; //定义秒计数器数据类型
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; //调用mktime函数,将日期时间转换为秒计数器格式
//- 8 * 60 * 60为东八区的时区调整
RTC_SetCounter(time_cnt); //将秒计数器写入到RTC的CNT中
RTC_WaitForLastTask(); //等待上一次操作完成
}
读取时间:
void MyRTC_ReadTime(void)
{
time_t time_cnt; //定义秒计数器数据类型
struct tm time_date; //定义日期时间数据类型
time_cnt = RTC_GetCounter() + 8 * 60 * 60; //读取RTC的CNT,获取当前的秒计数器
//+ 8 * 60 * 60为东八区的时区调整
time_date = *localtime(&time_cnt); //使用localtime函数,将秒计数器转换为日期时间格式
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;
}
(五)BKP常用函数
void BKP_DeInit(void);
//恢复缺省配置(用于清空BKP所有BKP寄存器)
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);
//配置TAMPER引脚的有效电平
void BKP_TamperPinCmd(FunctionalState NewState);
//是否开启侵入检测功能
void BKP_ITConfig(FunctionalState NewState);
//是否开启中断
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
//时钟输出功能配置(在RTC引脚上输出时钟信号、RTC校准时钟、RTC闹钟脉冲、秒脉冲)
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);
//设置RTC校准值
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
//写BKP备份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
//读BKP寄存器
FlagStatus BKP_GetFlagStatus(void);
//查看标志位
void BKP_ClearFlag(void);
//清除标志位
ITStatus BKP_GetITStatus(void);
//查看中断标志位
void BKP_ClearITPendingBit(void);
//清除中断标志位
读写BKP备份寄存器步骤
1.开启PWR(电源控制)和BKP的时钟
PWR的开启函数为RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); (开启后备电源VBAT)
BKP的开启函数RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); 开启BKP外设的时钟,都在APB1总线下
2.使能备份区域的访问
因为RTC实时时钟和BKP备份寄存器,都处于备份区域中。在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。
函数:PWR_BackpAccessCmd(ENABLE);
3.读写操作
写入:BKP_WriteBackupRegister(BKP_DR1,Data);
读出:Data = BKP_ReadBackupRegister(BKP_DR1);
uint16_t WriteArr[] = {0x0000, 0x0001};
uint16_t ReadArr[2] = { 0 };
int main()
{
//使能时钟电源和后备接口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
//使能备份访问控制
PWR_BackupAccessCmd(ENABLE);
while(1)
{
if(KEY_Get() == 1)
{
//写入BKP
BKP_WriteBackupRegister(BKP_DR1,WriteArr[0]);
BKP_WriteBackupRegister(BKP_DR2,WriteArr[1]);
WriteArr[0]++;
WriteArr[1]++;
}
//读取BKP
ReadArr[0] = BKP_ReadBackupRegister(BKP_DR1);
ReadArr[1] = BKP_ReadBackupRegister(BKP_DR2);
}
}