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

发布于:2025-05-19 ⋅ 阅读:(29) ⋅ 点赞:(0)

名词解释:

RTC:Real-Time Clock

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

        第一部分: 阐述外设工作原理;第二部分:芯片参考手册对应外设的学习;第三部分:使用STM32CubeMX进行外设初始化;第四部分:添加应用代码;第五部分:附上本篇文章的工程代码的下载地址。

        本文将介绍RTC的相关概念以及STM32CubeMX生成RTC的配置函数,实现日历与闹钟功能,同时使用备份寄存器存放时间数据,保证上电后以备份寄存器存放时间数据进行运行。

一、什么是RTC?

1.1 ​RTC简介

1.1.1 RTC功能介绍

1.2 RTC硬件框图介绍

1.2.1 时钟源

RTC 时钟源—RTCCLK 可以从 LSELSI HSE_RTC 这三者中得到。其中使用最多的是 LSE, LSE 由一个外部的 32.768KHZ6PF 负载)的晶振提供,精度高,稳定,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_CALIBRTC_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 初始化结构体

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

2.1.2 RTC_InitTypeDef 

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

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

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

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

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

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 初始化

  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);
        这个不太好使。

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

3. 禁用TAMPE位

// 清除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


网站公告

今日签到

点亮在社区的每一天
去签到