一.RTC基本信息
1.Unix时间戳
Unix时间戳定义: 从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒。
时间戳存储在一个秒计数器中,秒计数器为32位/64位的整形变量,32位到2038年就装不下了
世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间
GMT格林尼治标准时间 是一种以地球自转为基础的时间计量系统。他将地球自转一周的时间间隔等分为24小时,以此确定计数标准
UTC协调世界时以原子钟为基础的事件计量系统
2.RTC定义
RTC 是一个独立的定时器,可谓系统提供时钟和日历功能
RTC和时钟配置系统 处于后备区域,系统复位时数据不清零,VDD断电后可借助VBAT供电继续走时间,如果没有备用电源主电源掉电数据维持不了
32位的可编程计数器: 可对应Unix时间戳的秒计数器
20位的可编程预分频器: 可配置不同频率的输入时钟
三种RTC时钟源: HSE时钟除以128(通常为8MHz/128)
LSE振荡器时钟(通常为32.768KHz)
LSI振荡器时钟(40KHz)
HSE =高速外部时钟信号
HSI =高速内部时钟信号
LSE =低速外部时钟信号
LSI=低速内部时钟信号
3.操作注意事项
对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状态位,判断寄存器是否为更新状态。当RTOFF状态为是1时,才可以写入RTC寄存器
二.RTC基本流程
开启PWR,BKP时钟
PWR_BackupAccessCmd();//使能PWR
RCC_LSEConfig();//配置LSE(低速时钟),并使用RCC_LSE_ON打开
RCC_GetFlagStatus(RCC_FLAG_LSERDY);//等待LSE标志位置1
RCC_RTCCLKConfig();//配置RTC时钟源,选择LSE低速时钟源
RCC_RTCCLKCmd();//使能RTC时钟
RTC_WaitForSynchro();//RTC等待同步函数
RTC_WaitForLastTask();//等待上一步完成函数
RTC_SetPrescaler(32768 - 1);//配置预分频器满程-1
RTC_WaitForLastTask();//等待上一步完成函数
RTC_SetCount();//设置秒寄存器时间
RTC_WaitForLastTask();//等待上一步完成函数
三.代码实现
main.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,"Data: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,5,RTC_GetCounter(),10);//显示秒计数器
OLED_ShowNum(4,1,RTC_GetDivider(),10);//显示余数计数器
}
}
MyRTC.c
#include "stm32f10x.h" // Device header
#include <time.h>
#include "MyRTC.h"
uint16_t MyRTC_Time[] = {2023,1,1,23,59,55};//定义一个年月日数组
void MyRTC_Init (void)
{
//开启时钟
RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR ,ENABLE);//开启PWR时钟
RCC_APB1PeriphClockCmd (RCC_APB1Periph_BKP ,ENABLE);//开启BKP时钟
//使能PWR
PWR_BackupAccessCmd (ENABLE);//开启PWR访问备份配置寄存器
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)//判断读取备份寄存器标志位
//开启LSE低速外部时钟
{
RCC_LSEConfig (RCC_LSE_ON);//打开低速外部时钟
while(RCC_GetFlagStatus (RCC_FLAG_LSERDY) != SET);//等待LSE低速外部时钟置1
//配置RTC时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//时钟源选择LSE低速外部时钟源
RCC_RTCCLKCmd(ENABLE);//开启时钟源
//等待同步函数
RTC_WaitForSynchro();//同步函数等待
RTC_WaitForLastTask ();//等待上一个操作完成
//配置预分频器
RTC_SetPrescaler (32768 - 1);//预分频1KHz
RTC_WaitForLastTask ();//等待上一个操作完成
MyRTC_SetTime();//将不断刷新秒计数器
BKP_WriteBackupRegister(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;//年,从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;//将time_date结构体年月日数值转换成秒计数器
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 ;//年,从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