MCU开发学习记录17* - RTC学习与实践(HAL库) - 日历、闹钟、RTC备份寄存器 -STM32CubeMX

名词解释:

RTC:Real-Time Clock

统一文章结构(数字后加*):

第一部分: 阐述外设工作原理;第二部分:芯片参考手册对应外设的学习;第三部分:使用STM32CubeMX进行外设初始化;第四部分:添加应用代码;第五部分:附上本篇文章的工程代码的下载地址。
本文将介绍RTC的相关概念以及STM32CubeMX生成RTC的配置函数,实现日历与闹钟功能,同时使用备份寄存器存放时间数据,保证上电后以备份寄存器存放时间数据进行运行。

一、什么是RTC?

1.1 ​RTC简介

1.1.1 RTC功能介绍

1.2 RTC硬件框图介绍

1.2.1 时钟源

RTC 时钟源---RTCCLK 可以从 LSE、LSI 和 HSE_RTC 这三者中得到。其中使用最多的是 LSE, LSE 由一个外部的 32.768KHZ(6PF 负载)的晶振提供,精度高,稳定,RTC 首选。LSI 是芯片内部的 30KHZ 晶体,精度较低,会有温漂,一般不建议使用。HSE_RTC 由 HSE 分频得到,最高是 4M,使用的也较少。

1.2.2 预分频器

预分频器 PRER 由7位的异步预分频器PREDIV_A和15位的同步预分频器PREDIV_S组成。
要使用频率为 32.768 kHz 的 LSE 获得频率为 1 Hz 的内部时钟 (ck_spre),需要将异步预分 频系数设置为 128,并将同步预分频系数设置为 256。

异步预分频器时钟 CK_APRE 用于为二进制 RTC_SSR 亚秒递减计数器提供时钟。
异步预分频器时钟 f_CK_APRE = f_RTC_CLK/(PREDIV_A+1)
当 RTC_SSR 寄存器递减到 0 的时候,会使用 PREDIV_S 的值重新装载 PREDIV_S。而PREDIV_S 一般为 255,这样,我们得到亚秒时间的精度是:1/256 秒,即 3.9ms 左右,有了这个亚秒寄存器 RTC_SSR,就可以得到更加精确的时间数据。

同步预分频器时钟 CK_SPRE 用于更新日历,也可以用作 16 位唤醒自动重载定时器的时基。
同步预分频器时钟 f_CK_SPRE = f_RTC_CLK/((PREDIV_S+1) * (PREDIV_A+1))

1.2.3 实时时钟和日历

实时时钟一般是这样表示的:时/分/秒/亚秒。

· RTC_SSR对应于亚秒

亚秒值 = ( PREDIV_S - SS ) / ( PREDIV_S + 1 )

· RTC_TR 对应于时间

· RTC_DR对应于日期

RTC_CLK 经过预分频器后,有一个 512HZ 的 CK_APRE 和 1 个 1HZ 的 CK_SPRE,这两个时钟可以成为校准的时钟输出 RTC_CALIB,RTC_CALIB 最终要输出则需映射到 RTC_AF1 引脚,即PC13 输出,用来对外部提供时钟。

1.2.4 可编程闹钟

STM32F407 提供两个可编程闹钟:闹钟 A(ALARM_A)和闹钟 B(ALARM_B)。通过RTC_CR 寄存器的 ALRAE 和 ALRBE 位置 1 来使能闹钟。当亚秒、秒、分、小时、日期分别与闹钟寄存器 RTC_ALRMASSR/RTC_ALRMAR 和 RTC_ALRMBSSR/RTC_ALRMBR 中的值匹配 时,则可以产生闹钟。

RTC 有两个闹钟,闹钟 A 和闹钟 B,,当 RTC 运行的时间跟预设的闹钟时间相同的时候,相应的标志位 ALRAF(在 RTC_ISR 寄存器中)和 ALRBF 会置 1。利用这个闹钟我们可以做一些备忘提醒功能。

如果使能了闹钟输出(由 RTC_CR 的 OSEL[0:1] 位控制),则 ALRAF 和 ALRBF 会连接到闹钟输出引脚 RTC_ALARM,RTC_ALARM 最终连接到 RTC 的外部引脚 RTC_AF1(即PC13),输出的极性由 RTC_CR 寄存器的 POL 位配置,可以是高电平或者低电平。

