目录
STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解
1 什么是STM32的后备区域
分割线*
2.1 BKP备份寄存器简介
2.2 BKP备份寄存器基本结构
2.3 BKP外设头文件 bkp.h介绍
2.4 读写 BKP备份寄存器 操作步骤
2.5 编写 读写备份寄存器
5.1 文件介绍
main.c
分割线*
3.1 什么是Unix时间戳
3.2 C语言中时间戳转换函数
2.1 C语言提供的time.h函数介绍
2.2 时间戳转换图
3.3 RTC简介
3.4 RTC时钟选择
3.5 RTC框图
3.6 RTC基本框图
3.7 RTC硬件电路
3.8 RTC头文件介绍
3.9 使用 RTC实时时钟 操作步骤
3.10 编写RTC实时时钟显示年月日时分秒
3.10.1 文件介绍
MyRTC.c
MyRTC.h
main.c
STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解
1 什么是STM32的后备区域
STM32 的后备区域是芯片内部的一个特殊区域。
特点和作用:
- 后备区域可以在主电源VCC断电之后由备用电源VBA提供供电。
- 后备区域存储的数据不会因为复位而重置
- 但是如果包括主电源和VBAT都断电了。那么备用区域也会清除,因为他们的存储器本质是RAM存储器,掉电丢失。
后备区域有什么:
- BKP备份寄存器
- RTC实时时钟
分割线*
下面开始BKP部分
2.1 BKP备份寄存器简介
BKP(Backup Registers)备份寄存器(后备寄存器)
它位于后备区域。
-
BKP可用于存储数据。
-
存储特性:
- 当VDD(2.0~3.6V)电源(主电源)被切断,后备区域仍然由VBAT备用电源(1.8~3.6V)维持供电。
- 并且系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位
- 但是如果备用电源VBAT和主电源VCC都断电了,就会清除数据,因为BKP本质是RAM存储器。掉电丢失数据
-
STM32的TAMPER引脚
他可以产生的侵入事件可以将所有备份寄存器内容清除
- TAMPER是用于引入检测信号(可以是或上升沿/下降沿)的,当发生入侵时,将清除BKP所有内容,并申请中断。
- 并且他是由备用电源供电,主电源断电后侵入检测仍然有效,以保证数据安全
-
BKP的RTC引脚可以输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
-
BKP存储RTC时钟校准寄存器
-
STM32后备区域的供电特性:
- 当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电。
- 当VDD主电源上电时,后备区域供电会自动从VBAT切换到VDD。
-
BKP数据存储容量:
- 20字节(中容量和小容量)/ 84字节(大容量和互联型)
-
手册建议:
- 如果没有外部电池,建议VBAT引脚接到VDD,就是VBAT和主电源接到一起,并且再连接一个100nf的滤波电容
2.2 BKP备份寄存器基本结构
其中橙色部分就是后备区域,BKP处于后备区域。但后备区域不止有BKP。
STM32后备区域的特性:
- 当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电。
- 当VDD主电源上电时,后备区域供电会自动从VBAT切换到VDD。
数据寄存器:
- 每个寄存器有16位(可存储2个字节)。
- 中小容量设备有DR1~DR10,总共10个数据寄存器,每个寄存器存储2个字节,总容量为20字节。
- 大容量和互联型设备有42个数据寄存器(DR)。
TAMPER引脚:
- 用于引入检测信号(上升沿/下降沿),清除BKP所有内容以保证数据安全。
时钟输出:
- 可以将RTC相关时钟从PC13位置的RTC引脚输出出去,供外部使用。
- 输出校准时钟时,可以配合校准寄存器对RTC的误差进行校准。
2.3 BKP外设头文件 bkp.h介绍
void BKP_DeInit(void);
- 恢复缺省配置(用于清空BKP所有BKP寄存器)
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);
- 配置TAMPER引脚的有效电平
void BKP_TamperPinCmd(FunctionalState NewState);
- 是否开启侵入检测功能
void BKP_ITConfig(FunctionalState NewState);
- 是否开启中断
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
- 时钟输出功能配置(在RTC引脚上输出时钟信号、RTC校准时钟、RTC闹钟脉冲、秒脉冲)
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);
- 设置RTC校准值
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
- 写BKP备份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
- 读BKP寄存器
FlagStatus BKP_GetFlagStatus(void);
查看标志位
void BKP_ClearFlag(void);
清除标志位
ITStatus BKP_GetITStatus(void);
查看中断标志位
void BKP_ClearITPendingBit(void);
清除中断标志位
pwr.h中也有一个函数是需要用到的
void PWR_BackupAccessCmd(FunctionalState NewState);
- 备份寄存器访问使能(其实是设置BDP位)
2.4 读写 BKP备份寄存器 操作步骤
- 开启PWR和BKP的时钟
- PWR的开启函数为
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
它的目的是开启后备电源的时钟(可以理解为开启后备电源VBAT)。 - BKP的开启函数
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
他的目的是开启BKP外设的时钟,可以看到他们都是APB1总线下的。
- PWR的开启函数为
- 使能备份区域的访问
- 因为RTC实时时钟和BKP备份寄存器,都处于备份区域中。在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。它在PwR.h中 函数:
PWR_BackupAccessCmd(ENABLE);
- 因为RTC实时时钟和BKP备份寄存器,都处于备份区域中。在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。它在PwR.h中 函数:
- 读写操作
- 写入:
BKP_WriteBackupRegister(BKP_DR1,Data);
- 读出:
Data = BKP_ReadBackupRegister(BKP_DR1);
- 写入:
2.5 编写 读写备份寄存器
5.1 文件介绍
- main.c : 读写测试BKP备份寄存器是否在单主电源掉电时丢失数据。是否受到复位影响…
main.c
#include "stm32f10x.h" // Device header
#include "oled.h"
#include "Delay.h"
#include "Key.h"
/**
* 函 数:STM32 BKP备份寄存器的读写测试
* 参 数:无
* 返 回 值:无
* 注意事项:无
*/
uint16_t WriteArr[] = {0x0000, 0x0001};
uint16_t ReadArr[2] = { 0 };
int main()
{
OLED_Init();//初始化OLED;
KEY_Init();//初始化按键
//使能时钟电源和后备接口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
//使能备份访问控制
PWR_BackupAccessCmd(ENABLE);
OLED_ShowString(1,1,"W:");
OLED_ShowString(2,1,"R:");
while(1)
{
if(KEY_Get() == 1)
{
//写入BKP
BKP_WriteBackupRegister(BKP_DR1,WriteArr[0]);
BKP_WriteBackupRegister(BKP_DR2,WriteArr[1]);
//显示写入的值
OLED_ShowHexNum(1,3,WriteArr[0],4);
OLED_ShowHexNum(1,8,WriteArr[1],4);
WriteArr[0]++;
WriteArr[1]++;
}
//读取BKP
ReadArr[0] = BKP_ReadBackupRegister(BKP_DR1);
ReadArr[1] = BKP_ReadBackupRegister(BKP_DR2);
//显示读取的值
OLED_ShowHexNum(2,3,ReadArr[0],4);
OLED_ShowHexNum(2,8,ReadArr[1],4);
}
}
分割线*
下面开始RTC部分
3.1 什么是Unix时间戳
- Unix时间戳,最早是Unix系统使用的。所以叫Unix时间戳。
- 目前Windows、linux、安卓等系统都是实用的Unix时间戳。
- Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒。
-
GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准 (不过他不精准,因为地球自转是越来越慢的。所以又有了如下的规定)
(格林尼治是伦敦的一个区)
-
UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致
-
我们时间戳所说的1970年1月1日0时0分0秒。指的是伦敦伦敦时间的0时0秒。 其他的位置可以分为24个时区。每偏差一个时区,相应的时间就要加或减一个小时
-
- 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量
- 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间 比如Unix时间戳为0,代表伦敦的0时0分,那么北京就是+8得到8时0分
- C语言官方为我们提供了函数,可以直接把Unix时间戳转换为时间。
- 可以在百度直接搜索unix在线时间戳。
3.2 C语言中时间戳转换函数
2.1 C语言提供的time.h函数介绍
-
C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换
-
一些常用的函数如下:(粗细为更重要)
time_t time(time_t*); 获取系统时钟 struct tm* gmtime(const time_t*); 秒计数器转换为日期时间(格林尼治时间) struct tm* localtime(const time_t*); 秒计数器转换为日期时间(当地时间) time_t mktime(struct tm*); 日期时间转换为秒计数器(当地时间) char* ctime(const time_t*); 秒计数器转换为字符串(默认格式) char* asctime(const struct tm*); 日期时间转换为字符串(默认格式) size_t strftime(char*, size_t, const char*, const struct tm*); 日期时间转换为字符串(自定义格式)
2.2 时间戳转换图
这张图就清晰了显示了各个函数的作用:其实就是在各种数据类型之间进行转换。
- 首先先了解一下各个数据类型是什么
-
秒计数器数据类型:time_t,其实就是一个32或64位的有符号的整形数据。也就是64位的秒计数器(如果不特别声明,默认为64。)
-
日期时间数据类型:struct tm,这时一个结构体类型。成员如下:
- 秒、
- 分、
- 时、
- 月的几日、
- 月份、(需要+1偏移量)
- 年份(需要+1900偏移量)、
- 周某开始的星期几、从1月1日开始的第几天、
- 是否使用夏令时 (是为了鼓励夏天时早睡早起节约用电设计的,目前个别国家在使用)
-
字符串型数据类型:char*,用来指向一个表示时间的字符串
-
所以,在转换时,要根据函数的返回值,来进行相应赋值。比如struct tm* gmtime(const time_t*);
的返回值为Struct tm的指针类型。那么在赋值给自己定义的Struct tm xxx 结构体时,就要先用*取值,才能正确赋值
等等以此类推,需要根据不同的类型进行转换,
(time_t 整形、Struct tm 结构体 Char* 字符指针..)
3.3 RTC简介
RTC (Real Time Clock):实时时钟。
- RTC也位于STM32的后备区域中。可以由VBAT备用电源供电
- RTC是一个独立的定时器,可为系统提供时钟和日历的功能。
- RTC和时钟配置系统处于后备区域,系统复位时数据不清零,因为在VDD(2.0 - 3.6V)断电后可借助VBAT(1.8 - 3.6V)供电继续走时。
- RTCCLK可选择三种RTC时钟源:
- HSE时钟除以128(通常为8MHz/128)
- LSE振荡器时钟(通常为32.768KHz)
- LSI振荡器时钟(40KHz)
3.4 RTC时钟选择
时钟信号解释
-
HSE = 高速外部时钟信号
-
HSI = 高速内部时钟信号
-
LSI = 低速内部时钟信号
-
LSE = 低速外部时钟信号
-
H (High):高速,L (Low):低速,E (External):外部,I (Internal):内部
时钟选择
- 外部高速时钟:一般作为系统主时钟。供内部程序运行和主要外设使用。
- 内部低速时钟:主要用做看门狗等的使用。
- 外部低速时钟:这个时钟一般都是为了给RTC提供时钟的。
- 因为LSE外部低速时钟,才可以通过VBAT备用电池供电,
- 所以在主电源断电情况下LSE可以继续震荡,实现RTC主电源掉电继续走时的功能。
- 并且他的频率为32.768KHZ。经过2^15分频之后每次自然溢出时刚好为1s,也就是1HZ。
3.5 RTC框图
灰色填充区域均是后备区域。
可编程预分频器:
- RTC_CNT每秒自增,因此驱动计数器的时钟 TR_CLK 需要是1Hz的信号。 实际提供RTC模块的时钟(RTCCLK)频率较高,因此RTCCLK经过20位RTC预分频器(1~2^20分频),保证输出给计数器的频率为1Hz。
分频和计数:
- 输入时钟RTCCLK,经过RTC预分频器(由重装载寄存器RTC_PRL和余数寄存器RTC_DIV控制),计数器重装值ARR和CNT进行分频。
RTC_CNT:
- 可以作为Unix时间戳的秒计数器,再借用time.h的函数可以方便地得到年月日时分秒。
闹钟寄存器RTC_ALR:
- 32位寄存器,用来设置闹钟。设置闹钟时,将ALR写入一个秒数,当CNT的值等于ALR设定的闹钟值时,就会产生RTC_Alarm闹钟信号。 通过中断系统,在闹钟中断里执行相应操作。
- 同时,闹钟信号可以让STM32退出待机模式。 此外,这个闹钟值是一个定值,只能响一次。若想实现周期闹钟,在每次闹钟响过后,都需要重新设置下一次闹钟时间。
中断信号:
- RTC_Second(秒中断):来自于CNT的输入时钟。开启此中断后,程序会每秒进入一次RTC中断。
- RTC_Overflow(溢出中断):来自CNT右边,表示CNT的32位计数器计满溢出时触发一次中断。
- RTC_Alarm(闹钟中断):当计数器的值和闹钟值相等时触发中断,同时可以唤醒设备退出待机模式。
中断标志位和中断输出控制
- F(Flag) 结尾的是对应的中断标志位。
- IE(Interrupt Enable) 结尾的是中断使能。
- 最后三个信号通过一个或汇聚到NVIC中断控制器。
APB1总线和APB1接口:
- 程序读写寄存器的地方可以通过APB1总线完成,RTC位于APB1总线上的设备。 退出待机模式:唤醒机制
- 闹钟信号和WKUP引脚都可以唤醒设备,退出待机模式。
3.6 RTC基本框图
时钟来源配置:
- 最左边的RTCCLK时钟来源在RCC中配置,可以从三个时钟中选择一个作为RTCCLK。
时钟预分频:
- 选择的RTCCLK经过预分频器对时钟进行分频。
- 余数计数器是一个自减计数器,存储当前的计数值。
- 重装寄存器决定计数目标和分频值。
- 分频后得到1Hz的秒计数信号,传递给32位计数器,每秒自增一次。
闹钟设定:
- 32位计数器下有一个32位的闹钟值,可以设定闹钟时间。
中断信号触发:
- 右侧有三个信号可以触发中断:秒信号、计数器溢出信号和闹钟信号。
- 这三个信号通过中断输出控制,进行中断使能。
- 启用的中断信号才能传递到NVIC,然后向CPU申请中断。
程序配置步骤:
**配置数据选择器:**选择RTCCLK时钟来源。
**配置重装寄存器:**选择分频系数。
配置32位计数器:
- 进行日期时间的读写。
- 如果需要闹钟,配置32位闹钟值。
配置中断:
- 启用中断,再配置NVIC。
- 最后,编写对应的中断函数。
3.7 RTC硬件电路
- 备用电池供电
- **简单连接(左侧):**使用一个3V的电池B1直接连接到VBAT和GND。这样设计简单,但是电源冗余不高。
- **推荐连接(中间):**使用两个3V的电池B2和B3通过两个二极管D1和D2连接到VBAT和GND。这样设计增加了电源的可靠性,因为如果一个电池失效,另一个电池还能提供电源。电容C3(0.1uF)用于滤波,稳定电压。
- 外部低速晶振
- **晶振部分(中间):**使用一个32.768kHz的晶振(X1)连接到两个10pF的电容(C1和C2),并接地。这部分电路提供了一个稳定的时钟信号,通常用于RTC(实时时钟)功能。
- **连接到STM32单片机(右侧):**OSC32_IN和OSC32_OUT分别连接到STM32单片机的PC14和PC15引脚。
- STM32单片机连接
- **供电和地(右侧):**VDD和VSS分别是电源和地,VDD连接到电源正极,VSS连接到地。VBAT连接到备用电池供电部分的输出。
- **时钟信号(右侧):**PC14和PC15分别连接到外部低速晶振的OSC32_IN和OSC32_OUT。
- **其他引脚(右侧):**图中列出了STM32F103C8T6单片机的引脚配置,包括PA0到PA15,PB0到PB15等。这些引脚可以根据具体应用进行配置。
3.8 RTC头文件介绍
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
- RTC 中断使能:通过传入指定的中断类型
RTC_IT
和状态NewState
,实现对 RTC 中断的使能或失能控制。
void RTC_EnterConfigMode(void);
- 进入 RTC 配置模式:用于进入 RTC 的配置状态,以便进行相关参数的修改。
void RTC_ExitConfigMode(void);
- 退出 RTC 配置模式:在完成 RTC 配置操作后,使用此函数退出配置模式。
uint32_t RTC_GetCounter(void);
- 获取 RTC 计数器的值:返回 RTC 计数器的当前数值。
void RTC_SetCounter(uint32_t CounterValue);
- 设置 RTC 计数器的值:将 RTC 计数器设置为指定的数值
CounterValue
。
void RTC_SetPrescaler(uint32_t PrescalerValue);
- 设置 RTC 预分频的值:为 RTC 预分频设置特定的值
PrescalerValue
。
void RTC_SetAlarm(uint32_t AlarmValue);
- 设置 RTC 闹钟的值:设定 RTC 闹钟的触发值为
AlarmValue
。
uint32_t RTC_GetDivider(void);
- 获取 RTC 预分频分频因子的值:获取当前 RTC 预分频分频因子的数值。
void RTC_WaitForLastTask(void);
- 等待最近一次对 RTC 寄存器的写操作完成:确保之前对 RTC 寄存器的写入操作已经完成。
void RTC_WaitForSynchro(void);
- 等待 RTC 寄存器与 RTC 的 APB 时钟同步:等待 RTC 相关寄存器(如
RTC_CNT
、RTC_ALR
和RTC_PRL
)与 APB 时钟完成同步。
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
- 检查指定的 RTC 标志位设置与否:通过传入标志位
RTC_FLAG
,返回其状态。
void RTC_ClearFlag(uint16_t RTC_FLAG);
- 清除 RTC 的待处理标志位:清除指定的 RTC 标志位。
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
- 检查指定的 RTC 中断发生与否:根据传入的中断类型
RTC_IT
,判断中断是否发生。
void RTC_ClearITPendingBit(uint16_t RTC_IT);
- 清除 RTC 的中断待处理位:清除指定 RTC 中断的待处理位。
3.9 使用 RTC实时时钟 操作步骤
RTC使用时,一般都是和BKP一起使用的:
因为RTC在初始化时,需要配置其32位的时间戳计数器。
但每次复位或者主电源上电后,在执行主程序时,会重新初始化RTC的时间戳计数器。这就导致了RTC实时时钟本来能在备份电源的供给下计时,但是上电后又给我重新初始化覆盖了。导致不实时了!
所以需要在初始化程序中判断是否需要设置32位的时间戳计数器。
一般的方法是,在第一次初始化RTC时,顺便在BKP备份寄存器中写入特定值。在下一次初始化RTC时,对BKP备份寄存器的值进行判断。如果之前没有写入,那么就初始化。否则不初始化。
所以使用RTC实时时钟操作步骤如下:
-
执行以下操作将使能对BKP和RTC的访问:
-
开启PWR和BKP的时钟
- PWR的开启函数为
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
开启后备电源的时钟 - BKP的开启函数
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
开启BKP外设的时钟
- PWR的开启函数为
-
使能备份区域的访问
- 在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。
函数:
PWR_BackupAccessCmd(ENABLE);
-
-
判断是否需要初始化RTC见代码部分
-
开启LSE时钟
- 开启LSE时钟
RCC_LSEConfig(RCC_LSE_ON);
- 等待LSE时钟开启完毕
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
- 选择RTCCLK时钟为LSE
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
- 使能RTCCLK时钟
RCC_RTCCLKCmd(ENABLE);
- 开启LSE时钟
-
等待时钟同步
-
因为可能在恢复主电源之后,APB1总线刚刚恢复震荡频率,但是RTCCLK需要经过外部震荡源分频后才能有一次输出。如果直接读取,会导致读取不准确。 所以需要等待RTCCLK产生上升沿来激活更新一下时间戳计数器,这时APB1直接读取。才是准确的。 所以软件读取时必须等待RTCCLK来一个上升沿,使RTC_CRL寄存器中的**RSF位(寄存器同步标志)被硬件置1。**这时再读取RTC_CRL寄存器。才能正确读取。
函数:
RTC_WaitForSynchro();
(等待时钟同步)
-
-
等待写入完成
-
对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。
-
可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。 当RTOFF状态位是1时,才可以写入RTC寄存器
函数:
RTC_WaitForLastTask();
-
-
设置RTC预分频器
- 设置预分频器
RTC_SetPrescaler(32768 - 1);
- 等待写入完成
RTC_WaitForLastTask();
- 设置预分频器
-
写入前其实是需要设置RTC_CRL寄存器中的CNF位 (函数会帮我们自动完成,所以不需要)
- 在写入时,必须设置RTC_CRL寄存器中的CNF位, 使RTC进入配置模式后,才能写入RTC_PRL(预分频器)、RTC_CNT(时间戳计数器)、RTC_ALR(闹钟寄存器)寄存器
-
设置CNT时间戳计数器时间
(利用C语言中time.h来转换时间戳并写入,详见代码)
-
在BKP备份寄存器中写入特定数据。为下次复位或仅主电源断电后上电时判断是否初始化打下基础
3.10 编写RTC实时时钟显示年月日时分秒
3.10.1 文件介绍
- MyRTC.c 初始化RTC、通过C语言time.h函数来编写 时间转换时间戳、时间戳转换时间的函数
- MyRTC.h 函数声明
- main.c 测试MyRTC显示
MyRTC.c
#include "stm32f10x.h" // Device header
#include <time.h>
uint16_t MyRTC_Time[] = {2024, 8, 18, 23, 46, 0}; //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒
void MyRTC_SetTime(void); //函数声明
/**
* 函 数:RTC初始化
* 参 数:无
* 返 回 值:无
*/
void MyRTC_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启PWR的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //开启BKP的时钟
/*备份寄存器、RTC访问使能*/
PWR_BackupAccessCmd(ENABLE); //使用PWR开启对备份寄存器和RTC的访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) //通过写入备份寄存器的标志位,判断RTC是否是第一次配置
//if成立则执行第一次的RTC初始化
{
RCC_LSEConfig(RCC_LSE_ON); //开启LSE时钟
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //等待LSE准备就绪
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择RTCCLK来源为LSE
RCC_RTCCLKCmd(ENABLE); //RTCCLK使能
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上一次操作完成
RTC_SetPrescaler(32768 - 1); //设置RTC预分频器,预分频后的计数频率为1Hz
RTC_WaitForLastTask(); //等待上一次操作完成
MyRTC_SetTime(); //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置
}
else //RTC不是第一次配置
{
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上一次操作完成
}
}
/**
* 函 数:RTC设置时间
* 参 数:无
* 返 回 值:无
* 说 明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路
*/
void MyRTC_SetTime(void)
{
time_t time_cnt = 0; //定义秒计数器数据类型
struct tm time_date; //定义日期时间数据类型
time_date.tm_year = MyRTC_Time[0] - 1900; //将数组的时间赋值给日期时间结构体
time_date.tm_mon = MyRTC_Time[1] - 1;
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_cnt = mktime(&time_date) - 8 * 60 * 60; //调用mktime函数,将日期时间转换为秒计数器格式
//- 8 * 60 * 60为东八区的时区调整
RTC_SetCounter(time_cnt); //将秒计数器写入到RTC的CNT中
RTC_WaitForLastTask(); //等待上一次操作完成
}
/**
* 函 数:RTC读取时间
* 参 数:无
* 返 回 值:无
* 说 明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组
*/
void MyRTC_ReadTime(void)
{
time_t time_cnt; //定义秒计数器数据类型
struct tm time_date; //定义日期时间数据类型
time_cnt = RTC_GetCounter() + 8 * 60 * 60; //读取RTC的CNT,获取当前的秒计数器
//+ 8 * 60 * 60为东八区的时区调整
time_date = *localtime(&time_cnt); //使用localtime函数,将秒计数器转换为日期时间格式
MyRTC_Time[0] = time_date.tm_year + 1900; //将日期时间结构体赋值给数组的时间
MyRTC_Time[1] = time_date.tm_mon + 1;
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_ReadTime(void);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
MyRTC_Init(); //RTC初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Date:XXXX-XX-XX");
OLED_ShowString(2, 1, "Time:XX:XX:XX");
OLED_ShowString(3, 1, "CNT :");
OLED_ShowString(4, 1, "DIV :");
while (1)
{
MyRTC_ReadTime(); //RTC读取时间,最新的时间存储到MyRTC_Time数组中
OLED_ShowNum(1, 6, MyRTC_Time[0], 4); //显示MyRTC_Time数组中的时间值,年
OLED_ShowNum(1, 11, MyRTC_Time[1], 2); //月
OLED_ShowNum(1, 14, MyRTC_Time[2], 2); //日
OLED_ShowNum(2, 6, MyRTC_Time[3], 2); //时
OLED_ShowNum(2, 9, MyRTC_Time[4], 2); //分
OLED_ShowNum(2, 12, MyRTC_Time[5], 2); //秒
OLED_ShowNum(3, 6, RTC_GetCounter(), 10); //显示32位的秒计数器
OLED_ShowNum(4, 6, RTC_GetDivider(), 10); //显示余数寄存器
}
}