一、Unix时间戳
简介
UCT/GMT
时间戳转换
函数 | 作用 |
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*); | 日期时间转换为字符串(自定义格式) |
二、BKP/RTC
BKP简介
20字节(中容量和小容量)/ 84字节(大容量和互联型)
BKP基本结构
STM32为中容量设备,因此数据寄存器有20个字节,也就是图中数据寄存器部分只有DR1~DR10,
RTC简介
HSE时钟除以128(通常为8MHz/128)
LSE振荡器时钟(通常为32.768KHz)
LSI振荡器时钟(40KHz)
RTC结构
RTC_ALR闹钟计数器,用于设置闹钟,在寄存器中设置一个数字,当CNT的值跟ALR中的值一致时,就发送一个闹钟信号,这个闹钟信号可以用于触发中断或者使STM32退出待机模式
中断部分可以由三种方式触发:RTC_Second在CNT左侧每秒触发一次中断,RTC_Overflow是在CNT溢出后产生中断,RTC_Alarm是由闹钟信号触发的中断
基本结构图:
RTC注意事项
1.开启PWR和BKP的时钟 2.使用PWR使能BKP和RTC的访问
设置PWR_CR的DBP,使能对BKP和RTC的访问
三、读写备份寄存器&实时时钟
读写BKP
部分库函数解释:
//复位BKP里的数据
void BKP_DeInit(void);
//用于配置侵入Tamp检测的有效电平,即高/低电平触发
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);
//是否开启侵入检测
void BKP_TamperPinCmd(FunctionalState NewState);
//时钟输出功能的配置,可以选择在BKP引脚上输出RTC校准时钟、闹钟脉冲和秒脉冲
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
//设置RTC校准值
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);
//写备份寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
//读备份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
//备份寄存器访问使能
void PWR_BackupAccessCmd(FunctionalState NewState);
BKP配置步骤:
1.开启PWR、BKP时钟
2. 设置PWR_CR的DBP,使能对BKP和RTC的访问
3.写入读取
代码部分:
因为需要执行的操作很少所以本节代码不进行封装
#include "Key.h"
#include "OLED.h"
uint8_t KeyNum;
uint16_t ArrayWrite[]={0x1234,0x5678};
uint16_t ArrayRead[2];
int main(void)
{
OLED_Init();
KEY_Init();
OLED_ShowString(1,1,"W:");
OLED_ShowString(2,1,"R:");
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);
PWR_BackupAccessCmd(ENABLE);
while(1)
{
KeyNum=KEY_TAP();
if(KeyNum==1){
ArrayWrite[0]++;
ArrayWrite[1]++;
BKP_WriteBackupRegister(BKP_DR1,ArrayWrite[0]);
BKP_WriteBackupRegister(BKP_DR2,ArrayWrite[1]);
OLED_ShowHexNum(1,3,ArrayWrite[0],4);//显示写入DR1的值
OLED_ShowHexNum(1,8,ArrayWrite[1],4);//显示写入DR2的值
}
ArrayRead[0]=BKP_ReadBackupRegister(BKP_DR1);//读出DR1
ArrayRead[1]=BKP_ReadBackupRegister(BKP_DR2);//读出DR2
OLED_ShowHexNum(2,3,ArrayRead[0],4);
OLED_ShowHexNum(2,8,ArrayRead[1],4);
}
}
实时时钟
封装MyRTC.c函数,参考RTC基本结构图进行配置:
1.开启PWR、BKP时钟
2. 设置PWR_CR的DBP,使能对BKP和RTC的访问
3.开启RTC时钟,此处我们计划使用LSE外部低速时钟作为系统时钟,因此开启LSE时钟
4.配置RTCCLK指定LSE作为RTCCLK
5.等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1,等待上一次操作完成
6.配置预分频器,给PRL一个合适的输出值确保输出给计数器的时钟是1Hz
7.配置CNT的值给一个初始时间
8.根据需要开启闹钟和中断
部分库函数解释:
//配置LSE,开启LSE
void RCC_LSEConfig(uint8_t RCC_LSE);
//配置LSI,开启LSI,在外部时钟不起振的情况下可以使用这个时钟
void RCC_LSICmd(FunctionalState NewState);
//配置RTCCLK时钟源
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
//启动RTCCLK
void RCC_RTCCLKCmd(FunctionalState NewState);
//获取标志位
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
//进入RTC配置模式,配置模式中配置PRL/CNT/ARL
void RTC_EnterConfigMode(void);
//退出配置模式
void RTC_ExitConfigMode(void);
//获取cnt计数器的值
uint32_t RTC_GetCounter(void);
//写入预分频器
void RTC_SetPrescaler(uint32_t PrescalerValue);
//写入闹钟值
void RTC_SetAlarm(uint32_t AlarmValue);
//读取预分频器中的DIV余数寄存器,为了获取更细致的时间
uint32_t RTC_GetDivider(void);
//等待上一次操作完成
void RTC_WaitForLastTask(void);
//等待同步
void RTC_WaitForSynchro(void);
配置部分代码:
#include "stm32f10x.h" // Device header
#include <time.h>
uint16_t MyRTC_Time[]={2023,1,1,23,59,55};//定义全局变量存放年月日时分秒
void MyRTC_SetTime(void);
void MyRTC_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);
PWR_BackupAccessCmd(ENABLE);
if(BKP_ReadBackupRegister(BKP_DR1)!= 0xA5A5)//利用BKP判断是否完全断电,不完全断电就不进行初始化防止时间重置
{
RCC_LSEConfig(RCC_LSE_ON);//LSE开启
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);//等待LSE开启完毕
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//时钟选择
RCC_RTCCLKCmd(ENABLE);
//等待同步
RTC_WaitForSynchro();
//等待上一次操作完成
RTC_WaitForLastTask();
RTC_SetPrescaler(32768-1);//库函数自动进入配置模式,LSE是32.768kHz,要分成1Hz
RTC_WaitForLastTask();
MyRTC_SetTime();//写入时间
BKP_WriteBackupRegister(BKP_DR1,0xA5A5);//将BKP_DR1写为0xA5A5用于下次检测
}
else//防止意外进行等待
{
//等待同步
RTC_WaitForSynchro();
//等待上一次操作完成
RTC_WaitForLastTask();
}
}
//写入时间
void MyRTC_SetTime(void)
{
//设定结构体对应数组关系
time_t time_cnt;
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;//日期时间到秒数的转换,同时进行时区偏移
RTC_SetCounter(time_cnt);
RTC_WaitForLastTask();
}
//读取时间
void MyRTC_ReadTime(void)
{
time_t time_cnt;
struct tm time_date;
time_cnt=RTC_GetCounter()+8*60*60;//进行时区偏移
time_date=*localtime(&time_cnt);//返回值是指针,所以先取内容
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;
}
主函数:
#include "stm32f10x.h" // Device header
#include "Delay.h" //使用延时函数时提前引入
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MyRTC.h"
uint8_t KEY_num;
int main(void)
{
OLED_Init();
MyRTC_Init();
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();
OLED_ShowNum(1,6,MyRTC_Time[0],4);
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);
OLED_ShowNum(4,6,(32767-RTC_GetDivider())/32767.0*999,10);//将DIV转换为0~999毫秒
}
}