国外扁平化网站设计欣赏,一个网站如何做推广,章贡区建设局网站,平顶山北京网站建设参考#xff1a; 1.正点原子
前言#xff1a; RTC实时时钟是很基本的外设#xff0c;用来记录绝对时间。做个总结#xff0c;达到#xff1a; 1.学习RTC的原理和概念。 2.通过STM32CubeMX快速配置RTC。
27.1 RTC 时钟简介
STM32F407 的实时时钟#xff08;RTC#xf…参考 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_A0X7F即 128 分频PREDIV_S0XFF即 256 分频即可得到 1Hz 的 Fck_sprePREDIV_A 的值我们也是往尽量大的原则以最大程度降低功耗。 ③ 时间和日期相关寄存器 该部分包括三个影子寄存器RTC_SSR亚秒、RTC_TR时间、RTC_DR日期。实时时钟一般表示为时/分/秒/亚秒。RTC_TR 寄存器用于存储时/分/秒时间数据可读可写即可设置或者获取时间。RTC_DR 寄存器用于存储日期数据包括年/月/日/星期可读可写即可设置或者获取日期。RTC_SSR 寄存器用于存储亚秒级的时间这样我们可以获取更加精确的时间数据。 这三个影子寄存器可以通过与 PCLK1APB1 时钟同步的影子寄存器来访问这些时间和日期寄存器也可以直接访问这样可避免等待同步的持续时间。 每隔 2 个 RTCCLK 周期当前日历值便会复制到影子寄存器并置位 RTC_ISR 寄存器的RSF 位。我们可以读取 RTC_TR 和 RTC_DR 来得到当前时间和日期信息不过需要注意的是 时间和日期都是以 BCD 码的格式存储的读出来要转换一下才可以得到十进制的数据。 ④ 可编程闹钟 STM32F407 提供两个可编程闹钟闹钟 AALARM_A和闹钟 BALARM_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_spre1Hz作为输入时钟时可得到的唤醒时间为 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 时必须先设置 INIT1。 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 的分频我们在之前也有讲过这里就不多说了。该寄存器的配置必须在初始化模式INITF1下才可以进行。
⚫ 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 上面) 2RTC(实时时钟) 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;1Instance指向 RTC 寄存器基地址。 2Init是真正的 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 配合使用。
3Lock用于配置锁状态。 4StateRTC 设备访问状态。 ⚫ 函数返回值 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~14DayLightSaving 用来设置日历时间增加 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-InstanceRTC){/* 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 gitgitee.com:xiaoliangliangcong/stm32.git STM32F407ZGT6/7.RTC