一给 发表于 2025-1-4 19:51:35

STM32-BKP备份寄存器&RTC及时时钟

一、原理

Unix:

https://i-blog.csdnimg.cn/direct/7d9cce023ca64de08a1fb7e98aac8045.png
 一些体系是利用32bit有符号数存储,现实范围为-2,147,483,648到2,147,483,647‌即https://latex.csdn.net/eq?2%5E%7B31%7D-1~https://latex.csdn.net/eq?-2%5E%7B31%7D
经过计算int32数据会在2038年1月19日溢出,可以看到转换的为北京时间。
https://i-blog.csdnimg.cn/direct/19d363e475be4cf0bba9ebd246939506.pnghttps://i-blog.csdnimg.cn/direct/e478c0da13944356a85d1af7ecf653f6.png

STM32的时间戳为无符号时间戳。
我们需要把秒计数器的时间通过计算得到秒技术其对应的时间,然后根据时区举行偏移(思量到品平年闰年,大月小月闰月等)。
可以根据c语言官方函数直接计算:
https://i-blog.csdnimg.cn/direct/d9f54c7a11a84faea9f5a232e9970d9b.png

 UTC、GMThttps://i-blog.csdnimg.cn/direct/b9582caf7c6f4760b85d2f03f542ea06.png

 GMT是之前的时间尺度,UTC是计算了偏移量的现行尺度。中国一样寻常利用GMT+8/UTC+8。Unix时间戳没有闰秒,即和谐世界时间的功能,所以大概秒数会偏差。
 时间戳和日期举行转换(数据类型):

https://i-blog.csdnimg.cn/direct/3e776c5aa888425f941560d4a9ed3276.png
 time_t现实上是int64类型,用来存储秒计数值

 https://i-blog.csdnimg.cn/direct/587106b9cef647ef93dac6eb829dddf5.png
 tm类型为界说日期的布局体:struct tm

其中year为从1900年的第几年(最小应该为70);mon月份从0开始;wday表现周几;yday表现每年的第几天;isdst是否利用夏令时,1表现用,0不用,-1表现不知道。
https://i-blog.csdnimg.cn/direct/61687199391a45b9b2ad1ed1e03595c7.png
 夏令时为在炎天的某段时间将时间提前一个小时。https://i-blog.csdnimg.cn/direct/a5fcd53e3fc443ca98dcffdd6889e775.png
 现实利用:
https://i-blog.csdnimg.cn/direct/320b77953f5244daae2c8c23ce203358.png
 mktime函数原理,通过输入的年代日时分秒计算,其他参数会主动计算回填,可以通过此函数主动计算星期。
https://i-blog.csdnimg.cn/direct/dd74882d706f4ebba5a0e37e68d14b05.png
https://i-blog.csdnimg.cn/direct/93379bb9fc314867a66c8ce61ccfe79c.png
 strftime函数参数(char *c,size_length,const *char,const struct tm*),其中const *char为格式字符串。函数利用为,将const struct tm*的内容通过const *char格式化字符存入长度为size_length的数组char *c中。https://i-blog.csdnimg.cn/direct/915a3d7ddb9842bf807646c720a82de3.png
https://i-blog.csdnimg.cn/direct/27e930a1a2cb4ab6b8c358d412f651bb.png  其他函数:

https://i-blog.csdnimg.cn/direct/96ff300dc112459fbab56c7b3b42b5d7.png






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

1、BKP原理:

BKP寄存器数据需要VBT保持供电来举行掉电不丢失,现实利用方式和Flash类似。手册建议VBT无外部供电时接到VDD并上100nf的滤波电容。
https://i-blog.csdnimg.cn/direct/21697bed8c084ade9d3e22f4e3cedc19.png
 TAMPER在STM32F103C8T6中在PC13。可以外接上拉电阻和开关接地,做掩护措施,接收到低电平清除寄存器内容。主电源断电后,侵入检测仍然有效。RTC校准时钟可以对RTC时钟举行校准。存储RTC时钟校准寄存器可以配合RTC校准时钟对RTC举行校准。
