BKP (Backup Registers)备份寄存器:
1、BKP可用于存储用户应用程序数据。当VDD (2.0~3.6V)电源被切断,他们仍然由VBAT (1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位TAMPER引脚产生的侵入事件将所有备份寄存器内容清除RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲存储RTC时钟校准寄存器。
2、用户数据存储容量:20字节(中容量和小容量)/84字节(大容量和互联型)
RTC (Real Time Clock)实时时钟
1、RTC是一个独立的定时器,可为系统提供时钟和日历的功能
2、RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT (1.8~3.6V)供电继续走时
3、 32位的可编程计数器,可对应Unix时间戳的秒计数器
4、 20位的可编程预分频器,可适配不同频率的输入时钟可选择三种RTC时钟源:
HSE时钟除以128(通常为8MHz/128)LSE振荡器时钟(通常为32.768KHz)LSI振荡器时钟(40KHz)(通常为32.768KHz时,才能提供VBAT (1.8~3.6V)供电)
第一种写法代码如下:
(在OLED中显示)
1、MyRTC.c
#include "stm32f10x.h" // Device header
#include "MyRTC.h"
#include <time.h>
uint16_t MyRTC_Time[ ] = {2023,7,25,20,58,59};
void MyRTC_Init(void)
{
// 使能PWR时钟,用于控制备份寄存器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 使能BKP时钟,用于控制备份寄存器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
// 允许访问备份寄存器
PWR_BackupAccessCmd(ENABLE);
// 如果备份寄存器中的值不等于0xA7A7,表示RTC还未初始化
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA7A7)
{
// 使能外部低速晶振(LSE)
RCC_LSEConfig(RCC_LSE_ON);
// 等待外部低速晶振就绪
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
// 将RTC时钟源配置为外部低速晶振
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
// 使能RTC时钟
RCC_RTCCLKCmd(ENABLE);
// 等待RTC寄存器同步
RTC_WaitForSynchro();
// 等待RTC最后的任务完成
RTC_WaitForLastTask();
// 设置RTC预分频器,使RTC时钟频率为1Hz
RTC_SetPrescaler(32768-1);
// 等待RTC最后的任务完成
RTC_WaitForLastTask();
// 设置RTC时间,可能是一个自定义的函数
MyRTC_SetTime();
// 将特定值(0xA7A7)写入备份寄存器,表示RTC已经初始化过
BKP_WriteBackupRegister(BKP_DR1, 0xA7A7);
}
else
{
// 如果备份寄存器中的值等于0xA7A7,表示RTC已经初始化过
// 等待RTC寄存器同步
RTC_WaitForSynchro();
// 等待RTC最后的任务完成
RTC_WaitForLastTask();
}
}
// RTC设置时间函数
void MyRTC_SetTime(void)
{
// 定义time_t类型的变量time_cnt,用于保存RTC的时间值
time_t time_cnt;
// 定义tm结构体类型的变量time_data,用于保存时间的年月日时分秒信息
struct tm time_data;
// 将MyRTC_Time数组中的年份减去1900,存入time_data的年份字段
time_data.tm_year = MyRTC_Time[0] - 1900;
// 将MyRTC_Time数组中的月份减1,存入time_data的月份字段
time_data.tm_mon = MyRTC_Time[1] - 1;
// 将MyRTC_Time数组中的日期存入time_data的日期字段
time_data.tm_mday = MyRTC_Time[2];
// 将MyRTC_Time数组中的小时存入time_data的小时字段
time_data.tm_hour = MyRTC_Time[3];
// 将MyRTC_Time数组中的分钟存入time_data的分钟字段
time_data.tm_min = MyRTC_Time[4];
// 将MyRTC_Time数组中的秒钟存入time_data的秒钟字段
time_data.tm_sec = MyRTC_Time[5];
// 使用mktime函数将time_data结构体转换成time_t类型的时间值,并减去8小时的偏移量(假设时区为UTC+8)
time_cnt = mktime(&time_data) - 8 * 60 * 60;
// 将计算得到的时间值设置为RTC的计数器值
RTC_SetCounter(time_cnt);
// 等待RTC最后的任务完成
RTC_WaitForLastTask();
}
// RTC读取时间函数
void MyRTC_ReadTime(void)
{
// 定义time_t类型的变量time_cnt,用于保存RTC的时间值
time_t time_cnt;
// 定义tm结构体类型的变量time_data,用于保存时间的年月日时分秒信息
struct tm time_data;
// 从RTC获取计数器的值,并加上8小时的偏移量(假设时区为UTC+8),存入time_cnt
time_cnt = RTC_GetCounter() + 8 * 60 * 60;
// 使用localtime函数将time_t类型的时间值转换成tm结构体类型的时间信息,并存入time_data
time_data = *localtime(&time_cnt);
// 将time_data结构体中的年份加上1900,存入MyRTC_Time数组的年份字段
MyRTC_Time[0] = time_data.tm_year + 1900;
// 将time_data结构体中的月份加1,存入MyRTC_Time数组的月份字段
MyRTC_Time[1] = time_data.tm_mon + 1;
// 将time_data结构体中的日期存入MyRTC_Time数组的日期字段
MyRTC_Time[2] = time_data.tm_mday;
// 将time_data结构体中的小时存入MyRTC_Time数组的小时字段
MyRTC_Time[3] = time_data.tm_hour;
// 将time_data结构体中的分钟存入MyRTC_Time数组的分钟字段
MyRTC_Time[4] = time_data.tm_min;
// 将time_data结构体中的秒钟存入MyRTC_Time数组的秒钟字段
MyRTC_Time[5] = time_data.tm_sec;
}
2、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
3、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.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,6,RTC_GetCounter(),10);
OLED_ShowNum(4,6,(32767-RTC_GetDivider())/32767.0*999,10);//0~999毫秒
}
}
(注:第一种写法在VBAT引脚接了一个3.3V,因为我这块板子没有纽扣电池之类的给VBAT引脚供电,一定要接3.3V嗷)
第二种写法代码如下:
(串口和OLED显示)
1、rtc.c
#include "stm32f10x.h" // Device header
#include "LED.h"
#include "sys.h"
#include "rtc.h"
#include "OLED.h"
#include "usart.h"
#include "stdio.h"
__IO uint32_t TimeDisplay = 0;
uint8_t const table_week[12] = {0,3,3,6,1,4,6,2,5,0,3,5};//月修正数据表
const uint8_t mon_table[12] = {31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表
struct RTC_TIME{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t week;
uint8_t hour;
uint8_t min;
uint8_t sec;
}rtc_timer = {2023,07,19,3,13,54,10};
void rtc_init(void)
{
//NVIC的配置
NVIC_Configuration();
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A4)
{
//通过判断 BKP_DR1是否是0xA5A5来识别是否是第一次系统上电
printf("RTC no yet configured...\r\n");
//配置RTC 设置初始时间
RTC_Configuration();
printf("RTC Configured....\r\n");
//设置初始时间:年 月 日 时 分 秒
RTC_Set(rtc_timer.year,rtc_timer.month,rtc_timer.day,rtc_timer.hour,rtc_timer.min,rtc_timer.sec);
BKP_WriteBackupRegister(BKP_DR1, 0xA5A4);
}
else
{
//看系统是否掉电
if(RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
printf("Power On Reset occurred...\r\n");
}
//看是否是复位引脚引起的复位
else if(RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
printf("External Reset occurred...\r\n");
}
printf("No need to configure RTC...");
RTC_WaitForSynchro();
//使能秒中断
RTC_ITConfig(RTC_IT_SEC,ENABLE);
RTC_WaitForLastTask();
}
//清除标志位
RCC_ClearFlag();
}
void RTC_Configuration(void)
{
uint32_t t;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP,ENABLE);
//允许访问BKP区
PWR_BackupAccessCmd(ENABLE);
//复位
BKP_DeInit();
//外部低速时钟 使能 LES
RCC_LSEConfig(RCC_LSE_ON);
//等待LSE时钟稳定
while((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && t<5000)
{
t++;
}
// 选择LSE作为时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能 RTC 时钟
RCC_RTCCLKCmd(ENABLE);
//等待RTC寄存器的时钟同步
RTC_WaitForSynchro();
//等待RTC寄存器上一次操作完成RTOFF标志位
RTC_WaitForLastTask();
//使能RTC秒中断
RTC_ITConfig(RTC_IT_SEC,ENABLE);
//等待RTC寄存器上一次操作完成RTOFF标志位
RTC_WaitForLastTask();
//设置RTC预分频器时间是1秒
RTC_SetPrescaler(32767);
//等待RTC寄存器上一次操作完成
RTC_WaitForLastTask();
}
//读出当前时间值 //返回值:0,成功;其他:代码错误;
uint8_t RTC_Get(void)
{
static uint16_t daycnt=0;
uint32_t timecount=0;
uint32_t temp=0;
uint16_t temp1=0;
timecount=RTC_GetCounter();
temp=timecount/86400; //得到天数(秒钟数对应的)24*3600S
if(daycnt!=temp)
{//超过一天了
daycnt=temp;
temp1=1970; //从1970年开始
while(temp>=365)
{
if(Is_Leap_Year(temp1))
{//是闰年
if(temp>=366)temp-=366;//闰年的秒钟数
else {temp1++;break;}
}
else temp-=365; //平年
temp1++;
}
rtc_timer.year=temp1;//得到年份
temp1=0;
while(temp>=28){//超过了一个月
if(Is_Leap_Year(rtc_timer.year)&&temp1==1){//当年是不是闰年/2月份
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}else{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
rtc_timer.month=temp1+1;//得到月份
rtc_timer.day=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
rtc_timer.hour=temp/3600; //小时
rtc_timer.min=(temp%3600)/60; //分钟
rtc_timer.sec=(temp%3600)%60; //秒钟
rtc_timer.week=RTC_Get_Week(rtc_timer.year,rtc_timer.month,rtc_timer.day);//获取星期
return 0;
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
uint8_t Is_Leap_Year(uint16_t year)
{
if(year%4==0)
{ //必须能被4整除
if(year%100==0)
{
if(year%400==0)
return 1;//如果以00结尾,还要能被400整除
else
return 0;
}
else
return 1;
}
else
return 0;
}
//按年月日计算星期(只允许1901-2099年)
//由RTC_Get调用
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day)
{
uint16_t temp2;
uint8_t yearH,yearL;
yearH=year/100;
yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7); //返回星期值
}
//写入时间与日期
//月份数据表
uint8_t RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec){ //写入当前时间(1970~2099年有效),
uint16_t t;
uint32_t seccount=0;
if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099
for(t=1970;t<syear;t++){ //把所有年份的秒钟相加
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++){ //把前面月份的秒钟数相加
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
RTC_SetCounter(seccount);//把换算好的计数器值写入
RTC_WaitForLastTask(); //等待写入完成
return 0; //返回值:0,成功;其他:错误代码.
}
void Time_Display(void)
{
OLED_Init();
RTC_Get();
printf("DATA: %0.2d-%0.2d-%0.2d\r\n",rtc_timer.year, rtc_timer.month,rtc_timer.day);
printf("Time: %0.2d:%0.2d:%0.2d\r\n",rtc_timer.hour,rtc_timer.min,rtc_timer.sec);
OLED_ShowString(1,1,"DATA:");
OLED_ShowNum(1,6,rtc_timer.year,4);//2023
OLED_ShowChar(1,10,'-');
OLED_ShowNum(1,11,rtc_timer.month,2);//07
OLED_ShowChar(1,13,'-');
OLED_ShowNum(1, 14,rtc_timer.day, 2);//18
OLED_ShowString(3,1,"Time:");
OLED_ShowNum(3,6,rtc_timer.hour,2);
OLED_ShowChar(3,8,':');
OLED_ShowNum(3,9,rtc_timer.min,2);
OLED_ShowChar(3,11,':');
OLED_ShowNum(3,12,rtc_timer.sec,2);
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
//使能RTC中断
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
//清除秒中断标志位
RTC_ClearITPendingBit(RTC_IT_SEC);
PCout(13) ^= 1;
TimeDisplay = 1;
RTC_WaitForLastTask();
}
}
2、rtc.h
#ifndef _RTC_H
#define _RTC_H
extern __IO uint32_t TimeDisplay;
void rtc_init(void);
void RTC_Configuration(void);
void Time_Display(void);
uint8_t RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec);
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day);
uint8_t RTC_Get(void);
void RTC_Configuration(void);
uint8_t Is_Leap_Year(uint16_t year);
uint8_t RTC_Get(void);
void NVIC_Configuration(void);
#endif
3、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "rtc.h"
#include "LED.h"
//uint8_t RxData;
int ok = 0;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// LED_Init();
Serial_Init();
rtc_init();
while (1)
{
//秒标志位 1
if(TimeDisplay == 1)
{
Time_Display();
TimeDisplay = 0;
}
}
}
第二种写法串口打印结果
这是第一种写法的视频嗷
OLED显示实时时间
第二种的也来咯
第二种写法效果图
欢迎在座的大佬们过来指正批评