1.2.5 时间戳

时间戳即时间点的意思,就是某一个时刻的时间。时间戳复用功能 (RTC_TS) 可映射到 RTC_AF1或 RTC_AF2,当发生外部的入侵事件时,即发生时间戳事件时,RTC_ISR 寄存器中的时间戳标志位 (TSF) 将置 1,日历会保存到时间戳寄存器(RTC_TSSSR、RTC_TSTR 和 RTC_TSDR)中。时间戳往往用来记录危及时刻的时间,以供事后排查问题时查询。

1.2.6 周期性自动唤醒

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。

1.3 RTC的低功耗、中断

1.3.1 RTC的低功耗

1.3.2 RTC的中断(所有 RTC 中断均与 EXTI 控制器相连)

要使能 RTC 闹钟中断,需按照以下顺序操作:

  1. 将 EXTI 线 17 配置为中断模式并将其使能,然后选择上升沿有效。
  2. 配置 NVIC 中的 RTC_Alarm IRQ 通道并将其使能。
  3. 配置 RTC 以生成 RTC 闹钟(闹钟 A 或闹钟 B)。

要使能 RTC 唤醒中断,需按照以下顺序操作:

  1. 将 EXTI 线 22 配置为中断模式并将其使能,然后选择上升沿有效。
  2. 配置 NVIC 中的 RTC_WKUP IRQ 通道并将其使能。
  3. 配置 RTC 以生成 RTC 唤醒定时器事件。

要使能 RTC 入侵中断,需按照以下顺序操作:

  1. 将 EXTI 线 21 配置为中断模式并将其使能,然后选择上升沿有效。
  2. 配置 NVIC 中的 TAMP_STAMP IRQ 通道并将其使能。
  3. 配置 RTC 以检测 RTC 入侵事件。

要使能 RTC 时间戳中断,需按照以下顺序操作:

  1. 将 EXTI 线 21 配置为中断模式并将其使能,然后选择上升沿有效。
  2. 配置 NVIC 中的 TAMP_STAMP IRQ 通道并将其使能。
  3. 配置 RTC 以检测 RTC 时间戳事件。

1.4 读取(RTC_SSR、RTC_TR、RTC_DR)注意点

STM32的RTC模块通过 ​高阶日历影子寄存器​(RTC_SSR、RTC_TR、RTC_DR)实现时间/日期数据的原子性读取。其核心规则如下:

  1. 影子寄存器锁定​:

    • 当读取 RTC_SSR(亚秒)RTC_TR(时间) 时,硬件会立即锁定当前影子寄存器的值,直到 RTC_DR(日期) 被读取后,影子寄存器才会更新。
    • 目的:确保读取的时间、日期、亚秒数据来自同一时刻,避免因寄存器更新导致数据不一致(例如读取时间后日期突变)。
  2. 同步标志(RSF)​​:

    • RSF位(位于 RTC_ISR 寄存器的Bit 5)指示影子寄存器的更新状态:
      • RSF=1:影子寄存器已与真实计数器同步,可安全读取。
      • RSF=0:影子寄存器正在更新或未同步,读取可能不准确。

1.5 RTC****备份寄存器

备份寄存器 (RTC_BKPxR) 包括20 个 32 位寄存器,用于存储 80 字节的用户应用数据。这些寄存器在备份域中实现,可在 VDD 电源关闭时通过 VBAT 保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在器件从待机模式唤醒时复位。

TAMPER1 备用功能 (RTC_TAMP1) 可映射到 RTC_AF1(PC13) 或 RTC_AF2 (PI8),具体取决于 RTC_TAFCR 寄存器中 TAMP1INSEL 位的值(请参见第 23.6.17 节:RTC 入侵和复用功能配置寄存器 (RTC_TAFCR))。修改 TAMP1INSEL 后必须将 TAMPE 位清零,以避免将TAMPF 意外置 1。

二、RTC相关配置函数

2.1 RTC相关结构体

2.1.1 RTC_HandleTypeDef

功能:RTC模块的 ​核心句柄,用于管理RTC实例的配置、状态及数据。

1)Instance:指向 RTC 寄存器基地址。
2)Init:是真正的 RTC 初始化结构体

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

2.1.2RTC_InitTypeDef

功能:定义RTC模块的 ​初始化参数,用于配置时钟源、分频系数等。

cpp 复制代码
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;

