STM32快速复习(九)RTC时钟模块

文章目录


前言

STM32 的实时时钟(RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC 模块和时钟配置系统 (RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域 (BKP) 的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。

unix时间戳: Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

STM32相较于51的外接时钟寄存器DS1302,STM32内部计时是没有年月日,时分秒寄存器的。STM32是利用单独的秒寄存器,以1970年1月1日为0,进行每秒自增的定时器计数。

当前为1723138563秒,转换后再加上1970年就是当前时间

闰秒:由于地球自转的影响,每天有一定的计数误差,闰秒规定,当误差大于0.9秒。当前计数分钟会出现61秒,然后下一分钟再从0开始计数。

这样减少了硬件的负担,但是增加软件转换的麻烦。于是STM32定义了time.h 头文件。专门用于转换时间。

(BKP)备份寄存器 是42个16位的寄存器(中容量,小容量为10个16位的寄存器,20字节),可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。 此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。 复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。

一、RTC是什么?RTC的工作原理?

RTC(Real Time Clock):实时时钟,是指可以像时钟一様输出实际时间的电子设备,一般会是集成电路,因此也称为时钟芯片。总之,RTC只是个能靠电池维持运行的32位定时器,并不像实时时钟芯片,读出来就是年月日。RTC就只一个定时器而已,掉电之后所有信息都会丢失,因此我们需要找一个地方来存储这些信息,于是就找到了备份寄存器(BKP)。因为它掉电后仍然可以通过纽扣电池供电,所以能时刻保存这些数据。 STM32 的实时时钟(RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

TIPS:RTC 模块和时钟配置系统(RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护

下图中RTC数据有三条,RTC_Second(秒中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断),以及最下方的WKUP(唤醒,常用于PA0引脚),用来进行中断控制,增加了闹钟和休眠唤醒等功能。

RTC_PRL:计数目标(写入n,就是n+1分频);

RTC_DIV:自减计数器,每来一个输入时钟,DIV的值自减一次,减到0后再来一个时钟,从PRL获取到重装值继续自减;

RTC_CNT:Unix时间戳的秒计数器;

RTC_ALR:闹钟寄存器, 当CNT=ALR时会产生RTC_Alarm闹钟信号,就能进入右边的中断系统;同时也可以让STM32退出待机模式;

中断:RTC_Second,秒中断:开启后每秒进一次RTC中断;RTC_Overflow,溢出中断:CNT溢出;RTC_Alarm,闹钟中断:CNT=ALR时会触发中断,同时也可以让STM32退出待机模式;

图中浅灰色的部分都是属于备份域的,在VDD掉电时可在VBAT的驱动下继续运行.这部分仅包括RTC的分频器,计数器,和闹钟控制器.若VDD电源有效,RTC可以触发RTC_Second(秒中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断).从结构图可以看到到,其中的定时器溢出事件无法被配置为中断.如果STM32原本处于待机状态,可由闹钟事件或WKUP事件(外部唤醒事件,属于EXTI模块,不属于RTC)使它退出待机模式.闹钟事件是在计数器RTC_CNT的值等于闹钟寄存器RTC_ALR的值时触发的.

因为RTC的寄存器是属于备份域,所以它的所有寄存器都是16位的.它的计数RTC_CNT的32位由RTC_CNTL和RTC_CNTH两个寄存器组成,分别保存计数值的低16位和高16位.在配置RTC模块的时钟时,把输入的32768Hz的RTCCLK进行32768分频得到实际驱动计数器的时钟TR_CLK = RTCCLK/37768 = 1Hz,计时周期为1秒,计时器在TR_CLK的驱动下计数,即每秒计数器RTC_CNT的值加1(常用)

RTC只是一个时钟,但与RTC相连的有两个系统时钟,一个是APB1接口的PCLK1另一个是RTC时钟。这样,RTC功能也就分为两个部分:第一部分,APB1接口部分,与APB1总线相连,MCU也就是通过这条总线对其进行读写操作。另一部分,RTC核,由一系列可编程计数器组成,这部分又再细分为两个组件:预分频模块与32位可编程计数器。预分频模块用来产生最长为1秒的RTC时间基准,而32位的可编程的计数器可被初始化为当前的系统时间。

电路图简化:

流程(会按照流程写出代码即可):
1. 使能电源时钟和备份区域时钟
2. 取消备份区写保护
3. 复位备份区域,开启外部低速振荡器
4. 选择 RTC 时钟,并使能
5. 设置 RTC 的分频,以及配置 RTC 时钟
6. 更新配置,设置 RTC 中断分组
7. 编写中断服务函数

RTC 相关的库函数在文件 stm32f10x_rtc.c 和 stm32f10x_rtc.h 文件中, BKP 相关的库函数在

文件 stm32f10x_bkp.c 和文件 stm32f10x_bkp.h 文件中

二、库函数以及示例

1.标准库函数

1、RTC时钟源和时钟操作函数;

void RCC_RTCCLKConfig(uint32_t CLKSource);//时钟源选择;

void RCC_RTCCLKCmd(FunctionalState NewState);//时钟使能;

2、RTC初始化函数

ErrorStatus RTC_Init(RTC_InitTypeDef* RTC_InitStruct);

trypedef struct

{

uint32_t RTC_HourFormat;//小时格式:24/12

uint32_t RTC_AsynchPrediv;//异步分频系数

uint32_t RTC_SynchPrediv;//同步分频系数;

}RTC_InitTypeDef;

3、日历配置相关函数

ErrorStatus RTC_SetTime(uint32_t RTC_Format,RTC_TimeTypeDef* RTC_TimeStruct);数值当前时间

void RTC_GetTime(uint32_t RTC_Format,RTC_TimeTypeDef* RTC_TimeStruct);

ErrorStatus RTC_SetDate(uint32_t RTC_Format,RTC_Dae TypeDef* RTC_DataStruct);

void RTC_GetDate(uint32_t RTC_Format,RTC_Date TypeDef* RTC_DateStruct);

uint32_t RTC_GetSubSecond(void);

4、RTC闹钟相关函数

ErrorStatus RTC_AlarmCmd(uint32_t RTC_Alarm,FunctionalState NewState);

void RTC_SetAlarm();

void RTC_GetAlarm();

void RTC_AlarmSubSecondConfig();

uint32_t RTC_GetAlarmSubSecond(uint32_t RTC_Alarm);

5、RTC周期唤醒相关函数:

void RTC_WakeUpClockConfig();

void RTC_SetWakeUpCounter();

uint32_t RTC_GetWakeUpCounter(void);

RTC_WakeUpCmd(DISABLE);//关闭WAKEUP

6、RTC中断配置以及状态相关函数

void RTC_ITConfig();

FlagStatus RTC_GetFlgStatus(uint32_t RTC_FLAG);

void RTC_ClearFlag(uint32_t RTC_FLAG);

ITStatus RTC_GetITStatus(uint32_t RTC_IT);

void RTC_ClearITPendingBit();

7、RTC相关约束函数

void RTC_WriteProtectionCmd();//取消写保护

ErrorStatus RTC_EnterInitNode();//进入配hi模式,RTC_ISR_INITF位设置位1

void RTC_ExitInitMode(void);//退出初始化模式

8、其他函数

uint32_t RTC_ReadBackupRegister();

void RTC_WriteBackupRegister();

void RTC_ITConfig();

RTC_ITConfig 使能或者失能指定的 RTC 中断

RTC_EnterConfigMode 进入 RTC 配置模式

RTC_ExitConfigMode 退出 RTC 配置模式

RTC_GetCounter 获取 RTC 计数器的值

RTC_SetCounter 设置 RTC 计数器的值

RTC_SetPrescaler 设置 RTC 预分频的值

RTC_SetAlarm 设置 RTC 闹钟的值

RTC_GetDivider 获取 RTC 预分频分频因子的值

RTC_WaitForLastTask 等待最近一次对 RTC 寄存器的写操作完成

RTC_WaitForSynchro 等待 RTC 寄存器(RTC_CNT, RTC_ALR and RTC_PRL)与 RTC 的 APB 时钟同步

RTC_GetFlagStatus 检查指定的 RTC 标志位设置与否

RTC_ClearFlag 清除 RTC 的待处理标志位

RTC_GetITStatus 检查指定的 RTC 中断发生与否

RTC_ClearITPendingBit 清除 RTC 的中断待处理位

  1. 使能电源时钟和备份区域时钟

    前面已经介绍了,我们要访问 RTC 和备份区域就必须先使能电源时钟和备份区域时钟。

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

  2. 取消备份区写保护

    要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。 取消备份区域写保护的库函数实现方法是:

    PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问

  3. 复位备份区域,开启外部低速振荡器

    在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设置,当然这个

    操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。然后我们使能外部低速振荡器,注意这里一般要先判断 RCC_BDCR 的 LSERDY位来确定低速振荡器已经就绪了才开始下面的操作。

    BKP_DeInit();//复位备份区域

    RCC_LSEConfig(RCC_LSE_ON);// 开启外部低速振荡器

4.选择 RTC 时钟,并使能。

这里我们将通过 RCC_BDCR 的 RTCSEL 来选择选择外部 LSI 作为 RTC 的时钟。然后通过RTCEN 位使能 RTC 时钟。

RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟

对于 RTC 时钟的选择,还有 RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128两个,顾名思义,前者为 LSI,后者为 HSE 的 128 分频,这在时钟系统章节有讲解过。使能 RTC 时钟的函数是:

RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟

5.设置 RTC 的分频,以及配置 RTC 时钟。

在开启了 RTC 时钟之后,我们要做的就是设置 RTC 时钟的分频数,通过 RTC_PRLH 和RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置RTC 的允许配置位(RTC_CRH 的 CNF 位), 设置时间(其实就是设置 RTC_CNTH 和 RTC_CNTL两个寄存器)。 下面我们一一这些步骤用到的库函数:在进行 RTC 配置之前首先要打开允许配置位(CNF),库函数是:

RTC_EnterConfigMode();/// 允许配置

在配置完成之后,千万别忘记更新配置同时退出配置模式,函数是:

RTC_ExitConfigMode();//退出配置模式, 更新配置

设置 RTC 时钟分频数, 库函数是:

void RTC_SetPrescaler(uint32_t PrescalerValue);

这个函数只有一个入口参数,就是 RTC 时钟的分频数,很好理解。

然后是设置秒中断允许, RTC 使能中断的函数是:

void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);

这个函数的第一个参数是设置秒中断类型,这些通过宏定义定义的。 对于使能秒中断方法是:

RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断

下一步便是设置时间了,设置时间实际上就是设置 RTC 的计数值,时间与计数值之间是需要换算的。库函数中设置 RTC 计数值的方法是:

void RTC_SetCounter(uint32_t CounterValue)//最后在配置完成之后

6.更新配置,设置 RTC 中断分组。

在设置完时钟之后,我们将配置更新同时退出配置模式,这里还是通过 RTC_CRH 的 CNF

来实现。

RTC_ExitConfigMode();//退出配置模式,更新配置

在退出配置模式更新配置之后我们在备份区域 BKP_DR1 中写入 0X5050 代表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5050 来决定是不是要配置。接着我们配置 RTC 的秒钟中断,并进行分组。

往备份区域写用户数据的函数是:

void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);

这个函数的第一个参数就是寄存器的标号了,这个是通过宏定义定义的。 比如我们要往

BKP_DR1 写入 0x5050,方法是:

BKP_WriteBackupRegister(BKP_DR1, 0X5050);

同时,有写便有读,读取备份区域指定寄存器的用户数据的函数是

uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);

  1. 编写中断服务函数
    我们要编写中断服务函数,在秒钟中断产生的时候,读取当前的时间值,并显示到
    oled 模块上。

2.示例代码

代码如下(示例):

c 复制代码
//初始化配置
void MyRTC_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟
	
	/*备份寄存器访问使能*/
	PWR_BackupAccessCmd(ENABLE);				      //使用PWR开启对备份寄存器的访问
	
    //通过写入备份寄存器的标志位,判断RTC是否是第一次配置
	if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
	{
		RCC_LSEConfig(RCC_LSE_ON);							//开启LSE时钟
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);	//等待LSE准备就绪
		
        
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);				//选择RTCCLK来源为LSE
		RCC_RTCCLKCmd(ENABLE);								//RTCCLK使能
		
		RTC_WaitForSynchro();								//等待同步
		RTC_WaitForLastTask();								//等待上一次操作完成
		
		RTC_SetPrescaler(32768 - 1);	//设置RTC预分频器,预分频后的计数频率为1Hz
		RTC_WaitForLastTask();			//等待上一次操作完成
		
		MyRTC_SetTime();	//设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路
		
        //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置
		BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
	}
	else													//RTC不是第一次配置
	{
		RTC_WaitForSynchro();								//等待同步
		RTC_WaitForLastTask();								//等待上一次操作完成
	}
}
 
