STM32实时时钟(RTC)功能详解
STM32 的实时时钟(RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC 模块和时钟配置系统 (RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域 (BKP) 的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。
备份寄存器(BKP)
备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域 里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或 电源复位时,他们也不会被复位。 此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。 复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操 作。执行以下操作可以使能对备份寄存器和RTC的访问。
● 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
● 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。
二、RTC配置步骤
(1) 使能电源时钟和备份区域时钟
我们要访问 RTC 和备份区域就必须先使能电源时钟和备份区域时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
(2)取消备份区写保护
RTC 模块和时钟配置系统 (RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域 (BKP) 的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。
要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。 取消备份区域写保护的库函数实现方法是:
PWR_BackupAccessCmd(ENABLE); //使能 允许RTC和后备寄存器访问
(3) 复位备份区域。(清空备份寄存器)
当TAMPER引脚上的信号从0变成1或者从1变成0(取决于备份控制寄存器BKP_CR的TPAL位), 会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。 然而为了避免丢失侵入事件,侵入检测信号是边沿检测的信号与侵入检测允许位的逻辑与,从 而在侵入检测引脚被允许前发生的侵入事件也可以被检测到。
BKP_DeInit();//复位备份区域
(4)开启外部低速振荡器
RCC_LSEConfig(RCC_LSE_ON);
(5)等待LSE准备就绪
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{}
(6) 选择 RTC 时钟,并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟(RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128)
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
(7)设置 RTC 的分频,以及配置 RTC 时钟
在开启了 RTC 时钟之后,我们要做的是设置 RTC 时钟的分频数,通过 RTC_PRLH 和RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置 RTC 的允许配置位(RTC_CRH 的 CNF 位),设置时间(其实就是设置RTC_CNTH 和 RTC_CNTL两个寄存器)。
对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器.
a.进行配置要先等待
查阅固件函数库用户手册可知:对于RTC的配置(设置分频等)基本需要在调用函数前先调用RTC_WaitForLastTask(),等待标志位RTOFF被设置。如下RTC_SetPrescaler函数
b.设置秒中断允许
RTC_ITConfig(RTC_IT_SEC, ENABLE);
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
c.配置之前要先允许配置
RTC_EnterConfigMode();/// 允许配置
d.设置分屏
RTC_SetPrescaler(32768 - 1); //设置RTC预分频器,预分频后的计数频率为1Hz
RTC_WaitForLastTask(); //等待上一次操作完成
e.退出配置模式
RTC_ExitConfigMode(); //退出配置模式
void RTC_Configuration(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
NVIC_InitTypeDef NVIC_InitStructure={0};
/* Enable the RTC Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();/// 允许配置
RTC_SetPrescaler(32767); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(1972,1,2,1,1,1); //设置时间
RTC_ExitConfigMode(); //退出配置模式
}
三、设置时间(起始时间)
设置时间实际上就是设置 RTC 的计数值,时间与计数值之间是需要换算的。库函数中设置 RTC 计数值的方法是:
mktime()函数头文件是time.h
struct tm Set_time={0,8,16,8,4-1,2024-1900};
//void RTC_SetCounter(uint32_t CounterValue)//最后在配置完成之后
// RTC_SetCounter(1712563329);
RTC_SetCounter(mktime(&Set_time)); //根据结构体转成s数
//设置时间
void Time_Adjust(void)
{
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Change the current time */
// RTC_SetCounter(1712563329);
RTC_SetCounter(mktime(&Set_time)); //根据结构体转成s数
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
四、备份操作
在退出配置模式更新配置之后我们在备份区域 BKP_DR1 中写入 0X5050 代表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5050 来决定是不是要配置。接着我们配置 RTC 的秒钟中断,并进行分组。
往备份区域写用户数据的函数是
这个函数的第一个参数就是寄存器的标号了,这个是通过宏定义定义的。 比如我们要往
BKP_DR1 写入 0x5050,方法是:
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
同时,有写便有读,读取备份区域指定寄存器的用户数据的函数是
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
五、 编写中断服务函数
uint8_t Second_Flag=0;
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
/* Clear the RTC Second interrupt */
RTC_ClearITPendingBit(RTC_IT_SEC);
Second_Flag = 1;
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
}
作者:膽小