STM32-BKP备份寄存器RTC实时时钟

news/2025/1/5 18:55:55/

一、原理

Unix:

 一些系统是使用32bit有符号数存储,实际范围为-2,147,483,648到2,147,483,647‌即2^{31}-1~-2^{31}

经过计算int32数据会在2038年1月19日溢出,可以看到转换的为北京时间。

STM32的时间戳为无符号时间戳。

我们需要把秒计数器的时间通过计算得到秒技术其对应的时间,然后根据时区进行偏移(考虑到品平年闰年,大月小月闰月等)。

可以根据c语言官方函数直接计算:

 UTC、GMT

 GMT是之前的时间标准,UTC是计算了偏移量的现行标准。中国一般使用GMT+8/UTC+8。Unix时间戳没有闰秒,即协调世界时间的功能,所以可能秒数会偏差。

 时间戳和日期进行转换(数据类型):

 time_t实际上是int64类型,用来存储秒计数值

 

 tm类型为定义日期的结构体:struct tm

其中year为从1900年的第几年(最小应该为70);mon月份从0开始;wday表示周几;yday表示每年的第几天;isdst是否使用夏令时,1表示用,0不用,-1表示不知道。

 夏令时为在夏天的某段时间将时间提前一个小时。

 实际使用:

 mktime函数原理,通过输入的年月日时分秒计算,其他参数会自动计算回填,可以通过此函数自动计算星期。

 strftime函数参数(char *c,size_length,const *char,const struct tm*),其中const *char为格式字符串。函数使用为,将const struct tm*的内容通过const *char格式化字符存入长度为size_length的数组char *c中。

  其他函数:

二、 STM32的BKP备份寄存器&RTC实时时钟

1、BKP原理:

BKP寄存器数据需要VBT保持供电来进行掉电不丢失,实际使用方式和Flash类似。手册建议VBT无外部供电时接到VDD并上100nf的滤波电容。

 TAMPER在STM32F103C8T6中在PC13。可以外接上拉电阻和开关接地,做保护措施,接收到低电平清除寄存器内容。主电源断电后,侵入检测仍然有效。RTC校准时钟可以对RTC时钟进行校准。存储RTC时钟校准寄存器可以配合RTC校准时钟对RTC进行校准。

2、BKP的基本结构:

3、STM32的RTC外设

STM32的RTC类似DS1302外置实时时钟。RTC输入时钟具有20bit的分配器,即可分配1-2^{20}-1的分频。

RTC框图:

灰色部分为VBT断电供电部分选择RTC时钟-RTCCLK提供时钟-RTC_DIV(余数寄存器,自减)计数溢出后产生TR_CLK,并且通过RTC_PRL(重装载寄存器)进行重装载(预分频器原理)-通过TR_CLK的RTC_CNT进行计数(为无符号32bit),

  • RTC_CNT的计满溢出中断为RTC_Overflow。
  • 其中RTC_ALR为闹钟,和RTC_CNT一样的uint32寄存器,当RTC_ALR和RTC_CNT计数相同,会产生RTC_Alarm信号,前往中断系统(或唤醒芯片,退出睡眠模式,WKUP-PA0引脚也可以唤醒设备)。
  • RTC_Sencond中断来自TR_CLK的秒计数。
  • 中断选项中,IE结尾的是中断使能,F结尾的是中断标志位。

晶振选择:

一般可以选择三个时钟源。根据STM32RTC时钟树可以看到,包括2高速、2低俗、2内部、2外部共4个晶振作为晶振源,详细可见定时器文章。高速时钟一般为内部运行和主要外设使用,低速时钟一般供RTC、看门狗等使用。可以看到LSE OSC指向RTCCLK。且RTC有三个来源时钟。

32.768=2^{15}可直接经过分频1Hz。硬件电路计数器也方便进行计数溢出得到频率信号。一般使用LSE。

4、RTC基本结构

5、电路

 

 

 CR2032纽扣电池,印制面为正极。

