12:STM32---RTC实时时钟

目录

一:时间相关

1:Unix时间戳

2: UTC/GMT

3:时间戳转化

二:BKP

1:简历

2:基本结构

三: RTC

1:简历

2: 框图

3:RTC基本结构

4:RTC操作注意

四:案例

A:读写备份寄存器

1:连接图

2: 步骤

 3: 代码 

B:实时时钟

1:连接图

2:函数介绍 

3:代码


一:时间相关

1:Unix时间戳

        Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒

        时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量

        世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间

2: UTC/GMT

         GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准

         UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致

3:时间戳转化

        C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换

二:BKP

1:简历

        BKP(Backup Registers)备份寄存器

        BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位

        TAMPER引脚产生的侵入事件将所有备份寄存器内容清除

        RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲

        存储RTC时钟校准寄存器 用户数据存储容量:     

                20字节(中容量和小容量)/ 84字节(大容量和互联型)

2:基本结构

        BKP处于后备区域,  后备区域的作用:  当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电;  当VDD主电源上电时,后备区域供电会由VBAT切换到VDD,  也就是主电源有电时,VBAT不会用到,这样可以节省电池电量

        BKP里主要有 : 数据寄存器、控制寄存器、状态寄存器和RTC时钟校准寄存器,       其中数据寄存器是主要部分,用来存储数据的,  每个数据奇存器都是16位的,   一个数据奇存器可以存2个字节,  对于小的来说里面有DR1、DR2、一直到、DR10一共10个数据寄存器;    对于大容量和互联型里面有DR11~DR42的数据寄存器

        可以从PC13位置的TAMPERS脚引入一个检测信号器, 当TAMPER产生上升沿或者下降沿时, 清除BKP所有的内容,以保证安全-----对应简历中的第三条

        时钟输出,  可以把RTC的相关时钟,  从PC13位置的RTC引脚输出出去,供外部使用,  其中,输出校准时钟时,  再配合这个校准寄存器,可以对RTC的误差进行校准

三: RTC

1:简历

        RTC(Real Time Clock)实时时钟

        RTC是一个独立的定时器,可为系统提供时钟和日历的功能 RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时

        32位的可编程计数器,可对应Unix时间戳的秒计数器

        20位的可编程预分频器,可适配不同频率的输入时钟

        可选择三种RTC时钟源:     

                HSE时钟除以128(通常为8MHz/128)     

                LSE振荡器时钟(通常为32.768KHz)     

                LSI振荡器时钟(40KHz)

2: 框图

3:RTC基本结构

        3个时钟,选择一个当作RTCCLK,  之后RTCCLK先通过预分频器,对时钟进行分频.

        余数寄存器(DIV) : 是一个自减计数器,存储当前的计数值.

        重装寄存器 : 是计数目标,决定分频值, 分频之后,得到1Hz的秒计数信号,  通向32位计数器一秒自增一次

        3个信号可以触发中断,  分别是秒信号、计数器溢出信号和闹钟信号,  三个信号先通过中断输出控制,  三个信号先通过中断输出控制,  使能的中断才能通向NVIC

4: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寄存器

四:案例

A:读写备份寄存器

1:连接图

2: 步骤

  1: 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟     

  2: 设置PWR_CR的DBP,使能对BKP和RTC的访问

 3: 代码 

简单的实现BKP的功能 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"

void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
	{
		Delay_ms(20);
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
		Delay_ms(20);
		KeyNum = 1;
	}
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
	{
		Delay_ms(20);
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
		Delay_ms(20);
		KeyNum = 2;
	}
	
	return KeyNum;
}


/*
 执行以下操作将使能对BKP和RTC的访问:     

        1: 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟     

        2: 设置PWR_CR的DBP,使能对BKP和RTC的访问

*/
uint16_t ArrayWrite[]={0x1234,0x5678};
uint16_t ArrayRead[2];
uint16_t KeyNum;
int main(void)
{	
	Key_Init();
	OLED_Init();
	//1: 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟     
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	//2: 设置PWR_CR的DBP,使能对BKP和RTC的访问
	PWR_BackupAccessCmd(ENABLE);
	
	OLED_ShowString(1,1,"W:");
	OLED_ShowString(3,1,"R:");
	
	while (1)
	{
		KeyNum=Key_GetNum();
		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);
			OLED_ShowHexNum(1,8,ArrayWrite[1],4);

		}
		ArrayRead[0]=BKP_ReadBackupRegister(BKP_DR1);
		ArrayRead[1]=BKP_ReadBackupRegister(BKP_DR2);
		OLED_ShowHexNum(3,3,ArrayRead[0],4);
		OLED_ShowHexNum(3,8,ArrayRead[1],4);
	
	}
	
}

 执行以下操作将使能对BKP和RTC的访问:     

        1: 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟     

        2: 设置PWR_CR的DBP,使能对BKP和RTC的访问