//设置时间
uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};
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;	//调用mktime函数,将日期时间转换为秒计数器格式
													//- 8 * 60 * 60为东八区的时区调整
	
	RTC_SetCounter(time_cnt);						//将秒计数器写入到RTC的CNT中
	RTC_WaitForLastTask();							//等待上一次操作完成
}

		  

江科大STM32例程

c 复制代码
#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_PWR, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
	
	PWR_BackupAccessCmd(ENABLE);
	
	if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
	{
		BKP_DeInit();
	
		RCC_LSEConfig(RCC_LSE_ON);
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
		
		RTC_SetPrescaler(32767);
		RTC_WaitForLastTask();
		
		MyRTC_SetTime();
		
		BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
	}
	else
	{
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
	}
}

/*
void MyRTC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
	
	PWR_BackupAccessCmd(ENABLE);
	
	if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
	{
		BKP_DeInit();
	
		RCC_LSICmd(ENABLE);
		while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
		RCC_RTCCLKCmd(ENABLE);
		
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
		
		RTC_SetPrescaler(40000 - 1);
		RTC_WaitForLastTask();
		
		MyRTC_SetTime();
		
		BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
	}
	else
	{
		RCC_LSICmd(ENABLE);
		while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
		RCC_RTCCLKCmd(ENABLE);
		
		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;
}

总结

RTC的工作流程通常分为初始化和运行两个阶段。在初始化阶段,我们需要配置RTC的时钟源、预分频器、日历寄存器等参数,并启用相应的中断或事件。在运行阶段,RTC会根据预设的时钟源和分频器递增计数器,同时更新日历信息,处理闹钟事件等。通过合理配置和管理,我们可以充分发挥RTC模块的功能和性能。

相关推荐
XINVRY-FPGA17 分钟前
XC3S1000-4FGG320I Xilinx AMD Spartan-3 SRAM-based FPGA
嵌入式硬件·机器学习·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
猫猫的小茶馆3 小时前
【ARM】ARM的介绍
c语言·开发语言·arm开发·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆3 小时前
【PCB工艺】数模电及射频电路基础
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·pcb工艺
点灯小铭3 小时前
基于单片机的智能药物盒设计与实现
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
梓德原3 小时前
【基础】详细分析带隙型稳压电路的工作原理
单片机·嵌入式硬件·物联网
国科安芯4 小时前
航天医疗领域AS32S601芯片的性能分析与适配性探讨
大数据·网络·人工智能·单片机·嵌入式硬件·fpga开发·性能优化
小李做物联网5 小时前
【物联网毕业设计】60.1基于单片机物联网嵌入式项目程序开发之图像厨房监测系统
stm32·单片机·嵌入式硬件·物联网
贝塔实验室6 小时前
新手如何使用Altium Designer创建第一张原理图(三)
arm开发·单片机·嵌入式硬件·fpga开发·射频工程·基带工程·嵌入式实时数据库
@good_good_study6 小时前
STM32 ADC多通道采样实验
stm32·单片机·嵌入式硬件
Darken037 小时前
什么是“位带”?;在STM32单片机中有什么作用?
stm32·单片机·嵌入式硬件