6、操作注意事项

  •  PWR是电源管理
  •  第二点寄存器同步操作的原因:因为PCLK1(APB1总线时钟,36MHz)在主电源掉电时会停止。为了保证RTC掉电不丢失,RTC都是在RTCCLK(32.768Hz)同步下变更的。所以用APB1总线读取RTC寄存器内容,存在时钟不同步问题。时钟不同步会导致读取到错误数据。所以在APB总线刚开启时要进行时钟同步。
  • RTC_CRL为时钟配置使能标志位,使用时需要先配置。库函数自动进行了配置。
  • RTC的RTOFF为等待结束标志位。等待即可,当RTOFF=1才可写入。主要还是因为时钟频率不一样,不能立即更新。

三、程序实例

问题1:VBT供电导致STM32系统供电指示灯和OLED下电后还会有一些微弱显示。

问题2:有些芯片RTC晶振不起振。会导致程序卡死在晶振等待起振的地方。

1、写入BKP备份寄存器和从备份寄存器读出,显示到OLED。

将STM32断电、VBT不断电,STM32上电查看BKP数据是否掉电保存。(保存数据)

将VBT断电、STM32断电,然后STM32在上电查看BKP数据是否掉电保存。(不保存数据)

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"uint16_t Data[4]={0x01,0x02,0x03,0x04};//写入的数据
uint16_t GetData[4];//BKP读出的数据
int main(void){OLED_Init();RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//开启PWR时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//开启BKP时钟PWR_BackupAccessCmd(ENABLE);//开启RTC和BKP的访问使能BKP_WriteBackupRegister(BKP_DR1,Data[0]);//数据写入,在做STM32下电测试时,写入代码注释BKP_WriteBackupRegister(BKP_DR2,Data[1]);BKP_WriteBackupRegister(BKP_DR3,Data[2]);BKP_WriteBackupRegister(BKP_DR4,Data[3]);OLED_ShowString(1,1,"BKP:");GetData[0] = BKP_ReadBackupRegister(BKP_DR1);//数据读出GetData[1] = BKP_ReadBackupRegister(BKP_DR2);GetData[2] = BKP_ReadBackupRegister(BKP_DR3);GetData[3] = BKP_ReadBackupRegister(BKP_DR4);OLED_ShowHexNum(2,1,GetData[0],2);OLED_ShowHexNum(2,4,GetData[1],2);OLED_ShowHexNum(2,7,GetData[2],2);OLED_ShowHexNum(2,10,GetData[3],2);while(1){Delay_ms(200);}return 0;
}

2、RTC时钟