2.1.3 RTC_TimeTypeDef

功能:存储和操作 ​时间信息​(时、分、秒、亚秒)。

cpp 复制代码
typedef struct
{
  uint8_t Hours;            /*!< 指定 RTC 时间的小时。
                                 如果选择了 RTC_HourFormat_12,此参数必须是 Min_Data = 0 到 Max_Data = 12 之间的数字。
                                 如果选择了 RTC_HourFormat_24,此参数必须是 Min_Data = 0 到 Max_Data = 23 之间的数字。 */
  // 表示小时,范围根据12小时制 (0-12) 或24小时制 (0-23) 变化。

  uint8_t Minutes;          /*!< 指定 RTC 时间的分钟。
                                 此参数必须是 Min_Data = 0 到 Max_Data = 59 之间的数字。 */
  // 表示分钟,范围为0到59。

  uint8_t Seconds;          /*!< 指定 RTC 时间的秒。
                                 此参数必须是 Min_Data = 0 到 Max_Data = 59 之间的数字。 */
  // 表示秒,范围为0到59。

  uint8_t TimeFormat;       /*!< 指定 RTC 的 AM/PM 时间。
                                 此参数可以是 @ref RTC_AM_PM_Definitions 中的一个值。 */
  // 在12小时制下指定上午 (AM) 或下午 (PM),具体值参考 RTC_AM_PM_Definitions 定义。

  uint32_t SubSeconds;      /*!< 指定 RTC_SSR RTC 子秒寄存器的内容。
                                 此参数对应于时间单位范围在 [0-1] 秒之间,粒度为 [1 秒 / SecondFraction +1]。 */
  // 表示子秒 (小于1秒的部分),粒度由 SecondFraction 决定。

  uint32_t SecondFraction;  /*!< 指定与同步预分频器因子值 (PREDIV_S) 对应的子秒寄存器内容的范围或粒度。
                                 此参数对应于时间单位范围在 [0-1] 秒之间,粒度为 [1 秒 / SecondFraction +1]。
                                 此字段仅由 HAL_RTC_GetTime 函数使用。 */
  // 定义子秒的粒度,与预分频器相关,仅用于 HAL_RTC_GetTime 函数。

  uint32_t DayLightSaving;  /*!< 此接口已弃用。要管理夏令时,请使用 HAL_RTC_DST_xxx 函数。 */
  // 已弃用,用于夏令时管理,现推荐使用 HAL_RTC_DST_xxx 函数。

  uint32_t StoreOperation;  /*!< 此接口已弃用。要管理夏令时,请使用 HAL_RTC_DST_xxx 函数。 */
  // 已弃用,与夏令时相关,现推荐使用 HAL_RTC_DST_xxx 函数。
} RTC_TimeTypeDef;

2.1.4 RTC_DateTypeDef

功能 ​:存储和操作 ​日期信息​(年、月、日、星期)。

cpp 复制代码
typedef struct
{
  uint8_t WeekDay;  /*!< 指定 RTC 日期的星期。
                         此参数可以是 @ref RTC_WeekDay_Definitions 中的一个值 */
  // 表示星期几,具体值由 RTC_WeekDay_Definitions 定义提供。

  uint8_t Month;    /*!< 指定 RTC 日期的月份(BCD 格式)。
                         此参数可以是 @ref RTC_Month_Date_Definitions 中的一个值 */
  // 表示月份,采用 BCD 格式(二进制编码的十进制),具体值由 RTC_Month_Date_Definitions 定义。

  uint8_t Date;     /*!< 指定 RTC 日期。
                         此参数必须是 Min_Data = 1 到 Max_Data = 31 之间的数字 */
  // 表示日期,范围为1到31。

  uint8_t Year;     /*!< 指定 RTC 日期的年份。
                         此参数必须是 Min_Data = 0 到 Max_Data = 99 之间的数字 */
  // 表示年份,范围为0到99。
} RTC_DateTypeDef;

2.2 RTC驱动配置步骤

2.2.1 RTC 初始化

cpp 复制代码
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  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();
  }
  sTime.Hours = 21;
  sTime.Minutes = 13;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_SATURDAY;
  sDate.Month = RTC_MONTH_MAY;
  sDate.Date = 17;
  sDate.Year = 25;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }

