Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量。
世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。
GMT : GMT(Greenwich Mean Time), 格林威治平时(也称格林威治时间)。
它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准。
UTC:UTC(Coodinated Universal Time),协调世界时,又称世界统一时间、世界标准时间、国际协调时间。
UTC 是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成。
对于我们目前使用的电子设备,都是通过联网来确认时间戳的。所以我们看到的时间都是由时间戳转换成日期的。
C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换。
第一个,time_t time(time_t*),获取系统时钟。
通过使用设备当前时间,获取一个时间戳。
第二个,struct tm* gmtime(const time_t*);秒计数器转换为日期时间(格林尼治时间)
这里需要注意的是struct tm 结构体类型成员year和mon
年是从1900年算起的,所以实际时间要加上1900;
月是从0开始的,所以要加上1;
时是由于计算是标准时间,所以要加上8才是东八区的标准时间。
第三个,time_t mktime(struct tm*);日期时间转换为秒计数器(当地时间)
由于STM32无法计算系统时钟,需要软件输入设置当前时间,所以也没有当地时间与标准时间的概念。
备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。(对于中小容量的产品,只能存储20个字节)他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
也就是说,只要有一方供电,保存的数据就不会丢失;
BKP控制寄存器用来管理侵入检测和RTC校准功能。
侵入检测:当TAMPER引脚上的信号从’0’变成’1’或者从’1’变成’0’(取决于备份控制寄存器BKP_CR的TPAL位),会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。
RTC校准:为方便测量, RTC时钟可以经64分频输出到侵入检测引脚TAMPER上。
这里只是简单介绍这两种功能而已。
实时时钟是一个独立的定时器。 RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后, RTC的设置和时间维持不变。
操作访问方法与BKP相同。
RTC时钟部分就是在后备区域内,上面连接着APB1总线进行读写的操作,
左边连接着RTC时钟源。
RTC核心部分是完全独立于APB接口的,需要通过软件,经过APB1接口访问RTC的预分频值,计数器值和闹钟值。
RTC预分频器会先预装重装载器,然后给到DIV,在RTCCLK时钟源进来的每一次,DIV以递减的方式,直到DIV为0时才将输出时钟传到CNT上;
CNT是一个计数器,用来存储时间戳;下面的ALR是闹钟寄存器,当CNT与ALR相等时,可以通过闹钟来产生中断;
这些产生的时钟信号经过控制寄存器进入NVIC中断控制器。
这部分是备用电池供电的电路,对于简单连接的就是直接连上一个3.3V供电的即可。
右边的是纽扣电池的连接方式。
执行以下操作将使能对BKP和RTC的访问:
设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟
设置PWR_CR的DBP,使能对BKP和RTC的访问(开启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寄存器
OLED代码链接入口
MyRTC.h
#ifndef __MYRTC_H__
#define __MYRTC_H__
#include "stm32f10x.h" // Device header
#include
typedef struct Time
{
int sec;
int min;
int hour;
int mday;
int mon;
int year;
}Time;
void MyRTC_Init(Time* t);
void MyRTC_SetTime(Time* t);
void MyRTC_ReadTime(Time* t);
#endif
MyRTC.c
#include "MyRTC.h"
//时间设置
void MyRTC_SetTime(Time* t)
{
time_t time_cnt;//计数器
struct tm time_date;//时钟日期
time_date.tm_year= t->year-1900;
time_date.tm_mon= t->mon-1;
time_date.tm_mday= t->mday;
time_date.tm_hour= t->hour;
time_date.tm_min= t->min;
time_date.tm_sec= t->sec;
//设置计数器
//日期转换为时间戳
time_cnt=mktime(&time_date)-8*60*60;
RTC_SetCounter(time_cnt);
RTC_WaitForLastTask();
}
void MyRTC_Init(Time* t)
{
//打开电源和后备接口的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
//开启RTC的使用
PWR_BackupAccessCmd(ENABLE);
// if(BKP_ReadBackupRegister(BKP_DR1)!=0x1111)
// {
//配置LSE
RCC_LSICmd(ENABLE);
while(!RCC_GetFlagStatus(RCC_FLAG_LSIRDY));
//RCCLK选择LSE
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
//开启RTC的时钟源
RCC_RTCCLKCmd(ENABLE);
//时钟同步
RTC_WaitForSynchro();
RTC_WaitForLastTask();
//设置分频器,写操作等待完成
RTC_SetPrescaler(32767);
RTC_WaitForLastTask();
//时间初始化设置
MyRTC_SetTime(t);
//非首次和完全断电给一个值
// BKP_WriteBackupRegister(BKP_DR1,0x1111);
// }
// else
// {
// RTC_WaitForSynchro();
// RTC_WaitForLastTask();
// }
}
//读取时间
void MyRTC_ReadTime(Time* t)
{
time_t time_cnt;//计数器
struct tm time_date;//时钟日期
//时间戳转换为日期
time_cnt=RTC_GetCounter()+8*60*60;
time_date=*localtime(&time_cnt);
t->year=time_date.tm_year+1900;
t->mon=time_date.tm_mon+1;
t->mday=time_date.tm_mday;
t->hour=time_date.tm_hour;
t->min=time_date.tm_min;
t->sec=time_date.tm_sec;
}
对于时钟的选择内部时钟LSI无法在断开电源和复位时进入后备区域;LSE外部时钟可以,我们可以设置一个值,只要是这个值说明就进入了后备区域。由于我的外部时钟晶振无法震荡,所以只能使用内部时钟,读者可以
修改为
并将那些注释解开即可。
这里进行写操作时,函数中就已经进入了配置模式了
这里输入的时钟是东八区的时间,而设置计数器为标准时间的时间戳,所以要进行减去8* 60* 60
等到读取时间时再加回来
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main()
{
Time time;
time.year=2023;
time.mon=1;
time.mday=1;
time.hour=23;
time.min=59;
time.sec=55;
OLED_Init();
MyRTC_Init(&time);
MyRTC_SetTime(&time);
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(&time);
OLED_ShowNum(1, 6, time.year, 4);
OLED_ShowNum(1, 11,time.mon, 2);
OLED_ShowNum(1, 14, time.mday, 2);
OLED_ShowNum(2, 6, time.hour, 2);
OLED_ShowNum(2, 9, time.min, 2);
OLED_ShowNum(2, 12, time.sec, 2);
OLED_ShowNum(3, 6, RTC_GetCounter(), 10);
OLED_ShowNum(4, 6, RTC_GetDivider(), 10);
}
}