时间显示,如果VBT供电,那么STM32复位或下电RTC时钟不会丢失(RTC和BKP都可通过VBT供电)。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
#include "time.h"
Unixdate GetTime;
time_t CNT;
time_t DIVData;int main(void){OLED_Init();MyRTC_Init();OLED_ShowString(1,1,"Date:    -  -  ");OLED_ShowString(2,1,"Time:  :  :  ");OLED_ShowString(3,1,"CNT :");OLED_ShowString(4,1,"DIV :");while(1){GetTime = GetNowTime();//获取RTC内的时间CNT = GetCounter();DIVData = GetDIV();OLED_ShowNum(1,6,GetTime.years,4);OLED_ShowNum(1,11,GetTime.months,2);OLED_ShowNum(1,14,GetTime.day,2);OLED_ShowNum(2,6,GetTime.hours,2);OLED_ShowNum(2,9,GetTime.minutes,2);OLED_ShowNum(2,12,GetTime.second,2);OLED_ShowNum(3,6,CNT,10);OLED_ShowNum(4,6,DIVData,10);}return 0;
}

MyRTC.c

#include "stm32f10x.h"                  // Device header
#include "time.h"
#include "MyRTC.h"
Unixdate SetTime;
void MyRTC_Init(void){//时钟配置RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//使能RTC和BKP访问PWR_BackupAccessCmd(ENABLE);//开启LSE/LSI,并等待启动完成RCC_LSEConfig(RCC_LSE_ON);while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);
//	RCC_LSICmd(ENABLE);//备用配置LSI为内部时钟,并启动
//	while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY)!=SET);//等待启动完成//使用BKP来判断是否断电,若断电则进行初始化,相当于用BKP做了一个标志位if(BKP_ReadBackupRegister(BKP_DR1)!=0xA5A5){//选择LSE为时钟源,并使能时钟RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);//备用选择LSI作为时钟源RCC_RTCCLKCmd(ENABLE);//等待时钟同步,等待RTC上一次操作完成RTC_WaitForSynchro();RTC_WaitForLastTask();//配置预分频器,LSE=32768Hz,分频32768后为1Hz,LSI=40000HzRTC_SetPrescaler(32768-1);//函数内置写CNF=1/=0进入了配置模式/退出配置模式,只有配置模式可以写入寄存器//	RTC_SetPrescaler(40000-1);//备用使用LSI作为时钟源	RTC_WaitForLastTask();Time_Init(&SetTime);SetNowTime(SetTime);BKP_WriteBackupRegister(BKP_DR1,0xA5A5);}else{//若BKP不断电则不初始化//等待时钟同步,等待RTC上一次操作完成RTC_WaitForSynchro();RTC_WaitForLastTask();}}/*** @brief 获取当前CNT* @param  *     @arg * @param  *     @arg * @retval None*/
uint32_t GetCounter(void){return RTC_GetCounter();
}/*** @brief 获取当前余数值计数值* @param  *     @arg * @param  *     @arg * @retval None*/
uint32_t GetDIV(void){return RTC_GetDivider();
}/*** @brief 设置当前时间* @param  输入为Unixdate自定义日期类型*     @arg * @param  *     @arg * @retval None*/
void SetNowTime(Unixdate UnixdataStructure){struct tm NowTime;time_t count;NowTime.tm_min = UnixdataStructure.minutes;NowTime.tm_hour = UnixdataStructure.hours;NowTime.tm_mday = UnixdataStructure.day;NowTime.tm_mon = UnixdataStructure.months;NowTime.tm_year = UnixdataStructure.years;NowTime.tm_sec = UnixdataStructure.second;count = mktime(&NowTime)-8*60*60;//设置时间到RTC,输入东八区时间,偏移到0时区RTC_SetCounter(count);RTC_WaitForLastTask();//等待完成
}/*** @brief 获取RTC当前时间* @param  *     @arg * @param  *     @arg * @retval 返回当前RTC对应的日期时间*/
Unixdate GetNowTime(void){struct tm NowTime;Unixdate UnixdataStructure;time_t count;count = RTC_GetCounter()+8*60*60;//获取当前计数,偏移到东八区(STM32默认函数为0区时间)RTC_WaitForLastTask();//等待完成NowTime = *localtime(&count);//根据计数值换算成日期时间,将值传给NowTimeUnixdataStructure.years = NowTime.tm_year+1900;UnixdataStructure.months = NowTime.tm_mon+1;UnixdataStructure.day = NowTime.tm_mday;UnixdataStructure.hours = NowTime.tm_hour;UnixdataStructure.minutes = NowTime.tm_min;UnixdataStructure.second = NowTime.tm_sec;return UnixdataStructure;
}/*** @brief 日期变量初始化* @param  输入为日期变量结构体地址,直接对其进行改变*     @arg * @param  *     @arg * @retval None*/
void Time_Init(Unixdate *UnixdataStructure){UnixdataStructure->years = 2025-1900;UnixdataStructure->months = 1-1;UnixdataStructure->day = 3;UnixdataStructure->hours = 23;UnixdataStructure->minutes = 59;UnixdataStructure->second = 56;
}

MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_H
#include "stm32f10x.h"                  // Device header//#pragma pack(n)可修改编译器字节对齐数
typedef struct{uint8_t second;//(0-60)suint8_t minutes;//(0-59)minuint8_t hours;//(0-23)huint8_t months;//月(1-12)uint8_t day;//月中第几天(1-31)uint16_t years;//年
}Unixdate;void MyRTC_Init(void);
uint32_t GetCounter(void);
uint32_t GetDIV(void);
Unixdate GetNowTime(void);
void Time_Init(Unixdate *UnixdataStructure);
void SetNowTime(Unixdate UnixdataStructure);
#endif

其他

数据范围原理:

int32范围为-2^{31}~2^{31}-1,数据在计算机中为补码存储,

即int32范围:

在最大值情况下,符号位为 0,其余 31 位均为 1

0111 1111 1111 1111 1111 1111 1111 1111

在最小值情况下,符号位为 1,其余 31 位全为 0

1000 0000 0000 0000 0000 0000 0000 0000

最高位表示符号位,1为负,第32bit为2^{31},如上,所以正数可以达到2^{31}-1,负数可以达到-2^{31}

  • 最大值:(2^{31} - 1 = 2147483647)
  • 最小值:(-2^{31} = -2147483648)

同理int16范围为2^15-1  ~  -2^15  (32767~-32768)

int8_t范围为2^7-1  ~  -2^7  (127~-128)


http://www.ppmy.cn/news/1560639.html

相关文章

第五章 起航 01 一年之计

开年第一周,要做最重要的事情。 什么是重要的事?我认为不是重点项目,而是从我们整个在公司工作期间、甚至整个职业生涯视角来看,学习成长才是最重要的。 一、分享计划 前一年确定了每人每年做两次分享,对大家有点挑…

玩转OCR | 腾讯云智能结构化OCR初次体验

目录 一、什么是OCR(需要了解) 二、产品概述与核心优势 产品概述 智能结构化能做什么 举例说明(选看) 1、物流单据识别 2、常见证件识别 3、票据单据识别 4、行业材料识别 三、产品特性 高精度 泛化性 易用性 四、…

【每日学点鸿蒙知识】深色模式、Webview查看版本、window设置亮度、List缓存节点更新、预编译JS

1、HarmonyOS 深色模式下canvas绘制通过resourcemanager.getColor不是实际dark模式下的颜色? 深色模式下canvas绘制通过resourcemanager.getColor不是实际dark模式下的颜色 正确获取到资源resourcemanager. getColorSync($r(‘app.color.test_color’).id);深色模…

【Rust自学】9.3. Result枚举与可恢复的错误 Pt.2:传播错误、?运算符与链式调用

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 9.3.1. 传播错误 当你编写的函数中包含了一些可能会执行失败的调用时,除了在函数里处理这个错误,还可以把错误返回…

DC-2 靶场渗透

目录 环境搭建 开始渗透 扫存活 扫端口 扫服务 看一下80端口 看一下指纹信息 使用wpscan扫描用户名 再使用cewl生成字典 使用wpscan爆破密码 登陆 使用7744端口 查看shell rbash绕过 切换到jerry用户 添加环境变量 现在可以使用su命令了 提权 使用git提权 环…

CSS进阶和SASS

目录 一、CSS进阶 1.1、CSS变量 1.2、CSS属性值的计算过程 1.3、做杯咖啡 1.4、下划线动画 1.5、CSS中的混合模式(Blending) 二、SASS 2.1、Sass的颜色函数 2.2、Sass的扩展(extend)和占位符(%)、混合(Mixin) 2.3、Sass的数学函数 2.4、Sass的模块化开发 2.5、Sass…

Java实现UDP与TCP应用程序

三、Java实现UDP应用程序 3.1 InetAddress类 java.net.InteAddress类是用于描述IP地址和域名的一个Java类; 常用方法如下: public static InetAddress getByName(String host):根据主机名获取InetAddress对象public String getHostName()…

【车载网络】BUSOFF状态简述和制造

BUSOFF Bus Off,即总线掉线,当前该节点脱离总线,不参与通信,可以理解为当前节点的Controller关闭,节点无法在此期间收/发报文。 注意,此期间ECU依然在正常运行,所有的任务依然被OS调度 TEC&am…