☰
STM32-BKP和RTC
2026/10/9 2:42:46 网站建设 项目流程

Unix时间戳

  • 定义:从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒;
  • 时间戳存在一个秒计数器当中,秒计数器为32/64位的整型变量;
  • 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。

GMT

格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准。

UTC

协调世界时是一种以原子钟为基础的时间计量系统。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时时间与地球自转的协调一致。

时间戳转换

gmtime()是将秒计时器时间转换为伦敦日期时间,在操作系统上使用localtime()时,系统会直接匹配当地时区,将秒计数器时间转换为当地日期时间。(但是在STM32上,只会转换为伦敦时间)

BKP的简介

  • BKP(Backup Registers)备份寄存器
  • BKP可存储用户应用程序数据。当VDD(2~3.6V)被切断时,它仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或者系统复位、电源复位时,他们都不会倍复位;
  • TAMPER引脚产生的侵入事件将所有备份内容清除;
  • RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲;
  • 存储RTC时钟校准寄存器;
  • 用户数据存储容量:20字节(中、小容量)、84字节(大容量和互联型)

BKP的基本结构

RTC简介

  • RTC(Real Time Clock)实时时钟;
  • RTC是一个独立的定时器,可为系统提供时钟和日历的功能;
  • RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时;
  • 32位的可编程计数器,可对应Unx时间戳的秒计数器;
  • 20位的可编程预分频器,可适配不同频率的输入时钟;
  • 可选择三种RTC时钟源:
    HSE时钟除以128(通常为8MHz/128)
    LSE振荡器时钟(通常为32.768KHz)
    LSI振荡器时钟(40KHz)

RTC基本结构

RTC操作注意事项

  1. 执行以下操作将使能对BKP和RTC的访问:设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟;设置PWR_CR的DBP,使能对BKP和RTC的访问;
  2. 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1;
  3. 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC _CNT、RTC_ALR寄存器;
  4. 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中,仅当RTOFF状态位为1时,才可以写入RTC寄存器。

一、BKP的读写

本实验主要完成对BKP的读写操作。

程序

由于本实验比较简单,直接在主函数中写相关功能,main.c文件:

#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "key.h" uint8_t num;//创建变量用于存储按键状态 uint16_t WriteArray[]={0x1234,0x2345};//创建数组用于存储要写入到BKP中的数据 uint16_t ReadArray[2];//创建数组用于存储要从BKP中读取到的数据 int main(void) { OLED_Init(); key_init ();//各模块初始化 OLED_ShowString (1,1,"W:"); OLED_ShowString (2,1,"R:");//显示提示字符串 //打开APB1总线上BKP的时钟 RCC_APB1PeriphClockCmd (RCC_APB1Periph_BKP,ENABLE ); //打开APB1总线上PWR的时钟 RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR,ENABLE ); //将备份寄存器的电源打开(允许BKP运作) PWR_BackupAccessCmd(ENABLE); while(1) { //接收按键状态 num = Getnum (); //如果按键按下 if(num == 1) { //写入BKP的值加一 WriteArray[0]++; WriteArray[1]++; //将数值分别写入BKP中 BKP_WriteBackupRegister(BKP_DR1,WriteArray[0]); BKP_WriteBackupRegister(BKP_DR2,WriteArray[1]); //分别显示写入BKP的数据 OLED_ShowHexNum (1,3,WriteArray[0],4); OLED_ShowHexNum (1,8,WriteArray[1],4); } //读取BKP中的数据 ReadArray[0] = BKP_ReadBackupRegister(BKP_DR1); ReadArray[1] = BKP_ReadBackupRegister(BKP_DR2); //显示从BKP中读取到的数据 OLED_ShowHexNum (2,3,ReadArray[0],4); OLED_ShowHexNum (2,8,ReadArray[1],4); } }

最终效果:按一下按键,写入BKP中的数据值加一,并在OLED上显示写入BKP的数据和从BKP中读取出来的数据。

注意事项:

  • STM32上的复位按键对写入BKP的数据无复位作用,想要清除BKP中的数据需要同时断掉VDD和VBAT;

二、实时时钟

本实验用STM32单片机实现实时时钟。

程序

先对RTC部分代码进行编写,MyRTC.h文件:

