本文的大部分内容来自B站up主 江协科技, 此文只供本人学习记录用途, 侵删
一、BKP
- BKP(Backup Registers)备份寄存器
- BKP可用于存储用户应用程序数据。当VDD(2.03.6V)电源被切断,他们仍然由VBAT(1.83.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位
- TAMPER引脚产生的侵入事件将所有备份寄存器内容清除(防抄板的)
- RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
- 存储RTC时钟校准寄存器
- 用户数据存储容量: 20字节(中容量和小容量)/ 84字节(大容量和互联型)
直接看到BKP代码的使用
注意这里BKP里的数据需要给VBAT供电, 当电源断电时, VBAT给BKP供电, 保存数据
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); //开启PWR时钟(必备流程),知道原因回来补
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE); //开启BKP时钟
PWR_BackupAccessCmd(ENABLE); //使能后备区域的访问
BKP_WriteBackupRegister(BKP_DR1, 0xa9); //向DR1写入0xa9
uint8_t data = BKP_ReadBackupRegister(BKP_DR1); //读出DR1的数据
二、Unix时间戳
- Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒
- 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量世界上
- 所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间
- 本质上就是一个32位的数, 存放的值直接就能转化成年月日时分秒
- C语言的time.h模块提供了时间获取和时间戳转换的相关函数
- 可以方便地进行秒计数器、日期时间和字符串之间的转换
这是转化的框图, 一目了然
三、RTC
- RTC(Real Time Clock)实时时钟RTC是一个独立的定时器,可为系统提供时钟和日历的功能
- RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.03.6V)断电后可借助VBAT(1.83.6V)供电继续走时32位的
- 可编程计数器,可对应Unix时间戳的秒计数器
- 20位的可编程预分频器,可适配不同频率的输入时钟
- 可选择三种RTC时钟源:
- HSE时钟除以128(通常为8MHz/128)
- LSE振荡器时钟(通常为32.768KHz)
- LSI振荡器时钟(40KHz)
stm32的四个晶振 :
- H代表高速, L代表低速, E代表外部. I代表内部 于是有四种组合:
- HSI 高速内部时钟信号
- HSE 高速外部时钟信号
- LSI 低速内部时钟信号
- LSE 低速外部时钟信号
VBAT的电路和LSE的晶振 硬件接法 :
这个金属色的晶振(12mHz)是HSE的时钟源 , 这个尼龙色的晶振(32.768kHz)是LSE的时钟源 :
- 执行以下操作将使能对BKP和RTC的访问:
- 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟
- 设置PWR_CR的DBP,使能对BKP和RTC的访问
- 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
- 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器
四、常用配置代码
这里使用LSI作为RTCCLK的时钟来源
RTC的基本结构
代码(记得勾选Use MisroLIB):
#include "stm32f10x.h" // Device header
#include "time.h"
uint16_t MYRTC_Time[6];//extern出去的时钟数组Time[0]-Time[5]对应 年 月 日 时 分 秒
//初始化
void MYRTC_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); //开启PWR时钟(必备流程),知道原因回来补
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE); //开启BKP时钟
PWR_BackupAccessCmd(ENABLE); //使能后备区域的访问
RCC_LSICmd(ENABLE); //开启LSI
while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY)!=SET);//等待LSI就绪
//这里向BKP_DR1写入0x1234标志位 防止每次复位都重新给给RTC的CNT写值, 才能实现掉电不丢失时间
if(BKP_ReadBackupRegister(BKP_DR1)!=0x1234) {
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); //配置RTCCLK的时钟源
RCC_RTCCLKCmd(ENABLE); //使能RTCCLK
//这两行写不写都行, 写上上个保险
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上次写入完成(每次写RTC都调用一下)
//想给RTCCLK 1Hz 这里把4kHz的LSI分频40000次
RTC_SetPrescaler(40000-1);
RTC_WaitForLastTask(); //等待上次写入完成(每次写RTC都调用一下)
RTC_SetCounter(1695133871); //设置初始时间(RTC中的CNT, 这里写入的是一个时间戳)
RTC_WaitForLastTask();//等待上次写入完成(每次写RTC都调用一下)
BKP_WriteBackupRegister(BKP_DR1, 0x1234); //向DR1写入0x1234
}
else{
//上保险
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上次写入完成(每次写RTC都调用一下)
}
}
//更新时间数组
void MYRTC_UpdateTime(){
time_t timestamp=RTC_GetCounter();//拿到RTC中CNT的值
RTC_WaitForLastTask(); //等待上次写入完成(每次写RTC都调用一下)
struct tm * time; //时间结构体指针 (time.h中的struct tm)
time=localtime(×tamp); //将时间戳转化为 struct tm *
MYRTC_Time[0] = time->tm_year + 1900; //年(从1900开始算)
MYRTC_Time[1] = time->tm_mon + 1; //月(从0开始算)
MYRTC_Time[2] = time->tm_mday; //日
MYRTC_Time[3] = time->tm_hour + 8; //时(+8倒时差)
MYRTC_Time[4] = time->tm_min; //分
MYRTC_Time[5] = time->tm_sec; //秒
}
//设置时间戳
void MYRTC_SetTime(uint32_t Time){
RTC_SetCounter(Time); //设置时间(RTC中的CNT, 这里写入的是一个时间戳)
RTC_WaitForLastTask(); //等待上次写入完成(每次写RTC都调用一下)
}