当前位置: 首页 > news >正文

7.STM32F407ZGT6-RTC

参考:
1.正点原子

前言:
RTC实时时钟是很基本的外设,用来记录绝对时间。做个总结,达到:
1.学习RTC的原理和概念。
2.通过STM32CubeMX快速配置RTC。

27.1 RTC 时钟简介

STM32F407 的实时时钟(RTC)是一个独立的定时器。STM32 的 RTC 模块拥有一组连续计数的计数器,在相对应的软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统的当前时间和日期。
RTC 模块和时钟配置系统(RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变,只要后备区域供电正常,那么 RTC 将可以一直运行。
但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前,先要取消备份区域(BKP)写保护。

27.1.1 RTC 框图

下面先来学习 RTC 框图,通过学习 RTC 框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。RTC 的框图,如图 27.1.1 所示:
在这里插入图片描述

我们把 RTC 框图分成以下几个部分讲解:
① 时钟源
STM32F407 的 RTC 时钟源(RTCCLK)通过时钟控制器,可以从 LSE 时钟、LSI 时钟以及 HSE 时钟三者中选择其一(通过设置 RCC_BDCR 寄存器选择)。一般我们选择 LSE,即外部 32.768KHz 晶振作为时钟源(RTCCLK)。外部晶振具有精度高的优点。LSI 是 STM32 芯片内部的低速 RC 振荡器,频率约 32 KHz,缺点是精度较低,所以一般不建议使用。比如当没有外部低速晶振(32.768KHz)的时候,分频后的 HSE 可以作为备选使用的时钟源。
② 预分频器
预分配器(RTC_PRER)分为 2 个部分:一个通过 RTC_PRER 寄存器的 PREDIV_A 位配置的 7 位异步预分频器。另一个通过 RTC_PRER 寄存器的 PREDIV_S 位配置的 15 位同步预分频器。
经过 7 位异步预分频器出来的时钟 ck_apre 可作为 RTC_SSR 亚秒递减计数器(RTC_SSR)的时钟,ck_apre 时钟频率的计算公式如下:
Fck_apre =Frtcclk ( PREDIV_A + 1)
当 RTC_SSR 寄存器递减到 0 的时候,会使用 PREDIV_S 的值重新装载 PREDIV_S。而PREDIV_S 一般为 255,这样,我们得到亚秒时间的精度是:1/256 秒,即 3.9ms 左右,有了这个亚秒寄存器 RTC_SSR,就可以得到更加精确的时间数据。
经过 15 位同步预分频器出来的时钟 ck_spre 可以用于更新日历,也可以用作 16 位唤醒自动重载定时器的时基,ck_apre 时钟频率的计算公式如下:
Fck_spre =Frtcclk(PREDIV_S + 1) ∗ ( PREDIV_A + 1)
PREDIV_A 和 PREDIV_S 分别为 RTC 的异步和同步分频器,使用两个预分频器时,我们推荐设置 7 位异步预分频器(PREDIV_A)的值较大,以最大程度降低功耗。例如:本实验我们的外部低速晶振的频率 32.768KHz 经过 7 位异步预分频器后,再经过 15 位同步预分频器,要得到 1Hz 频率的时钟用于更新日历。通过计算知道,32.768KHz 的时钟要经过 32768 分频,才能得到 1Hz 的 ck_spre。于是我们只需要设置: PREDIV_A=0X7F,即 128 分频;PREDIV_S=0XFF,即 256 分频,即可得到 1Hz 的 Fck_spre,PREDIV_A 的值我们也是往尽量大的原则,以最大程度降低功耗。
③ 时间和日期相关寄存器
该部分包括三个影子寄存器,RTC_SSR(亚秒)、RTC_TR(时间)、RTC_DR(日期)。实时时钟一般表示为:时/分/秒/亚秒。RTC_TR 寄存器用于存储时/分/秒时间数据,可读可写(即可设置或者获取时间)。RTC_DR 寄存器用于存储日期数据,包括年/月/日/星期,可读可写(即可设置或者获取日期)。RTC_SSR 寄存器用于存储亚秒级的时间,这样我们可以获取更加精确的时间数据。
这三个影子寄存器可以通过与 PCLK1(APB1 时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。
每隔 2 个 RTCCLK 周期,当前日历值便会复制到影子寄存器,并置位 RTC_ISR 寄存器的RSF 位。我们可以读取 RTC_TR 和 RTC_DR 来得到当前时间和日期信息,不过需要注意的是:
时间和日期都是以 BCD 码的格式存储的,读出来要转换一下,才可以得到十进制的数据。
④ 可编程闹钟
STM32F407 提供两个可编程闹钟:闹钟 A(ALARM_A)和闹钟 B(ALARM_B)。通过RTC_CR 寄存器的 ALRAE 和 ALRBE 位置 1 来使能闹钟。当亚秒、秒、分、小时、日期分别与闹钟寄存器 RTC_ALRMASSR/RTC_ALRMAR 和RTC_ALRMBSSR/RTC_ALRMBR 中的值匹配时,则可以产生闹钟(需要适当配置)。本章我们将利用闹钟 A 产生闹铃,即设置RTC_ALRMASSR 和 RTC_ALRMAR 即可。
⑤ 周期性自动唤醒
STM32F407 的 RTC 不带秒钟中断了,但是多了一个周期性自动唤醒功能。周期性唤醒功能,由一个 16 位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒。
我们可以通过 RTC_CR 寄存器中的 WUTE 位设置使能此唤醒功能。
唤醒定时器的时钟输入可以是:2、4、8 或 16 分频的 RTC 时钟(RTCCLK),也可以是 ck_spre时钟(一般为 1Hz)。
当选择 RTCCLK(假定 LSE 是:32.768 kHz)作为输入时钟时,可配置的唤醒中断周期介于122us(因为 RTCCLK/2 时,RTC_WUTR 不能设置为 0)和 32 s 之间,分辨率最低为:61us。
当选择 ck_spre(1Hz)作为输入时钟时,可得到的唤醒时间为 1s 到 36h 左右,分辨率为 1秒。并且这个 1s~36h 的可编程时间范围分为两部分:
当 WUCKSEL[2:1]=10 时为:1s 到 18h。
当 WUCKSEL[2:1]=11 时约为:18h 到 36h。
在后一种情况下,会将 2^16 添加到 16 位计数器当前值(即扩展到 17 位,相当于最高位用WUCKSEL [1]代替)。
初始化完成后,定时器开始递减计数。在低功耗模式下使能唤醒功能时,递减计数保持有效。此外,当计数器计数到 0 时,RTC_ISR 寄存器的 WUTF 标志会置 1,并且唤醒寄存器会使用其重载值(RTC_WUTR 寄存器值)自动重载,之后必须用软件清零 WUTF 标志。
通过将 RTC_CR 寄存器中的 WUTIE 位置 1 来使能周期性唤醒中断时,可以使 STM32 退出低功耗模式。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响,它仍然可以正常工作,故唤醒定时器,可以用于周期性唤醒 STM32。

27.1.2 RTC 寄存器

接下来,我们介绍本实验我们要用到的 RTC 寄存器。

⚫ RTC 时间寄存器(RTC_TR)
RTC 时间寄存器描述如图 27.1.2.1 所示:
在这里插入图片描述

该寄存器是 RTC 的时间寄存器,可读可写,对该寄存器写,可以设置时间,对该寄存器读,可以获取当前的时间,此外该寄存器受到寄存器写保护,通过 RTC 写保护寄存器(RTC_WPR)设置,后面会讲解到 RTC_WPR 寄存器。需要注意的是:本寄存器存储的数据都是 BCD 格式的,读取之后需要进行转换,方可得到十进制的时分秒等数据。

⚫ RTC 日期寄存器(RTC_DR)
RTC 日期寄存器描述如图 27.1.2.2 所示:
在这里插入图片描述

该寄存器是 RTC 的日期寄存器,可读可写,对该寄存器写,可以设置日期,对该寄存器读,可以获取当前的日期,同样该寄存器也受到寄存器写保护,存储的数据也都是 BCD 格式的。

⚫ RTC 控制寄存器(RTC_CR)
RTC 控制寄存器描述如图 27.1.2.3 所示:
在这里插入图片描述

该寄存器重点介绍几个要用到的位:WUTIE 是唤醒定时器中断使能位,ALRAIE 是闹钟 A中断使能位,本章用到这两个使能位,都设置为 1 即可。WUTE 和 ALRAE 分别是唤醒定时器和闹钟 A 使能位,同样都设置为 1,开启。FMT 为小时格式选择位,我们设置为 0,选择 24 小时制。WUCKSEL[2:0],用于唤醒时钟选择,这个前面已经有介绍了,我们这里就不多说了。

⚫ RTC 亚秒寄存器(RTC_SSR)
RTC 亚秒寄存器描述如图 27.1.2.4 所示:
在这里插入图片描述

该寄存器可用于获取更加精确的 RTC 时间。不过,在本章没有用到,如果需要精确时间的地方,大家可以使用该寄存器。

⚫ RTC 初始化和状态寄存器(RTC_ISR)
RTC 初始化和状态寄存器描述如图 27.1.2.5 所示:
在这里插入图片描述

该寄存器中,WUTF、ALRBF 和 ALRAF,分别是唤醒定时器、闹钟 B 和闹钟 A 的中断标志位,当对应事件产生时,这些标志位被置 1,如果设置了中断,则会进入中断服务函数,这些位通过软件写 0 清除。
INIT 为初始化模式控制位,要初始化 RTC 时,必须先设置 INIT=1。
INITF 为初始化标志位,当设置 INIT 为 1 以后,要等待 INITF 为 1,才可以更新时间、日期和预分频寄存器等。
RSF 位为寄存器同步标志,仅在该位为 1 时,表示日历影子寄存器已同步,可以正确读取RTC_TR/RTC_TR 寄存器的值了。
WUTWF、ALRBWF 和 ALRAWF 分别是唤醒定时器、闹钟 B 和闹钟 A 的写标志,只有在这些位为 1 的时候,才可以更新对应的内容。比如:要设置闹钟 A 的 ALRMAR 和 ALRMASSR,则必须先等待 ALRAWF 为 1,才可以设置。

⚫ RTC 预分频寄存器(RTC_PRER)
RTC 预分频寄存器描述如图 27.1.2.6 所示:
在这里插入图片描述

该寄存器用于 RTC 的分频,我们在之前也有讲过,这里就不多说了。该寄存器的配置,必须在初始化模式(INITF=1)下,才可以进行。

⚫ RTC 唤醒寄存器(RTC_WUTR)
RTC 唤醒寄存器描述如图 27.1.2.7 所示:
在这里插入图片描述

该寄存器用于设置自动唤醒重装载值,可用于设置唤醒周期。该寄存器的配置,必须等待RTC_ISR 的 WUTWF 为 1 才可以进行。

⚫ RTC 闹钟 A 寄存器(RTC_ALRMAR)
RTC 闹钟 A 寄存器描述如图 27.1.2.8 所示:
在这里插入图片描述

该寄存器用于设置闹铃 A,当 WDSEL 选择 1 时,使用星期制闹铃,本章我们选择星期制闹铃。该寄存器的配置,必须等待 RTC_ISR 的 ALRAWF 为 1 才可以进行。另外,还有RTC_ALRMASSR 寄存器,该寄存器我们这里就不再介绍了,大家参考手册。

⚫ RTC 写保护寄存器(RTC_WPR)
RTC 写保护寄存器:RTC_WPR,该寄存器比较简单,低八位有效。上电后,所有 RTC 寄存器都受到写保护(RTC_ISR[13:8]、RTC_TAFCR 和 RTC_BKPxR 除外),必须依次写入:0xCA、0x53 两关键字到 RTC_WPR 寄存器,才可以解锁。写一个错误的关键字将再次激活 RTC 的寄存器写保护。

⚫ RTC 备份寄存器(RTC_BKPxR)
RTC 备份寄存器描述如图 27.1.2.9 所示:
在这里插入图片描述

该寄存器组总共有 32 个,每个寄存器是 32 位的,可以存储 128 个字节的用户数据,这些寄存器在备份域中实现,可在 VDD 电源关闭时通过 VBAT 保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在 MCU 从待机模式唤醒时复位。
复位后,对 RTC 和 RTC 备份寄存器的写访问被禁止,执行以下操作可以使能 RTC 及 RTC备份寄存器的写访问:
1)电源控制寄存器(PWR_CR)的 DBP 位来使能 RTC 及 RTC 备份寄存器的访问。
2)往 RTC_WPR 写入 0xCA、0x53 解锁序列(先写 0xCA,再写 0x53)。
我们可以用 BKP 来存储一些重要的数据,相当于一个 EEPROM,不过这个 EEPROM 并不是真正的 EEPROM,而是需要电池来维持它的数据。

