【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

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

  1. X999game is quickly becoming my favorite. Plenty of games to keep you entertained. Don’t believe me? See for yourself now X999game

  2. X999game is quickly becoming my favorite. Plenty of games to keep you entertained. Don’t believe me? See for yourself now X999game

  3. X999game is quickly becoming my favorite. Plenty of games to keep you entertained. Don’t believe me? See for yourself now X999game

Leave a Comment