当前位置: 首页 > article >正文

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

一、原理

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=40000Hz
		RTC_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);//根据计数值换算成日期时间,将值传给NowTime
	UnixdataStructure.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)s
	uint8_t minutes;//(0-59)min
	uint8_t hours;//(0-23)h
	uint8_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.kler.cn/a/466054.html

相关文章:

  • 基于Arduino的FPV头部追踪相机系统
  • 深入解析 Linux 设备树中的引脚控制(pinctrl)二
  • PHP7和PHP8的最佳实践
  • 3D Object Detection和6D Pose Estimation有什么异同?
  • unity学习6:unity的3D项目的基本界面和菜单
  • [Qt] 输入控件 | Line | Text | Combo | Spin | Date | Dial | Slider
  • nginx高可用集群搭建
  • primevue的<Menu>组件
  • MYSQL------sql基础
  • 单元测试、系统测试和集成测试知识
  • 家政预约小程序07参与活动
  • 堆排序基础与实践:如何在Java中实现堆排序
  • VMware去虚拟化
  • halcon中图像处理及图像滤波
  • ElementUI中的el-select显示的不是对应label值
  • 【react】Redux的设计思想与工作原理
  • LeetCode1507 转变日期格式
  • c++不够用知识补充-using namespace
  • 经纬度噪点与误差处理的优化
  • Beamer-LaTeX学习(教程批注版)【1】
  • 【计算机网络】课程 作业一 搭建连续覆盖的办公网络
  • Conda 安装 Jupyter Notebook
  • 深入解析Reactive Programming框架在微服务中的最新应用
  • 鸿蒙NEXT开发中使用星闪服务
  • 线性代数考研笔记
  • 汽车燃油软件标定测试