⚫ 备份区域控制(RCC_BDCR)
备份区域控制寄存器描述如图 27.1.2.10 所示:
在这里插入图片描述

RTC 的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在 RTC 操作之前先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。

27.2 硬件设计

1. 例程功能
本实验通过串口输出 RTC 时间,并可以通过串口 设置 RTC 时间,从而调节时间。
2. 硬件资源
1)串口 1(PA9/PA10 连接在板载 USB 转串口芯片 CH340 上面)
2)RTC(实时时钟)
3. 原理图
RTC 属于 STM32F407 内部资源,通过软件设置好就可以了。不过 RTC 不能断电,否则数据就丢失了,我们如果想让时间在断电后还可以继续走,那么必须确保开发板的电池有电。

27.3 程序设计

27.3.1 RTC 的 HAL 库驱动

RTC 在 HAL 库中的驱动代码在 stm32f4xx_hal_rtc.c 文件(及其头文件)中。下面介绍几个重要的 RTC 函数,其他没有介绍的请看源码。
1. HAL_RTC_Init 函数
RTC 的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);
⚫ 函数描述:
用于初始化 RTC。
⚫ 函数形参:
形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,其定义如下:

typedef struct
{RTC_TypeDef *Instance; /* 寄存器基地址 */RTC_InitTypeDef Init; /* RTC 配置结构体 */ HAL_LockTypeDef Lock; /* RTC 锁定对象 */ __IO HAL_RTCStateTypeDef State; /* RTC 设备访问状态 */
}RTC_HandleTypeDef;