#include "stm32f10x.h" // Device header #include <Time.h> uint16_t TimeArray[]={2026,9,23,20,50,30};//创建数据用于设置初始时间和存储后续读取的时间 //对函数提前进行声明 void RTC_SetTime(void); void MyRTC_Init(void) { //打开APB1总线上BKP的时钟 RCC_APB1PeriphClockCmd (RCC_APB1Periph_BKP ,ENABLE ); //打开APB1总线上PWR的时钟 RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR ,ENABLE ); //打开PWR对BKP的使能 PWR_BackupAccessCmd(ENABLE); //用备份寄存器中的数据进行判断,使计时能够达到掉电不丢失,不重复初始化 if(BKP_ReadBackupRegister(BKP_DR1) != 0x77) { //打开外部低速时钟 RCC_LSEConfig(RCC_LSE_ON); //等待外部低速时钟起振 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); //配置RTC的时钟为外部低速时钟 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //使能RTC时钟 RCC_RTCCLKCmd(ENABLE); //等待时钟对齐 RTC_WaitForSynchro(); //等待上一个任务完成 RTC_WaitForLastTask(); //对RTC频率进行分频,设置为秒计时器 RTC_SetPrescaler(32768 - 1); //等待上一个任务完成 RTC_WaitForLastTask(); //设置初始时间 RTC_SetTime(); //向BKP中写入特定数据,用于判断,保证函数值初始化一次 BKP_WriteBackupRegister(BKP_DR1,0x77); } //如果系统掉电,但是备用电源VBAT通电 else { //等待时钟对齐 RTC_WaitForSynchro(); //等待上一个任务完成 RTC_WaitForLastTask(); } } //函数:对当前时间进行设置 void RTC_SetTime(void) { //创建time_t类型变量,用于存储计时秒数 time_t Cnt; //创建时间结构体,用于存储关于时间的结构化数据 struct tm Time_Date; //将数组中的值对应传输给时间结构体的对应成员 Time_Date.tm_year = TimeArray[0] - 1900;//C语言标准规定年份偏差为1900,需要手动校正 Time_Date.tm_mon =TimeArray[1] - 1;//月份相差1个月,需要手动校正 Time_Date.tm_mday =TimeArray[2]; Time_Date.tm_hour =TimeArray[3]; Time_Date.tm_min =TimeArray[4]; Time_Date.tm_sec =TimeArray[5]; //将结构化时间数据转化为时间戳的秒计时,这里需要进行时区的校正 Cnt = mktime (&Time_Date) - 8 * 60 * 60; //对时间进行设置 RTC_SetCounter(Cnt); //等待上一个任务完成 RTC_WaitForLastTask(); } //函数:读取时间 void RTC_ReadTime(void) { //创建time_t类型变量,用于存储计时秒数 time_t Cnt; //创建时间结构体,用于存储关于时间的结构化数据 struct tm Time_Date; //将时间戳时间读取出来赋值给Cnt,这里需要进行时区的校正 Cnt = RTC_GetCounter() + 8 * 60 * 60; //使用localtime()函数将秒计数转换为格式化时间数据 Time_Date = *localtime(&Cnt); //用数组按顺序分别将年、月、日等时间数据存储起来 TimeArray[0] = Time_Date.tm_year + 1900; TimeArray[1] = Time_Date.tm_mon + 1; TimeArray[2] = Time_Date.tm_mday; TimeArray[3] = Time_Date.tm_hour; TimeArray[4] = Time_Date.tm_min; TimeArray[5] = Time_Date.tm_sec; }

MyRTC.h文件:

#ifndef _MYRTC_H #define _MYRTC_H extern uint16_t TimeArray[6]; void MyRTC_Init(void); void RTC_SetTime(void); void RTC_ReadTime(void); #endif

最后在主函数中对RTC部分函数进行调用,miain.c文件:

#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "MyRTC.h" int main(void) { OLED_Init(); MyRTC_Init();//各模块初始化 OLED_ShowString (1,1,"Date:xxxx xx xx"); OLED_ShowString (2,1,"Hour:xx xx xx");//显示提示字符串 while(1) { //不断读取时间 RTC_ReadTime (); //显示读取到的时间数据 OLED_ShowNum (1,6,TimeArray[0],4); OLED_ShowNum (1,11,TimeArray[1],2); OLED_ShowNum (1,14,TimeArray[2],2); OLED_ShowNum (2,6,TimeArray[3],2); OLED_ShowNum (2,9,TimeArray[4],2); OLED_ShowNum (2,12,TimeArray[5],2); } }

最终效果:在OLED上显示出实时时间,并且在不断更新,当VDD断开时,实时时钟还是会继续计时。

注意事项

  • 对时间的转换需要使用相关函数,必须声明头文件<Time.h>;
  • 对时间的校正,如果是单纯的Unix秒计数器时间校正,需要使用1970进行年份校正,但是C语言标准规定使用1900进行年份校正;
  • STM32标准库中 localtime() 函数转换出来的时间是伦敦时间,不是当地时间,需要自己计算时区来手动校正,最好在设置时间函数和读取时间函数中的秒计数器处进行校正,这样不用考虑进位的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询