【ARM Cortex-M开发实战指南(基础篇)】第16章 RTC

开发环境:

MDK:Keil 5.30
STM32CubeMX:V6.4.0
MCU:STM32F103ZET6

16.1 RTC工作原理

16.1.1 RTC简介

STM32 的 RTC 外设,实质是一个掉电后还继续运行的定时器。从定时器的角度来说,相对于通用定时器 TIM 外设,它十分简单,只有很纯粹的计时功能(当然,可以触发中断);但从掉电还继续运行的角度来说,它却是 STM32 中唯一一个具有如此强大功能的外设。所以 RTC 外设的复杂之处并不在于它的定时功能,而在于它掉电还继续运行的特性。

以上所说的掉电,是指主电源 VDD断开的情况,为了 RTC 外设掉电继续运行,必须给STM32 芯片通过 VBAT引脚接上锂电池。当主电源 VDD有效时,由 VDD给 RTC 外设供电。当 VDD掉电后,由 VBAT给 RTC 外设供电。但无论由什么电源供电,RTC 中的数据都保存在属于 RTC 的备份域中,若主电源 VDD和 VBAT都掉电,那么备份域中保存的所有数据将丢失。备份域除了 RTC 模块的寄存器,还有 42 个 16 位的寄存器可以在 VDD掉电的情况下保存用户程序的数据,系统复位或电源复位时,这些数据也不会被复位。

从 RTC 的定时器特性来说,它是一个 32 位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的 128 分频:HSE/128;低速内部时钟 LSI;使 HSE 分频时钟或 LSI 的话,在主电源 VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证 RTC 正常工作。因此 RTC 一般使用 低速外部时钟 LSE,频率为实时时钟模块中常用的 32.768KHz,这是因为 32768 = 215,分频容易实现,所以它被广泛应用到 RTC 模块。在主电源 VDD有效的情况下(待机),RTC 还可以配置闹钟事件使 STM32 退出待机模式。

RTC模块在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。

16.1.2主要特性

 可编程的预分频系数:分频系数最高为2^20
 32位的可编程计数器,可用于较长时间段的测量。
 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
 可以选择以下三种RTC的时钟源:

─ HSE 时钟除以128;
─ LSE振荡器时钟;
─ LSI振荡器时钟

 2个独立的复位类型:

