尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

stm32-时间戳、BKP、RTC

stm32-时间戳、BKP、RTC Unix时间戳简介从伦敦时间1970年1月1日0点开始计时只计秒数不会进位成分钟什么的到图中的2023年伦敦时间已经计时了16亿多秒。这样的计时方式对人类来说不友好不直观但对于计算机是非常方便的只需要计秒这一个变量不需要年月日时分这些变量硬件电路设计也因为只需要计秒数而变得简单但这样需要转换为实时时间的时候也会多使用一些资源。秒数要转换成年月日时分秒的实时时间较为麻烦要考虑平年闰年大月小月但好在步骤都是固定的。时间戳当然不能够一直计数下去时间戳存储在32位的秒计数器中上限就是2^32-1差不多可以计数到2108年然后就会计数溢出了64位秒计数器更久可以计三千亿年了。时间戳是以伦敦时间为准其他时区的实时时间也只需要加上时间偏移就好了如图中的北京。GMT、UTCGMT是以地球自转来计时的但现在地球因为潮汐力等原因自转越来越慢所以在GMT中1秒的时间长度会变化是不稳定的所以现在都使用UTC了。UTC规定了1秒的长度基本是固定不变的但现实中的实时时间应与地球自转同步所以当UTC的时间比GMT时间快超过0.9秒即接近一秒时UTC就会多走一秒这一分钟就有61秒相当于等待GMT时间这一秒就是闰秒了。C语言相关函数BKP备份寄存器简介VDD主电源供电时BKP由主电源供电VDD被切断时BKP由备用电源VBAT供电当二者都被切断BKP数据清零因为BKP本质是RAM存储器掉电会丢失。TAMPER引脚侵入事件清除BKP数据可以用于保护数据防止被盗。结构BKP重要的寄存器存在于后备区域即图中橙色部分凡是VDD切断后可以使用VBAT供电的都在后备区域包括RTC。RTC简介既然是实时时钟那么就要一秒自增一次频率应为1HZ所以对于各种频率的外部时钟应该先进行分频并且外部时钟频率一般比较高所以就设置了可1~2^20分频的预分频器。对于高速外部时钟HSE可以将自身8MHZ进行128分频后输入RTC的RTCCLK引脚之后RTC内部再进行1~2^20预分频。先进行128分频是因为HSE频率很高只使用RTC内部预分频是达不到需要的1HZ的对于低速外部时钟LSE频率是32.768KHZ即32768HZ32768本身就是2的次方数所以使用RTC的2^15预分频就能很方便得到1HZ。LSE是专门为RTC设计的对于低速内部时钟LSI频率40KHZ也可以作为RTC的外部时钟源。三种时钟源中HSE是系统时钟LSI是看门狗时钟它们的主要任务都不是给RTC提供时钟并且它们只能用VDD供电而LSE是专门给RTC使用的而且可以在VDD切断后使用VBAT供电。所以LSE是最佳选择。结构左边的部分是计时部分选择的外部时钟首先进行预分频RTC预分频器里其实也是一个定时器RTC_PRL中写入需要的分频值RTC_DIV余数寄存器相当于定时器的CNT外部时钟每来一个输入脉冲这个DIV就自减一次减到0之后再来一个输入脉冲就会给右边的计数器一个输出脉冲计数器里面就自增一次同时左边的CNT数值回到分频值。例如使用LSE时钟输入频率是32768HZ设置PRL32767因为包含0所以分频值32767132768DIV初始值0。LSE输入一个脉冲DIV自减这时会给右边计数器一个输出脉冲计数器1DIV的值回到32767LSE继续给脉冲DIV自减给32768个脉冲后DIV从0又自减再给计数器一个输出脉冲计数器1DIV回到32767。相当于原本的32768HZ一秒可以产生32768个脉冲通过左边定时器的控制使得它一秒产生的32768个脉冲才输出一个脉冲将频率降低了32768倍即1HZ。右边的计数器就是和时间戳有关的可以一直计数到2^32-1之后溢出。RTC_ALR是闹钟寄存器设置一个值当计数器的值等于它时就可以产生中断也可以唤醒系统退出待机模式但是值是固定的。右边的部分是中断有三种信号可以产生中断一是闹钟寄存器二是计数器溢出三是自增一次即过去一秒可以进入一次中断。其中只有使能的中断才可以通向NVIC控制。下面部分和PWR电源控制有关WKUP引脚也可以使系统退出待机模式。图中灰色部分都位于后备区域可以用VBAT供电待机时也能维持供电其余区域待机时就断电了。RTC时钟源在APB1总线上选择。流程外部电路VBAT供电电路有两种方案简单连接就是备用电池直接连接VBAT引脚但推荐连接是VDD和VBAT供电时都加一个低压降二极管防止VDD的电流倒灌到备用电源同时加一个滤波电容。LSE电路使用晶振和两个起振电容连接通过两个引脚和核心板连接。注意事项要使用BKP和RTC需要先开启APB1总线上PWR和BKP的时钟再设置PWR寄存器的DBP位这样才能使能BKP和RTC。由结构图可以看出里面有两种时钟APB1总线使用的PCLK1时钟和RTC使用的时钟它们的频率是不一样的所以想通过APB1总线去访问RTC就需要等待RTC的寄存器同步到APB1总线不然一开始就去访问数据就是错的。即访问RTC前要等待RTC中的寄存器同步标志位RSF置1。想要对RTC进行写入操作要先设置CNF位。对RTC前一次写入结束之后才能开始下一次写入所以要等待RTOFF标志位置1才能继续写入其实还是时钟不同步的问题数据写入RTC需要时间。读写BKP代码实现按一下按键就改写一次BKP寄存器里的值并读出。主函数#include stm32f10x.h// Device header #include Delay.h #include OLED.h #include key.h uint16_t num,writeArray[2]{0x1234,0x2345}; uint16_t receArray[2]; int main() { OLED_Init(); key_init(); RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//开启时钟 PWR_BackupAccessCmd(ENABLE);//设置DBP位使能访问 while(1) { numkey_num(); BKP_WriteBackupRegister(BKP_DR1,writeArray[0]); BKP_WriteBackupRegister(BKP_DR2,writeArray[1]); OLED_ShowHexNum(1,1,BKP_ReadBackupRegister(BKP_DR1),4); OLED_ShowHexNum(2,1,BKP_ReadBackupRegister(BKP_DR2),4); if(num1) { writeArray[0]; writeArray[1]; } } }RTC读写时间代码MYRTC.c文件#include stm32f10x.h // Device header #include time.h uint16_t MYRTC_time[]{2023,1,1,23,59,55};//年月日时分秒 //初始化RTC先开PWR和BKP时钟设置PWR的DBP位。然后打开LSE选择LSE使能LSE。接着等待同步配置分频值和计数值 void MYRTC_init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//开启时钟 PWR_BackupAccessCmd(ENABLE);//设置DBP位使能访问 //没有彻底断电过RTC的值就不应该重置所以需要一个if判断决定有没有彻底断电过可以通过判断BKP寄存器的值有没有重置来判断 if(BKP_ReadBackupRegister(BKP_DR1)!0xa5a5)//如果有彻底断电再次上电BKP重置后寄存器值0RTC需要重置 { //使用LSE为例省电LSE默认是关闭的需要先打开 RCC_LSEConfig(RCC_LSE_ON); //观察标志位LSERDY来得知LSE开启成功没有 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY)RESET); //再选择LSE作为时钟源 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //使能时钟 RCC_RTCCLKCmd(ENABLE); //等待寄存器同步、等待上一次写入完成 RTC_WaitForSynchro(); RTC_WaitForLastTask(); //配置分频器、等待写入完成 RTC_SetPrescaler(32768-1);//需要先设置CNF位才能对定时器的寄存器进行写入但在代码中对这些寄存器进行写入操作的函数里都已经设置了CNF位了 RTC_WaitForLastTask(); //设置计数器初始CNT等待写入完成 RTC_SetCounter(1672588795);//计数器的值CNT会从这里开始一秒自增如果不设置CNT就是0开始即1970年1月1日0点 RTC_WaitForLastTask(); BKP_WriteBackupRegister(BKP_DR1,0xa5a5);//改写BKP寄存器值用于下次断电判断 } else//如果没有彻底断电过RTC值不能重置 { RTC_WaitForSynchro(); RTC_WaitForLastTask(); } } void MYRTC_settime(void)//数组写入年月日时分秒函数转换为CNT的值 { time_t time_cnt;//time_t是unsigned int struct tm time_date;//结构体tm中的变量名称叫time_date结构体成员是年月日时分秒 time_date.tm_year MYRTC_time[0]-1900;//结构体年份范围是在1900年基础上加的所以这里数组减去1900 time_date.tm_mon MYRTC_time[1]-1;//结构体月的范围是0~11所以这里减一 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_cntmktime(time_date);//mktime函数变量是结构体地址返回值是CNT RTC_SetCounter(time_cnt);//将RTC的CNT设置为mktime函数计算好的CNT值 RTC_WaitForLastTask();//等待写入完成 } void MYRTC_gettime(void)//读取CNT函数转化为年月日时分秒 { time_t time_cnt; struct tm time_date; time_cntRTC_GetCounter();//获取RTC的CNT值,起始值是初始化函数里设定的CNT值 time_date*localtime(time_cnt); MYRTC_time[0]time_date.tm_year1900; MYRTC_time[1]time_date.tm_mon1; 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; }MYRTC.h文件#ifndef __MYRTC_H_ #define __MYRTC_H_ extern uint16_t MYRTC_time[]; void MYRTC_init(void); void MYRTC_settime(void); void MYRTC_gettime(void); #endif主函数#include stm32f10x.h// Device header #include Delay.h #include OLED.h #include MYRTC.h int main() { OLED_Init(); MYRTC_init(); OLED_ShowString(1,1,xxxx-xx-xx); OLED_ShowString(2,1,xx:xx:xx); OLED_ShowString(3,1,CNT:); while(1) { MYRTC_gettime(); OLED_ShowNum(1,1,MYRTC_time[0],4); OLED_ShowNum(1,6,MYRTC_time[1],2); OLED_ShowNum(1,9,MYRTC_time[2],2); OLED_ShowNum(2,1,MYRTC_time[3],2); OLED_ShowNum(2,4,MYRTC_time[4],2); OLED_ShowNum(2,7,MYRTC_time[5],2); OLED_ShowNum(3,5,RTC_GetCounter(),10); } }
返回列表