目录
STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解
[1 什么是STM32的后备区域](#1 什么是STM32的后备区域)
[2.1 BKP备份寄存器简介](#2.1 BKP备份寄存器简介)
[2.2 BKP备份寄存器基本结构](#2.2 BKP备份寄存器基本结构)
[2.3 BKP外设头文件 bkp.h介绍](#2.3 BKP外设头文件 bkp.h介绍)
[2.4 读写 BKP备份寄存器 操作步骤](#2.4 读写 BKP备份寄存器 操作步骤)
[2.5 编写 读写备份寄存器](#2.5 编写 读写备份寄存器)
[5.1 文件介绍](#5.1 文件介绍)
[3.1 什么是Unix时间戳](#3.1 什么是Unix时间戳)
[3.2 C语言中时间戳转换函数](#3.2 C语言中时间戳转换函数)
[2.1 C语言提供的time.h函数介绍](#2.1 C语言提供的time.h函数介绍)
[2.2 时间戳转换图](#2.2 时间戳转换图)
[3.3 RTC简介](#3.3 RTC简介)
[3.4 RTC时钟选择](#3.4 RTC时钟选择)
[3.5 RTC框图](#3.5 RTC框图)
[3.6 RTC基本框图](#3.6 RTC基本框图)
[3.7 RTC硬件电路](#3.7 RTC硬件电路)
[3.8 RTC头文件介绍](#3.8 RTC头文件介绍)
[3.9 使用 RTC实时时钟 操作步骤](#3.9 使用 RTC实时时钟 操作步骤)
[3.10 编写RTC实时时钟显示年月日时分秒](#3.10 编写RTC实时时钟显示年月日时分秒)
[3.10.1 文件介绍](#3.10.1 文件介绍)
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); //显示余数寄存器
}
}