B:实时时钟

1:连接图

2:函数介绍 

在stm32f10x_rcc.h的文件中-----时钟相关的函数

void RCC_LSEConfig(uint8_t RCC_LSE);

void RCC_LSICmd(FunctionalState NewState);

void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
 

void RCC_RTCCLKCmd(FunctionalState NewState)

RCC_LSEConfig : 配置外部低速时钟(LSE)

RCC_LSICmd : 配置内部低速时钟(LSI)

RCC_RTCCLKConfig: 这个函数用来选择RTCCLK的时钟源 ,  实际上就是配置PPT的数据选择器

RCC_RTCCLKCmd : 使能--开启或者关闭RTC时钟

时钟在选择完毕后 , 需要获取标志位,等待标志完成后在操作

	RCC_LSEConfig(RCC_LSE_ON);//选择外部低速时钟
	
	while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET);//LSE准备ok了

在stm32f10x_rcc.h的文件中-----获取标志位函数

FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);

在stm32f10x_rtc.h的文件中-----进入RTC配置模式

void RTC_EnterConfigMode(void)

 这个函数作用: 置CRL的CNF为1,进入配置模式

        必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器

在stm32f10x_rtc.h的文件中-----退出RTC配置模式

void RTC_ExitConfigMode(void)

作用 : 就是把CNF位清零 

在stm32f10x_rtc.h的文件中-----CNT计数器相关

uint32_t RTC_GetCounter(void)
 

void RTC_SetCounter(uint32_t CounterValue)

RTC_GetCounter :  获取RTC计数器值

RTC_SetCounter :  写入CNT的值

在stm32f10x_rtc.h的文件中-----预分频器

void RTC_SetPrescaler(uint32_t PrescalerValue)

 RTC_SetPrescaler : 写入预分频器的值----这个值会写入到预分频器的PRL重装寄存器中,  用来配置预分频器的分频系数

在stm32f10x_rtc.h的文件中-----写入闹钟的值

void RTC_SetAlarm(uint32_t AlarmValue)

在stm32f10x_rtc.h的文件中-----读取预分频器中的DIV余数寄存器

 uint32_t  RTC_GetDivider(void);

余数奇存器是一个自减计数器 ,  获取余数奇存器的值,一般是为了得到更细致的时间

在stm32f10x_rtc.h的文件中-----等待完成操作

void RTC_WaitForLastTask(void);

void RTC_WaitForSynchro(void);

RTC_WaitForLastTask : 等待上次操作完成 

        对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器

 RTC_WaitForSynchro :  等待同步----清除RSF标志位,然后循环,直到RSF为1

        若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1

3:代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYRTC.h"

uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};
void MYRTC_init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	PWR_BackupAccessCmd(ENABLE);
	
	if(BKP_ReadBackupRegister(BKP_DR1)!=0xA5A5)
	{
		//LSE的频率是32.768KHz,也就是32768Hz
		RCC_LSEConfig(RCC_LSE_ON);//选择外部低速时钟
		
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET);//LSE准备ok了
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//配置RTC时钟RTCCLK
		RCC_RTCCLKCmd(ENABLE);//开启RTC时钟
		
		RTC_WaitForLastTask();//等待上次操作完成 
		RTC_WaitForSynchro();//等待同步时钟
		
		//写预分频器
		RTC_SetPrescaler(32768-1);
		RTC_WaitForLastTask();
		
		RTC_SetCounter(1672588795); //写入CNT
		RTC_WaitForLastTask();
		
		BKP_WriteBackupRegister(BKP_DR1,0xA5A5);
	
	}
	else
	{
			
		RTC_WaitForLastTask();//等待上次操作完成 
		RTC_WaitForSynchro();//等待同步时钟
		
	}

	
}

void MYRTC_Settime(void)
{
	//内部的定义typedef unsigned int time_t; /* date/time in unix secs past 1-Jan-70 */	
	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; //mktimer日期类型的时间数据类型--转化为--秒计数器数据类型
	RTC_SetCounter(time_cnt);//写入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;
}




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, RTC_GetDivider(), 10);
	}
}

猜你喜欢

转载自blog.csdn.net/m0_74739916/article/details/133084178