1)Instance:指向 RTC 寄存器基地址。
2)Init:是真正的 RTC 初始化结构体,其结构体类型 RTC_InitTypeDef 定义如下:

typedef struct
{uint32_t HourFormat;     /* 小时格式 */uint32_t AsynchPrediv;   /* 异步预分频系数 */ uint32_t SynchPrediv;    /* 同步预分频系数 */  uint32_t OutPut;         /* 选择连接到 RTC_ALARM 输出的标志 */ uint32_t OutPutPolarity; /* 设置 RTC_ALARM 的输出极性 */uint32_t OutPutType;     /* 设置 RTC_ALARM 的输出类型为开漏输出还是推挽输出 */ 
}RTC_InitTypeDef;

HourFormat : 用来设置小时格式,可以是 12 小时制或者 24 小时制,这两个选项的宏定义分别为 RTC_HOURFORMAT_12 和 RTC_HOURFORMAT_24。
AsynchPrediv: 用来设置 RTC 的异步预分频系数,也就是设置 RTC_PRER 寄存器的PREDIV_A 相关位,因为异步预分频系数是 7 位,所以最大值为 0x7F,不能超过这个值。
SynchPrediv:用来设置RTC的同步预分频系数,也就是设置RTC_PRER寄存器的PREDIV_S相关位,因为同步预分频系数也是 15 位,所以最大值为 0x7FFF,不能超过这个值。
OutPut: 用来选择要连接到 RTC_ALARM 输出的标志,取值为:RTC_OUTPUT_DISABLE(禁止输出),RTC_OUTPUT_ALARMA(使能闹钟 A 输出),RTC_OUTPUT_ALARMB(使能闹钟 B 输出)和 RTC_OUTPUT_WAKEUP(使能唤醒输出)。
OutPutPolarity: 用来设置 RTC_ALARM 的输出极性,与 Output 成员变量配合使用,取值为RTC_OUTPUT_POLARITY_HIGH(高电平)或 RTC_OUTPUT_POLARITY_LOW(低电平)。
OutPutType: 用来设置 RTC_ALARM 的输出类型为开漏输出(RTC_OUTPUT_TYPE_OPENDRAIN)还是推挽输出(RTC_OUTPUT_TYPE_PUSHPULL),与成员变量 OutPut 和OutPutPolarity 配合使用。