https://i-blog.csdnimg.cn/direct/a96f79e1ab0a47dd9a8eb0db88b1fe56.png
2、BKP的基本布局:

https://i-blog.csdnimg.cn/direct/3ef4909e32e34c63a8f02a345ad10ef9.png
3、STM32的RTC外设

https://i-blog.csdnimg.cn/direct/b7a209b701c64988a938a64bbe7f0f58.png
STM32的RTC类似DS1302外置及时时钟。RTC输入时钟具有20bit的分配器,即可分配1-https://latex.csdn.net/eq?2%5E%7B20%7D-1的分频。
RTC框图:

https://i-blog.csdnimg.cn/direct/1797b0d5430c4b26a9601b5258817d0a.png
灰色部门为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有三个泉源时钟。

https://i-blog.csdnimg.cn/direct/78272cb696da40909d058a55f7740d3d.pnghttps://i-blog.csdnimg.cn/direct/6ae654be25cb4176b3882aa82f8112c0.png
32.768=https://latex.csdn.net/eq?2%5E%7B15%7D可直接经过分频1Hz。硬件电路计数器也方便举行计数溢出得到频率信号。一样寻常利用LSE。
4、RTC基本布局

https://i-blog.csdnimg.cn/direct/3ffedb7fa1c94dd8bfc67c69db20614f.png
5、电路

https://i-blog.csdnimg.cn/direct/8958461500c540bcb9f1bcd2ed7c1bd2.png
https://i-blog.csdnimg.cn/direct/6ae19476af1b4a27bc2cce14567a34c3.png

 https://i-blog.csdnimg.cn/direct/52dfa02a6dbf4d1e95effb3f8f2123f0.png
 https://i-blog.csdnimg.cn/direct/f4c91b0eb89f420e84d87dbeef01871f.png
https://i-blog.csdnimg.cn/direct/0aaea7567eec4f5ea57c47cfd3213ef5.png CR2032纽扣电池,印制面为正极。
6、操作注意事项

https://i-blog.csdnimg.cn/direct/3932beba354f405e9046f0b020bd8601.png


[*] 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数据是否掉电保存。(不保存数据)
https://i-blog.csdnimg.cn/direct/ea00cb3136ff47f4b5e88b0dc0707e52.pnghttps://i-blog.csdnimg.cn/direct/743332a15499472f9643aa82c5fe95ae.png
main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

uint16_t Data={0x01,0x02,0x03,0x04};//写入的数据
uint16_t GetData;//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);//数据写入,在做STM32下电测试时,写入代码注释
        BKP_WriteBackupRegister(BKP_DR2,Data);
        BKP_WriteBackupRegister(BKP_DR3,Data);
        BKP_WriteBackupRegister(BKP_DR4,Data);
       
        OLED_ShowString(1,1,"BKP:");
       
        GetData = BKP_ReadBackupRegister(BKP_DR1);//数据读出
        GetData = BKP_ReadBackupRegister(BKP_DR2);
        GetData = BKP_ReadBackupRegister(BKP_DR3);
        GetData = BKP_ReadBackupRegister(BKP_DR4);
       
        OLED_ShowHexNum(2,1,GetData,2);
        OLED_ShowHexNum(2,4,GetData,2);
        OLED_ShowHexNum(2,7,GetData,2);
        OLED_ShowHexNum(2,10,GetData,2);
       
        while(1){
               
                Delay_ms(200);
        }
        return 0;
}
2、RTC时钟

https://i-blog.csdnimg.cn/direct/b6940c083edd4b398c51f469c08a57d7.png
时间表现,如果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范围为https://latex.csdn.net/eq?-2%5E%7B31%7D~https://latex.csdn.net/eq?2%5E%7B31%7D-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为https://latex.csdn.net/eq?2%5E%7B31%7D,如上,所以正数可以到达https://latex.csdn.net/eq?2%5E%7B31%7D-1,负数可以到达https://latex.csdn.net/eq?-2%5E%7B31%7D。


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

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



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: STM32-BKP备份寄存器&RTC及时时钟