─ APB1接口由系统复位;
─ RTC核心(预分频器、闹钟、计数器和分频只能由后备域复位

 3个专门的可屏蔽中断:

─ 闹钟中断,用来产生一个软件可编程的闹钟中断。
─ 秒中断,用来产生一个可编程的周期性中断信号 (最长可达1秒)。
─ 溢出中断,指示内部可编程计数器溢出并回转为的状态。

16.1.3 RTC架构

RTC的架构如图所示。

xWkF10.md.png

RTC 由两个主要部分组成, 第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。 APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。

另一部分(RTC 核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是 RTC 的预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。 RTC 的预分频模块包含了一个 20位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。第二个模块是一个 32 位的可编程计数器,可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。

RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。

图中浅灰色的部分都是属于备份域的,在 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/32768= 1 Hz,计时周期为 1 秒,计时器在 TR_CLK 的驱动下计数,即每秒计数器 RTC_CNT 的值加 1。

由于备份域的存在,使得 RTC 核具有了完全独立于 APB1 接口的特性,也因此对 RTC寄存器的访问要遵守一定的规则。

系统复位后,禁止访问后备寄存器和 RTC,防止对后备区域(BKP)的意外写操作。执行以下操作使能对后备寄存器和 RTC 的访问:

 设置 RCC_APB1ENR 寄存器的 PWREN 和 BKPEN 位来使能电源和后备接口时钟。
 设置 PWR_CR 寄存器的 DBP 位使能对后备寄存器和 RTC 的访问。

设置为可访问后,在第一次通过 APB1 接口访问 RTC 时,必须等待 APB1 与 RTC 外设同步,确保被读取出来的 RTC 寄存器值是正确的。若在同步之后,一直没有关闭 APB1的 RTC 外设接口,就不需要再次同步了。

如果内核要对 RTC 寄存器进行任何的写操作,在内核发出写指令后,RTC 模块在 3 个RTCCLK 时钟之后,才开始正式的写 RTC 寄存器操作。我们知道 RTCCLK 的频率比内核主频低得多,所以必须要检查 RTC 关闭操作标志位 RTOFF,当这个标志被置 1 时,写操作才正式完成。

16.1.4 BKP简介

备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD 电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。

此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。

16.2 RTC寄存器分析

16.2.1 RTC寄存器描述

RTC 总共有 2 个控制寄存器 RTC_CRH 和 RTC_CRL,两个都是 16 位的。

RTC_CRH寄存器用来控制中断的,我们本章将要用到秒钟中断,所以在该寄存器必须设置最低位为 1,以允许秒钟中断。如下图所示。

RTC_CRL的第 0 位是秒钟标志位,我们在进入闹钟中断的时候,通过判断这位来决定是不是发生了秒钟中断。然后必须通过软件将该位清零(写0)。第 3 位为寄存器同步标志位,我们在修改控制寄存器 RTC_CRH/CRL 之前,必须先判断该位,是否已经同步了,如果没有则等待同步,在没同步的情况下修改 RTC_CRH/CRL 的值是不行的。第 4 位为配置标位,在软件修改 RTC_CNT/RTC_ALR/RTC_PRL 的值的时候,必须先软件置位该位,以允许进入配置模式。第 5 位为 RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次操作。

xWknAJ.md.png

xWkuN9.md.png

【注意】

 任何标志位都将保持挂起状态,直到适当的RTC_CR请求位被软件复位,表示所请求的中断已经被接受。
 在复位时禁止所有中断,无挂起的中断请求,可以对RTC寄存器进行写操作。
 当APB1时钟不运行时, OWF、 ALRF、 SECF和RSF位不被更新。
 OWF、 ALRF、 SECF和RSF位只能由硬件置位,由软件来清零。
 若ALRF=1且ALRIE=1,则允许产生RTC全局中断。如果在EXTI控制器中允许产生EXTI线 17中断,则允许产生RTC全局中断和RTC闹钟中断。
 若ALRF=1,如果在EXTI控制器中设置了EXTI线 17的中断模式,则允许产生RTC闹钟中断;如果在EXTI控制器中设置了EXTI线 17的事件模式,则这条线上会产生一个脉冲(不会产生RTC闹钟中断)。

RTC 预分频装载寄存器,也有 2 个寄存器组成, RTC_PRLH 和RTC_PRLL。这两个寄存器用来配置 RTC 时钟的分频数的,比如我们使用外部 32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为 32767,以得到一秒钟的计数频率。RTC_PRLH 的各位描述如下图所示.

xWkKhR.md.png

从和是上图可以看出, RTC_PRLH 只有低四位有效,用来存储 PRL 的 19~16 位。而 PRL的前 16 位,存放在 RTC_PRLL 里面,寄存器 RTC_PRLL 的各位描述如下图所示。

xWkGnO.md.png

【注】如果输入时钟频率是32.768kHz(fRTCCLK),这个寄存器中写入7FFFh可获得周期为1秒钟的信号。

RTC 预分频器余数寄存器,该寄存器也有 2 个寄存器组成 RTC_DIVH 和 RTC_DIVL,这两个寄存器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到 0.1 秒,或者 0.01 秒等。该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器的各位是一样的,这里我们就不列出来了。

接着要介绍的是 RTC 最重要的寄存器, RTC 计数器寄存器 RTC_CNT。该寄存器由 2 个 16位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位,用来记录秒钟值(一般情况下)。此两个计数器也比较简单,我们也不多说了。注意一点,在修改这个寄存器的时候要先进入配置模式。

最后我们介绍 RTC 部分的最后一个寄存器, RTC 闹钟寄存器,该寄存器也是由 2 个 16 为的寄存器组成 RTC_ALRH 和 RTC_ALRL。总共也是 32 位,用来标记闹钟产生的时间(以秒为单位),如果 RTC_CNT 的值与 RTC_ALR 的值相等,并使能了中断的话,会产生一个闹钟中断。该寄存器的修改也要进入配置模式才能进行。

因为我们使用到备份寄存器来存储 RTC 的相关信息(我们这里主要用来标记时钟是否已经经过了配置)。关于备份寄存器请读者朋友查看《STM32参考手册》。

16.2.2读RTC寄存器

RTC完全独立于RTC APB1接口。
软件通过APB1接口访问RTC的预分频值、计数器值和闹钟值。但是,相关的可读寄存器只在与RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。RTC标志也是如此的。

这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0) 。下述几种情况下能够发生这种情形:

 发生系统复位或电源复位
 系统刚从待机模式唤醒
 系统刚从停机模式唤醒

所有以上情况中,APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时,RTC 的APB1 接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’。

16.2.3配置RTC寄存器

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

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

配置过程:

 查询RTOFF位,直到RTOFF的值变为’1’
 置CNF值为1,进入配置模式
 对一个或多个RTC寄存器进行写操作
 清除CNF标志位,退出配置模式
 查询RTOFF,直至RTOFF位变为’1’ 以确认写操作已经完成。

仅当CNF标志位被清除时,写操作才能进行,这个过程至少需要3个RTCCLK周期。

16.3 RTC具体代码实现-标准库

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 时钟。库函数中,选择 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)最后在配置完成之后