3)Lock:用于配置锁状态。
4)State:RTC 设备访问状态。
⚫ 函数返回值:
HAL_StatusTypeDef 枚举类型的值。

2. HAL_RTC_SetTime 函数
HAL_RTC_SetTime 是设置 RTC 的时间函数。其声明如下:
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime, uint32_t Format);
⚫ 函数描述:
该函数用于设置 RTC 的时间,即设置时间寄存器 RTC_TR 的相关位的值。
⚫ 函数形参:
形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。
形参 2 是 RTC_TimeTypeDef 结构体类型指针变量,定义如下:

typedef struct
{uint8_t Hours;uint8_t Minutes;uint8_t Seconds;uint8_t TimeFormat;uint32_t SubSeconds;uint32_t SecondFraction;uint32_t DayLightSaving;uint32_t StoreOperation;
}RTC_TimeTypeDef;

前面四个成员变量就比较好理解了,分别用来设置 RTC 时间参数的小时,分钟,秒钟,以及 AM/PM 符号,大家参考前面讲解的 RTC_TR 的位描述即可。SubSeconds 用来读取保存亚秒寄存器 RTC_SSR 的值,SecondFraction 用来读取保存同步预分频系数的值,也就是 RTC_PRER的位 0~14,DayLightSaving 用来设置日历时间增加 1 小时,减少 1 小时,还是不变。StoreOperation用户可对此变量设置以记录是否已对夏令时进行更改。
形参 3 是 uint32_t 类型变量,用来设置输入的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。
⚫ 函数返回值:
HAL_StatusTypeDef 枚举类型的值。

3. HAL_RTC_SetDate 函数
HAL_RTC_SetDate 是设置 RTC 的日期函数。其声明如下:
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc,RTC_DateTypeDef *sDate, uint32_t Format);
⚫ 函数描述:
该函数用于设置 RTC 的日期,即设置日期寄存器 RTC_DR 的相关位的值。
⚫ 函数形参:
形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。
形参 2 是 RTC_DateTypeDef 结构体类型指针变量,定义如下:

typedef struct
{uint8_t WeekDay; /* 星期 */uint8_t Month; /* 月份 */uint8_t Date; /* 日期 */uint8_t Year; /* 年份 */
}RTC_DateTypeDef;

结构体一共四个成员变量,这四个成员变量分别对应星期、月份、日期和年份,对应的是RTC_DR 寄存器。
形参 3 是 uint32_t 类型变量,用来设置输入的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。
⚫ 函数返回值:
HAL_StatusTypeDef 枚举类型的值。

4. HAL_RTC_GetTime 函数
HAL_RTC_GetTime 是获取当前 RTC 时间函数。其声明如下:
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
⚫ 函数描述:
该函数用于获取当前 RTC 时间,即读时间寄存器 RTC_TR 的相关位的值。
⚫ 函数形参:
形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。
形参 2 是 RTC_TimeTypeDef 结构体类型指针变量,对应的是 RTC_TR 寄存器。
形参 3 是 uint32_t 类型变量,用来设置获取的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。
⚫ 函数返回值:
HAL_StatusTypeDef 枚举类型的值。

5. HAL_RTC_GetDate 函数
HAL_RTC_SetDate 是获取当前 RTC 日期函数。其声明如下:
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);
⚫ 函数描述:
该函数用于获取当前 RTC日期 ,即读时间寄存器 RTC_DR 的相关位的值。
⚫ 函数形参:
形参 1 是 RTC_HandleTypeDef 结构体类型指针变量,即 RTC 的句柄。
形参 2 是 RTC_DateTypeDef 结构体类型指针变量,对应的是 RTC_DR 寄存器。
形参 3 是 uint32_t 类型变量,用来设置获取的时间格式为 BIN 格式还是 BCD 格式,可选值为 RTC_FORMAT_BIN 或者 RTC_FORMAT_BCD。
⚫ 函数返回值:
HAL_StatusTypeDef 枚举类型的值。