//相关中断使能
/** Enable the Alarm A
  */
  sAlarm.AlarmTime.Hours = 22;
  sAlarm.AlarmTime.Minutes = 14;
  sAlarm.AlarmTime.Seconds = 30;
  sAlarm.AlarmTime.SubSeconds = 0;
  sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
  sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY|RTC_ALARMMASK_HOURS;
  sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
  sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
  sAlarm.AlarmDateWeekDay = 1;
  sAlarm.Alarm = RTC_ALARM_A;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable the Alarm B
  */
  sAlarm.AlarmTime.Hours = 15;
  sAlarm.AlarmTime.Minutes = 50;
  sAlarm.AlarmTime.Seconds = 50;
  sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY|RTC_ALARMMASK_HOURS
                              |RTC_ALARMMASK_MINUTES;
  sAlarm.Alarm = RTC_ALARM_B;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable the WakeUp
  */
  if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 2, RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable the RTC Tamper 1
  */
  sTamper.Tamper = RTC_TAMPER_1;
  sTamper.PinSelection = RTC_TAMPERPIN_DEFAULT;
  sTamper.Trigger = RTC_TAMPERTRIGGER_LOWLEVEL;
  sTamper.Filter = RTC_TAMPERFILTER_4SAMPLE;
  sTamper.SamplingFrequency = RTC_TAMPERSAMPLINGFREQ_RTCCLK_DIV512;
  sTamper.PrechargeDuration = RTC_TAMPERPRECHARGEDURATION_2RTCCLK;
  sTamper.TamperPullUp = RTC_TAMPER_PULLUP_ENABLE;
  sTamper.TimeStampOnTamperDetection = RTC_TIMESTAMPONTAMPERDETECTION_ENABLE;
  if (HAL_RTCEx_SetTamper_IT(&hrtc, &sTamper) != HAL_OK)
  {
    Error_Handler();
  }

三、基于HAL库实现RTC实验

3.1 基于STM32CubeMX配置RTC实验

3.2 实验的应用代码

3.2.1 日历与时间读取函数

3.2.2 闹钟回调函数

3.2.3 读写RTC备份寄存器

3.2.4 使用RTC备份寄存器重新加载时间

3.2.5 查看上次启动

3.2.6 修改stm32cubemx代码

3.3 实验备份寄存器无法正常读写的问题

如果发现备份寄存器无法正常读写。可能和RTC_AF1有关。有三个解决方法:

  1. __HAL_RTC_TAMPER_CLEAR_FLAG(&hrtc, RTC_FLAG_TAMP1F);

这个不太好使。

  1. 可以尝试将 RTC_AF1(PC13)引脚连接 高电平 or 低电平。

  2. 禁用TAMPE位

cpp 复制代码
// 清除TAMPE位(禁用TAMP1入侵检测)
CLEAR_BIT(RTC->TAMPCR, RTC_TAMPCR_TAMP1E);

四、本文的工程文件下载链接

工程Github下载链接:https://github.com/chipdynkid/MCU-DL-STM32

(国内)工程Gitcode下载链接https://gitcode.com/chipdynkid/MCU-DL-STM32

相关推荐
贝塔实验室26 分钟前
LDPC码的概念
科技·学习·程序人生·算法·学习方法·程序员创富·改行学it
尘似鹤1 小时前
微信小程序学习(三)
学习
尘似鹤1 小时前
微信小程序学习(二)
学习·微信小程序·小程序
lingzhilab2 小时前
零知IDE——STM32F407VET6与ADS1115模数转换器实现多通道数据采集显示系统
stm32·单片机·开源
xxy.c5 小时前
基于IMX6ULL的时钟,定时器(EPIT,GPT)
单片机·嵌入式硬件·fpga开发
happygrilclh6 小时前
stm32L496 flash 分配
stm32·单片机·嵌入式硬件
古译汉书7 小时前
嵌入式铁头山羊STM32-各章节详细笔记-查阅传送门
数据结构·笔记·stm32·单片机·嵌入式硬件·个人开发
一枚码农~10 小时前
STM32红外与LED控制实战
单片机·嵌入式硬件
Heavy sea10 小时前
STM32定时器(寄存器与HAL库实现)
stm32·单片机
Coovally AI模型快速验证10 小时前
从避障到实时建图:机器学习如何让无人机更智能、更安全、更实用(附微型机载演示示例)
人工智能·深度学习·神经网络·学习·安全·机器学习·无人机