通过这个函数直接设置 RTC 计数值。

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);

这个函数就很好理解了,这里不做过多讲解。

设置中断分组的方法之前已经详细讲解过, 调用 NVIC_Init 函数即可, 这里不做重复讲解。

7)编写中断服务函数。
最后,我们要编写中断服务函数,在秒钟中断产生的时候,读取当前的时间值。

/**
  * @brief  This function handles RTC exception.
  * @param  None
  * @retval None
  */
void RTC_IRQHandler(void)
{
   if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)//读取中断标志
   {
       RTC_ClearITPendingBit(RTC_IT_SEC);//清楚中断标志
       tim_bz=1;//秒中断标志
   }
}

16.4 RTC具体代码实现-HAL库

16.4.1 STM32Cube生成工程

我们在串口的例子的基础上进行配置。

串口通信(HAL库)

基础配置这里就讲了,关于RTC的配置主要的两个部分。

1.RCC配置
RTC不同于其他外设,对功耗的要求极其敏感,如果使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,资源消耗太大,小小的纽扣电池根本满足不了持续的供电需求,因此RTC一般都时钟低速外部时钟LSE。这里就需要使能外部晶振LSE。

xWkfCn.md.png

HSE在默认的配置工程已经配置过,只需使能LSE即可。

2.RTC配置

既然是研究RTC,那么肯定要配置RTC。

xWkouT.md.png

RTC从定时器特性来说,它是一个 32 位的计数器,只是只能向上计数。因此它被归类在Timers模块。

首先要是勾选[Activate Clock Source],这是激活时钟源,然后勾选[Activate calendar激活日历,值得注意的是,先是使能时钟源,然后才能使能RTC日历。

RTC_OUT设置为[Not RTC_OUT],也就是是否使能 tamper(PC13)引脚上输出校正的秒脉冲时钟,有兴趣的可以研究其他选项。
Tamper是 RTC入侵检测校验功能。

【小贴士】RTC校验功能,使能侵入检测功能。RTC时钟经64分频输出到侵入检测引脚TAMPER上。当 TAMPER引脚上的信号从 0变成1或者从 1变成 0(取决于备份控制寄存器BKP_CR的 TPAL位),会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。

值得注意的是,RTC_OUT和Tamper选项共用引脚PC13,因此两个功能只能选择其一。

接下来设置下日期与时间。

xWk7bF.md.png

此处设置时间为2021/03/06 11:00:00,

【日期格式】Binary data format 表示十六进制;BCD data format BCD表示码进制

接下来配置中断。

xWASKK.md.png

配置中断优先级。

xWApDO.md.png

最后配置下RTC的时钟,选择LSE。

xWA9bD.md.png

然后生成工程即可。

16.4.2 RTC具体代码分析

我们先看主函数代码:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  RTC_DateTypeDef GetData;  //获取日期结构体
  RTC_TimeTypeDef GetTime;   //获取时间结构体

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    /* Get the RTC current Time */
    HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);

    /* Get the RTC current Date */
    HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);

    /* Display date Format : yy/mm/dd */
    printf("%02d/%02d/%02d \r",2000 + GetData.Year, GetData.Month, GetData.Date);

    /* Display time Format : hh:mm:ss */
    printf("%02d:%02d:%02d\r\n",GetTime.Hours, GetTime.Minutes, GetTime.Seconds);

    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