RTC 配置步骤
1)使能电源时钟,并使能 RTC 及 RTC 后备寄存器写访问。
我们要访问 RTC 和 RTC 备份区域就必须先使能电源时钟,然后使能 RTC 即后备区域访问。电源时钟使能,通过 RCC_APB1ENR 寄存器来设置;RTC 及 RTC 备份寄存器的写访问,通过 PWR_CR 寄存器的 DBP 位设置。HAL 库设置方法为:
__HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 PWR /
__HAL_RCC_BKP_CLK_ENABLE(); /
使能备份时钟 /
HAL_PWR_EnableBkUpAccess(); /
取消备份区域写保护 */
2)开启外部低速振荡器 LSE,选择 RTC 时钟,并使能
调用 HAL_RCC_OscConfig 函数配置开启 LSE。
调用 HAL_RCCEx_PeriphCLKConfig 函数选择 RTC 时钟源。
使能 RTC 函数为:__HAL_RCC_RTC_ENABLE。
3)初始化 RTC,设置 RTC 的分频,以及配置 RTC 参数
在 HAL 中,通过 HAL_RTC_Init 函数配置 RTC 分频系数,以及 RTC 的工作参数。
注意:该函数会调用:HAL_RTC_MspInit 函数来完成对 RTC 的底层初始化,包括:RTC 时钟使能、时钟源选择等。
4)设置 RTC 的时间
调用 HAL_RTC_SetTime 函数设置 RTC 时间,该函数实际设置时间寄存器 RTC_TR 的相关位的值。
5)设置 RTC 的日期
调用 HAL_RTC_SetDate 函数设置 RTC 的日期,该函数实际设置日期寄存器 RTC_DR 的相关位的值。
6)获取 RTC 当前日期和时间
调用 HAL_RTC_GetTime 函数获取当前 RTC 时间,该函数实际读取 RTC_TR 寄存器,然后将值存放到相应的结构体中。
调用 HAL_RTC_GetDate 函数获取当前 RTC 日期,该函数实际读取 RTC_DR 寄存器,然后将值存放到相应的结构体中。
通过以上 6 个步骤,我们就完成了对 RTC 的配置,RTC 即可正常工作,而且这些操作不是每次上电都必须执行的,可以视情况而定。当然,我们还可以唤醒中断、闹钟等,这些将在后面介绍。

27.3.1 程序解析

1. RTC 驱动代码
先看 RTC 的初始化函数,其定义如下:

void MX_RTC_Init(void)
{/* USER CODE BEGIN RTC_Init 0 *//* USER CODE END RTC_Init 0 */RTC_TimeTypeDef sTime = {0};RTC_DateTypeDef sDate = {0};/* USER CODE BEGIN RTC_Init 1 *//* USER CODE END RTC_Init 1 *//** Initialize RTC Only*/hrtc.Instance = RTC;hrtc.Init.HourFormat = RTC_HOURFORMAT_24;/* RTC 设置为 24 小时格式 */hrtc.Init.AsynchPrediv = 127;            /* RTC 异步分频系数(1~0x7F) */hrtc.Init.SynchPrediv = 255;             /* RTC 同步分频系数(0~0x7FFF) */hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;if (HAL_RTC_Init(&hrtc) != HAL_OK){Error_Handler();}/* USER CODE BEGIN Check_RTC_BKUP *//* 检查是不是第一次配置时钟 */uint32_t Magic_Value = HAL_RTCEx_BKUPRead(&hrtc, 0x01);if (0xAA55 == Magic_Value){printf("RTC is valid!\r\n");return;}else{HAL_RTCEx_BKUPWrite(&hrtc, 0x01, 0xAA55);printf("RTC is not valid!\r\n");}/* USER CODE END Check_RTC_BKUP *//** Initialize RTC and set the Time and Date*/sTime.Hours = 0x0;sTime.Minutes = 0x0;sTime.Seconds = 0x0;sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;sTime.StoreOperation = RTC_STOREOPERATION_RESET;if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK){Error_Handler();}sDate.WeekDay = RTC_WEEKDAY_MONDAY;sDate.Month = RTC_MONTH_JANUARY;sDate.Date = 0x1;sDate.Year = 0x0;if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK){Error_Handler();}/* USER CODE BEGIN RTC_Init 2 *//* USER CODE END RTC_Init 2 */}

该函数用来初始化 RTC 配置以及日期和时钟,但是只在第一次的时候设置时间,以后如果重新上电/复位都不会再进行时间设置了(前提是备份电池有电)。在第一次配置的时候,我们是按照上面介绍的 RTC 初始化步骤调用函数 HAL_RTC_Init 来实现的。
我们通过读取 BKP 寄存器 地址0x01的值来判断是否需要进行时间的设置。第一次未对 RTC 进行初始化 BKP 寄存器地址0x01 的值非 0xAA55,当进行 RTC 初始化时,BKP 寄存器 地址0x01 的值就是 0xAA55,所以以上代码操作确保时间只会设置一次,复位时不会重新设置时间。电池正常供电时,我们设置的时间不会因复位或者断电而丢失。
读取后备寄存器的函数其实还是调用 HAL 库提供的函数接口,写后备寄存器函数同样也是。这两个函数如下:

uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister);
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister,uint32_t Data);

这两个函数的使用方法就非常简单,分别用来读和写 BKR 寄存器的值。这里我们只是略微点到为止,详看例程源码。
接下来,我们用 HAL_RTC_MspInit 函数来编写 RTC 时钟配置等代码,其定义如下:

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};if(rtcHandle->Instance==RTC){/* USER CODE BEGIN RTC_MspInit 0 *//* USER CODE END RTC_MspInit 0 *//** Initializes the peripherals clock*/PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;/* 选择要配置外设 RTC */PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;/* RTC 时钟源选择 LSE */if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){Error_Handler();}/* RTC clock enable */__HAL_RCC_RTC_ENABLE();/* USER CODE BEGIN RTC_MspInit 1 *//* USER CODE END RTC_MspInit 1 */}
}

2. main.c 代码
在 main.c 里面编写如下代码:

