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操作注意事项
- 执行以下操作将使能对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寄存器。
一、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() 函数转换出来的时间是伦敦时间,不是当地时间,需要自己计算时区来手动校正,最好在设置时间函数和读取时间函数中的秒计数器处进行校正,这样不用考虑进位的问题。