这里有两个重要的结构体RTC_DateTypeDef 和RTC_TimeTypeDef。其原型如下:

/**
  * @brief  RTC Time structure definition
  */
typedef struct
{
  uint8_t Hours;            /*!< Specifies the RTC Time Hour.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 23 */

  uint8_t Minutes;          /*!< Specifies the RTC Time Minutes.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 59 */

  uint8_t Seconds;          /*!< Specifies the RTC Time Seconds.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 59 */

} RTC_TimeTypeDef;

/**
  * @brief  RTC Date structure definition
  */
typedef struct
{
  uint8_t WeekDay;  /*!< Specifies the RTC Date WeekDay (not necessary for HAL_RTC_SetDate).
                         This parameter can be a value of @ref RTC_WeekDay_Definitions */

  uint8_t Month;    /*!< Specifies the RTC Date Month (in BCD format).
                         This parameter can be a value of @ref RTC_Month_Date_Definitions */

  uint8_t Date;     /*!< Specifies the RTC Date.
                         This parameter must be a number between Min_Data = 1 and Max_Data = 31 */

  uint8_t Year;     /*!< Specifies the RTC Date Year.
                         This parameter must be a number between Min_Data = 0 and Max_Data = 99 */

} RTC_DateTypeDef;

这两个结构体定义了日期和时间。在循环体中每个1s获取当前时间,也有两个重要的函数。

HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);

值得注意的是,要先调用日期函数在调用时间函数。

虽然通过以上配置可以获取时间,但是让我们断电或者复位后,时间又会重新设置。这就需要设置BKP寄存器,BKP寄存器保存一个标志。每次启动时都读取这个标志并判断是不是预先设定的值:如度果不是就初始化RTC并设置时间,再设置标志为预期值;如果是预期值就跳过初始化和时间设置,继续执行后面的程序,所以这里我们只需要每次上电执行RTC初始化之前,将标志设置为预期值即可。

这里只需要替换MX_RTC_Init ()函数即可,代码如下:

/**
  * @brief RTC Initialization Function
  * @param None
  * @retval None
  */
static void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */
  RTC_DateTypeDef datebuff;   //日期结构体参数
  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef DateToUpdate = {0};

  /* USER CODE BEGIN RTC_Init 1 */
  if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!= 0x5051)
  {

  /* USER CODE END RTC_Init 1 */
  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 11;
  sTime.Minutes = 0;
  sTime.Seconds = 0;

  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
  DateToUpdate.Month = RTC_MONTH_MARCH;
  DateToUpdate.Date = 6;
  DateToUpdate.Year = 21;

  if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */

    __HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);    //开启RTC时钟秒中断
    datebuff = DateToUpdate;  //把日期数据拷贝到自己定义的data中
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5051);//向指定的后备区域寄存器写入数据
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)datebuff.Year);
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)datebuff.Month);
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)datebuff.Date);
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)datebuff.WeekDay);

  }
    else
    {
        datebuff.Year    = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
        datebuff.Month   = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
        datebuff.Date    = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
        datebuff.WeekDay = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
        DateToUpdate = datebuff;
        if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
        {
            Error_Handler();
        }
        __HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC);    //开启RTC时钟秒中断       
    }

  /* USER CODE END RTC_Init 2 */

}

16.5实验现象

打开串口助手,打印信息如下:

xWAF5d.md.png

这里没有设置初始时间,时分秒是从0开始的。


欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎


资源获取方式

1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[Cortex-M]获取资料提取码

Related posts

Leave a Comment