int main(void)
{/* USER CODE BEGIN 1 *//* 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_TIM6_Init();MX_RTC_Init();/* USER CODE BEGIN 2 *//* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){uart_debug_task();XL_TIME6_time_show();/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

我们在while(1)循环中调用 XL_TIME6_time_show()读取 RTC 的时间和日期,并输出到串口。

void XL_TIME6_time_show(void)
{if (g_time_1s >= RTC_SHOW_PERIODIC_1S){g_time_1s = g_time_1s -RTC_SHOW_PERIODIC_1S;RTC_TimeTypeDef sTime = {0};HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);printf("H:%d, M:%d, S:%d\r\n", sTime.Hours, sTime.Minutes, sTime.Seconds);RTC_DateTypeDef sDate = {0};HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);printf("Y:%d, M:%d, W:%d, D:%d\r\n", sDate.Year, sDate.Month, sDate.WeekDay, sDate.Date);}
}

3.RTC 时间的设置

void uart_debug_task(void)
{uint8_t len;static uint32_t once_flag = 0;uint8_t cmd_buf[64] = {0};uint32_t para_1 = 0;uint32_t para_2 = 0;uint32_t para_3 = 0;uint32_t ret = 0;if (0 == once_flag){once_flag = 1;HAL_UART_Receive_IT(&huart1, (uint8_t *)g_rx1_buffer, RX1BUFFERSIZE); }if (g_usart1_rx_sta & 0x8000)         /* 接收到了数据? */{if (1)//for debug{len = g_usart1_rx_sta & 0x3fff;  /* 得到此次接收到的数据长度 */printf("\r\nThe message you sent is:\r\n");HAL_UART_Transmit(&huart1, (uint8_t*)g_usart1_rx_buf, len, 1000);    /* 发送接收到的数据 */while(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) != SET);           /* 等待发送结束 */printf("\r\n");             /* 插入换行 */}ret = sscanf((void*)g_usart1_rx_buf, "%s %d %d %d", cmd_buf, &para_1, &para_2, &para_3);if (1 <= ret){printf("cmd:%s, ret:%d\r\n", cmd_buf, ret);uint8_t cmd_1[32] = "cmd_RTC_set";if (0 == strncmp((void*)cmd_1, (void*)cmd_buf, strlen((void*)cmd_1))){uint32_t year;uint32_t Month;uint32_t WeekDay;uint32_t Date;uint32_t Hours;uint32_t Minutes;uint32_t Seconds;sscanf((void*)g_usart1_rx_buf, "%s %d %d %d %d %d %d %d", cmd_buf, &year, &Month, &WeekDay, &Date,  &Hours,  &Minutes,  &Seconds);XL_RTC_set_time( year, Month, WeekDay, Date, Hours, Minutes, Seconds);printf("XL_RTC_set_time success!!\r\n");}g_usart1_rx_sta = 0;memset((void*)g_usart1_rx_buf, 0, sizeof(g_usart1_rx_buf));}/* Clear the receiving cache */g_usart1_rx_sta = 0;memset((void*)g_usart1_rx_buf, 0, sizeof(g_usart1_rx_buf));}else{}return;
}void XL_RTC_set_time(uint32_t year, uint32_t Month, uint32_t WeekDay, uint32_t Date, uint32_t Hours, uint32_t Minutes, uint32_t Seconds)
{RTC_TimeTypeDef sTime = {0};sTime.Hours = Hours;sTime.Minutes = Minutes;sTime.Seconds = Seconds;HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);//#define RTC_WEEKDAY_SUNDAY             ((uint8_t)0x07)RTC_DateTypeDef sDate = {0};sDate.Year    = year;sDate.Month   = Month;sDate.WeekDay = WeekDay;sDate.Date    = Date;HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}

27.4 下载验证

将程序下载到开发板后,串口每秒输出时间和日期,实际显示效果如图所示:
如果时间不正确,通过串口命令来设置。“cmd_RTC_set 25 1 6 11 13 29 10”代表的是2025年 1月 周六 11号,13点 29分 10秒:
在这里插入图片描述

27.5 STM32CubeMX
1.RTC初始化
最简单的配置,实现基本的RTC初始化,不涉及闹钟 唤醒等。主要是RTC时钟的使能,时钟分频的配置。有效时间和日期都是无效的。
在这里插入图片描述

27.6 源代码路径

git clone git@gitee.com:xiaoliangliangcong/stm32.git
STM32F407ZGT6/7.RTC

相关文章:

7.STM32F407ZGT6-RTC

参考&#xff1a; 1.正点原子 前言&#xff1a; RTC实时时钟是很基本的外设&#xff0c;用来记录绝对时间。做个总结&#xff0c;达到&#xff1a; 1.学习RTC的原理和概念。 2.通过STM32CubeMX快速配置RTC。 27.1 RTC 时钟简介 STM32F407 的实时时钟&#xff08;RTC&#xf…...

重写(补充)

大家好&#xff0c;今天我们把剩下一点重写内容说完&#xff0c;来看。 [重写的设计规则] 对于已经投入使用的类,尽量不要进行修政 &#xff0c;最好的方式是:重新定义一个新的类,来重复利用其中共性的内容 我们不该在原来的类上进行修改&#xff0c;因为原来的类,可能还有用…...

30分钟内搭建一个全能轻量级springboot 3.4 + 脚手架 <3>5分钟集成好druid并使用druid自带监控工具监控sql请求

快速导航 快速导航 <1> 5分钟快速创建一个springboot web项目 <2> 5分钟集成好最新版本的开源swagger ui&#xff0c;并使用ui操作调用接口 <3> 5分钟集成好druid并使用druid自带监控工具监控sql请求 <4> 5分钟集成好mybatisplus并使用mybatisplus g…...

【C#深度学习之路】如何使用C#实现Yolo8/11 Segment 全尺寸模型的训练和推理

【C#深度学习之路】如何使用C#实现Yolo8/11 Segment 全尺寸模型的训练和推理 项目背景项目实现推理过程训练过程 项目展望写在最后项目下载链接 本文为原创文章&#xff0c;若需要转载&#xff0c;请注明出处。 原文地址&#xff1a;https://blog.csdn.net/qq_30270773/article…...

Oracle 分区索引简介

目录 一. 什么是分区索引二. 分区索引的种类2.1 局部分区索引&#xff08;Local Partitioned Index&#xff09;2.2 全局分区索引&#xff08;Global Partitioned Index&#xff09; 三. 分区索引的创建四. 分区索引查看4.1 USER_IND_COLUMNS 表4.2 USER_INDEXES 表 五. 分区索…...

【科技赋能未来】NDT2025第三届新能源数字科技大会全面启动!

随着我国碳达峰目标、碳中和目标的提出&#xff0c;以及经济社会的发展进步&#xff0c;以风电、光伏发电为代表的新能源行业迎来巨大发展机遇&#xff0c;成为未来绿色经济发展的主要趋势和方向。 此外&#xff0c;数字化技术的不断发展和创新&#xff0c;其在新能源领域的应…...

Broker收到消息之后如何存储

1.前言 此文章是在儒猿课程中的学习笔记&#xff0c;感兴趣的想看原来的课程可以去咨询儒猿课堂《从0开始带你成为RocketMQ高手》&#xff0c;我本人觉得这个作者还是不错&#xff0c;都是从场景来进行分析&#xff0c;感觉还是挺适合我这种小白的。这块主要都是我自己的学习笔…...

Mysql--实战篇--SQL优化(查询优化器,常用的SQL优化方法,执行计划EXPLAIN,Mysql性能调优,慢日志开启和分析等)

一、查询优化 1、查询优化器 (Query Optimizer) MySQL查询优化器&#xff08;Query Optimizer&#xff09;是MySQL数据库管理系统中的一个关键组件&#xff0c;负责分析和选择最有效的执行计划来执行SQL查询。查询优化器的目标是尽可能减少查询的执行时间和资源消耗&#xff…...

BERT与CNN结合实现糖尿病相关医学问题多分类模型

完整源码项目包获取→点击文章末尾名片&#xff01; 使用HuggingFace开发的Transformers库&#xff0c;使用BERT模型实现中文文本分类&#xff08;二分类或多分类&#xff09; 首先直接利用transformer.models.bert.BertForSequenceClassification()实现文本分类 然后手动实现B…...

rabbitmqp安装延迟队列

在RabbitMQ中&#xff0c;延迟队列是一种特殊的队列类型。当消息被发送到此类队列后&#xff0c;不会立即投递给消费者&#xff0c;而是会等待预设的一段时间&#xff0c;待延迟期满后才进行投递。这种队列在多种场景下都极具价值&#xff0c;比如可用于处理需要在特定时间触发…...

深入探讨DICOM医学影像中的MPPS服务及其具体实现

深入探讨DICOM医学影像中的MPPS服务及其具体实现 1. 引言 在医疗影像的管理和传输过程中&#xff0c;DICOM&#xff08;数字影像和通信医学&#xff09;标准发挥着至关重要的作用。除了DICOM影像的存储和传输&#xff08;如影像存储SCP和影像传输SCP&#xff09;&#xff0c;…...

集合帖:区间问题

一、AcWing 803&#xff1a;区间合并 &#xff08;1&#xff09;题目来源&#xff1a;https://www.acwing.com/problem/content/805/ &#xff08;2&#xff09;算法代码&#xff1a;https://blog.csdn.net/hnjzsyjyj/article/details/145067059 #include <bits/stdc.h>…...

C#,入门教程(27)——应用程序(Application)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(26)——数据的基本概念与使用方法https://blog.csdn.net/beijinghorn/article/details/124952589 一、什么是应用程序 Application&#xff1f; 应用程序是编程的结果。一般把代码经过编译&#xff08;等&#xff09;过程&#…...

迅翼SwiftWing | ROS 固定翼开源仿真平台正式发布!

经过前期内测调试&#xff0c;ROS固定翼开源仿真平台今日正式上线&#xff01;现平台除适配PX4ROS环境外&#xff0c;也已实现APROS环境下的单机飞行控制仿真适配。欢迎大家通过文末链接查看项目地址以及具体使用手册。 1 平台简介 ROS固定翼仿真平台旨在实现固定翼无人机决策…...

CSS 样式 box-sizing: border-box; 详细解读

box-sizing是 CSS 中的一个属性&#xff0c;用于控制元素的盒模型计算方式。border-box值表示元素的宽度和高度将包括内边距&#xff08;padding&#xff09;和边框&#xff08;border&#xff09;&#xff0c;而不仅仅是内容的宽度和高度。这意味着当你为元素设置宽度和高度时…...

FLASK创建下载

html用a标签 <!-- Button to download the image --> <a href"{{ url_for(download_file, filenameimage.png) }}"><button>Download Image</button> </a> 后端&#xff1a;url_for双大括号即是用来插入变量到模板中的语法。也就是绑…...

漫话架构师|什么是系统架构设计师(开篇)

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 关注犬余&#xff0c;共同进步 技术从此不孤单...

Web Socket

Web Socket ‌WebSocket‌是一种基于TCP的网络通信协议&#xff0c;允许客户端和服务器之间建立全双工&#xff08;双向&#xff09;通信通道。WebSocket通过HTTP协议进行握手&#xff0c;建立连接后&#xff0c;客户端和服务器可以在同一个连接上同时发送和接收数据&#xff0…...

JavaSE学习心得(反射篇)

反射 前言 获取class对象的三种方式 利用反射获取构造方法 利用反射获取成员变量 利用反射获取成员方法 练习 保存信息 跟配置文件结合动态创建 前言 接上期文章&#xff1a;JavaSE学习心得&#xff08;多线程与网络编程篇&#xff09; 教程链接&#xff1a;黑马…...

使用FRP进行内网穿透

一、基本概念 内网穿透&#xff1a;它是一种网络技术或方法&#xff0c;旨在允许外部网络&#xff08;如互联网&#xff09;访问位于内部网络&#xff08;内网&#xff09;中的设备或服务。由于内部网络通常处于NAT&#xff08;网络地址转换&#xff09;、防火墙或其他安全机制…...

长安“战疫”网络安全公益赛的一些随想

起因 今年刚进入大学&#xff0c;开始带校队&#xff0c;为了培养校队新成员&#xff0c;也就一直计划着和当地的一些高校合作交流&#xff0c;但是由于种种原因一直被搁置下来。正巧学校信息中心和四叶草有一个培训项目的合作&#xff0c;学校的网安协会也算是沾了光成为了培…...

flutter 安卓端打包

在 Flutter 中打包 Android 应用程序是一个相对简单的过程。你可以使用 Flutter 的命令行工具来构建并打包你的 APK 或 AAB&#xff08;Android App Bundle&#xff09;。以下是打包 Flutter Android 应用的步骤&#xff1a; 1. 安装 Flutter 环境 确保你已经安装了 Flutter …...

Cesium中的CustomDataSource 详解

Cesium CustomDataSource 详解 在 Cesium 中&#xff0c;CustomDataSource 是一个强大的类&#xff0c;用于处理自定义的地理数据。它提供了一种方法&#xff0c;可以通过程序方式添加、管理和更新动态的地理实体&#xff0c;而无需依赖外部数据格式&#xff08;如 GeoJSON 或…...

[Qt]常用控件介绍-按钮类控件-QPushButton、QRedioButton、QCheckBox、QToolButton控件

目录 1.QPushButton按钮 介绍 属性 Demo&#xff1a;键盘方向键控制人物移动 2.Redio Button按钮 属性 clicked、pressed、released、toggled区别 单选按钮的分组 Demo&#xff1a;点餐小程序 3.CheckBox按钮 属性 Demo&#xff1a;获取今天的形成计划 4.ToolBu…...

Windows 蓝牙驱动开发-安装蓝牙设备

蓝牙配置文件驱动程序有两种安装类型&#xff1a; 客户端安装&#xff0c;在此类安装中&#xff0c;远程设备播发其服务&#xff0c;并且计算机与之连接。 示例包括&#xff1a;鼠标、键盘和打印机&#xff1b;服务器端安装&#xff0c;在此类安装中&#xff0c;计算机播发服务…...

element表格有横向滚动条时产生错位或者偏移(火狐浏览器)

问题图 解决方法&#xff1a;给表头增加竖向滚动条的宽度 // 解决拖拽表格滚动条&#xff0c;错位问题 ::v-deep .el-table__header-wrapper{padding-right: 20px!important; // 滚动条宽度 }效果图如下&#xff1a;...

C# 下 SQLite 并发操作与锁库问题的 5 种解决方案

开篇&#xff1a;你是否被 SQLite 并发锁库困扰&#xff1f; 在当今数字化的时代浪潮中&#xff0c;数据已然成为了企业与开发者们手中最为宝贵的资产之一。C# 作为一门广泛应用于各类软件开发的强大编程语言&#xff0c;常常需要与数据库进行紧密交互&#xff0c;以实现数据的…...

2025封禁指定国家ip-安装xtables-addons记录

如何安装和使用 安装lux仓库(该仓库包含xtables-addons所需的依赖环境) # wget http://repo.iotti.biz/CentOS/7/noarch/lux-release-7-1.noarch.rpm # rpm -ivh lux-release-7-1.noarch.rpm 安装xtables-addons。注意&#xff1a;必须先安装kmod-xtables-addons&#xff0c;再…...

卷积神经02-CUDA+Pytorch环境安装

卷积神经02-CUDAPytorch环境安装 在使用Python进行pytorch的使用过程中遇到各种各样的版本冲突问题&#xff0c;在此进行记录 0-核心知识脉络 1&#xff09;根据自己电脑的CUDA版本安装对应版本的Pytorch&#xff0c;充分的使用GPU性能2&#xff09;电脑要先安装【CUDA ToolKi…...

高斯数据库与MySQL数据库的区别

高斯数据库与MySQL数据库的区别 在当今数据驱动的时代&#xff0c;选择合适的数据库管理系统&#xff08;DBMS&#xff09;对于项目的成功至关重要。高斯数据库和MySQL作为两款广泛使用的数据库系统&#xff0c;各自具有独特的特点和优势&#xff0c;适用于不同的应用场景。本…...