Diving into the STM32 HAL----- Real-Time Clock笔记
大量嵌入式应用程序需要跟踪当前时间和日期。数据记录器、定时器、家用电器和控制设备只是一个有限的例子。传统上,微控制器与专用 IC 连接,这些 IC 能够使用 SPI 或 I²C 总线进行通信。例如,ST M41T81 IC,这是一种流行的实时时钟 (RTC),需要几个无源器件和一个 32kHz 振荡器来跟踪当前时间。此外,它还能够生成警报事件并充当看门狗定时器。
所有 STM32 微控制器都提供了一个集成的 RTC 单元,不仅限于跟踪当前日期/时间。事实上,RTC 提供了一些额外的相关功能,例如防篡改检测、警报事件的生成以及从更深的低功耗模式中唤醒 MCU 的能力。本章介绍如何使用相关的 CubeHAL 模块对此外设进行编程。
1、RTC 外设简介
STM32 RTC 是一个独立的二进制编码十进制 (BCD) 计数器。BCD 是一种二进制编码,其中十进制数的每个数字都由固定数量的位独立表示。例如,RTC 定时器按以下方式表示当前小时:
• 两位用于对 小时 十位进行编码;
• 四位用于对 小时 个位进行编码;
• 三位用于对 分钟 十位进行编码;
• 四位用于对 分钟 个位进行编码。
上图 18.1 显示了 STM32 RTC 如何以 BCD 格式对当前时间进行编码。为什么使用这种方法对日期/时间进行编码?这种跟踪当前日期/时间的方式是小型嵌入式系统的典型特征,它允许以人类可读的格式表示时间,而无需执行任何类型的转换。传统上,高级操作系统使用 unsigned long 变量来跟踪时间,该变量每秒自动递增一次。例如,UNIX 时间表示自纪元以来经过的秒数,纪元对应于 1970 年 1 月 1 日星期四 00:00:00。但是,需要大量的 CPU 能力和固件空间才能将自该日期以来经过的秒数转换为当前日期/时间。转换例程需要跟踪多个因素,例如一个月有多少天、闰年和秒数等。BCD 编码允许以地球上许多人可以理解的方式立即安排当前日期/时间,但代价是内部电路更加复杂。
STM32 RTC 外设可以轻松配置和显示日历数据字段:
• 日历功能:
– 亚秒(不可编程)
– 秒
– 分钟
– 12 小时或 24 小时格式的小时
– 星期几(一周中的)
– 日(一月中的)
– 月
– 年
• 自动管理 28 天、29 天(闰年)、30 天和 31 天的月份
• 夏令时调整可通过软件编程
与大多数 STM32 外设不同,RTC 可以独立于三个不同的时钟源进行计时:LSI、LSE 和 HSE。一系列专用的 prescaler 允许将 1Hz 时钟传送到日历单元,而不管时钟源如何。当 RTC (RTCCLK) 的 clock source 是 HSE 时,用户有责任正确配置 prescalers,以便正确的 clock frequency 可以馈送到 RTC。但是,CubeMX 旨在根据指定的 HSE 晶体频率自动处理该事件。
即使 RTC 提供了纠正 clock不精确的工具,并非所有 clock sources 都适合实现 RTC 的良好精度,特别是当 MCU 在与环境温度不同的温度下工作时。如果精度对您的应用很重要,那么强烈建议使用专用的外部 LSE 晶体,并根据晶体规格和 PCB 布局进行调整。
RTC 功能不仅限于时间/日期管理。RTC 提供两个独立的告警单元,分别名为 Alarm A 和 Alarm B,可用于在 RTC 计数器达到配置的告警值时生成事件。闹钟单位是高度可定制的:亚秒、秒、分钟、小时和日期字段可以独立选择或屏蔽,以提供丰富的闹钟组合。RTC 与两个警报单元一起,提供了一个独立的、可编程的专用唤醒单元,用于将 MCU 从更深的睡眠状态中唤醒。事实上,我们将看到 RTC 是唯一能够以可编程方式将 MCU 从待机休眠状态唤醒的外设。
最后,RTC 能够对多个给定输入进行采样以检测篡改:由于 RTC 外设可以由多个 STM32 微控制器中的电池²供电,因此即使设备断电,它也能够检测篡改。在篡改检测时,将设置一个特定的寄存器,并且备份存储器的内容也将归零。
2、HAL_RTC 模块
要对 RTC 外设进行编程,HAL 定义了 C 结构体RTC_HandleTypeDef,其定义方式如下:
typedef struct {
RTC_TypeDef *Instance; /* Register base address */
RTC_InitTypeDef Init; /* RTC required parameters */
HAL_LockTypeDef Lock; /* RTC locking object */
__IO HAL_RTCStateTypeDef State; /* Time communication state */
} RTC_HandleTypeDef;
此结构体唯一值得注意的字段是 Instance,它是指向 RTC 外设描述符的指针,以及用于配置外设的 Init 字段。该字段是 C 结构体RTC_InitTypeDef的一个实例,其定义方式如下:
typedef struct {
uint32_t HourFormat; /* Specifies the RTC Hour Format. */
uint32_t AsynchPrediv; /* Specifies the RTC Asynchronous Predivider value. */
uint32_t SynchPrediv; /* Specifies the RTC Synchronous Predivider value. */
uint32_t OutPut; /* Specifies which signal will be routed to the RTC output. */
uint32_t OutPutPolarity; /* Specifies the polarity of the output signal. */
uint32_t OutPutType; /* Specifies the RTC Output Pin mode. */
} RTC_InitTypeDef;
• HourFormat:此字段指定小时格式,它可以采用值 RTC_HOURFORMAT_12 来设置 AM/PM 小时格式,RTC_HOURFORMAT_24指定 24 小时/天格式。
• AsynchPrediv 和 SynchPrediv:两个预分频器用于派生 1Hz 时钟,以从 LSI/LSE/HSE 振荡器源馈送 RTC 外设。asynchronous prescaler(7 位 counter )馈送给 synchronous prescaler(15 bit counter )。必须根据以下公式设置这两个字段的值,以便达到 1Hz 频率。
其中 CalendarCLK 是 LSI/LSE/HSE 之一。最新的 CubeMX 版本 (4.22) 无法自动派生 AsynchPrediv 和 SynchPrediv 字段的正确值。可以使用下表中的值来获得大多数相关的振荡器频率。
我们将看到具有高引脚数的 STM32 MCU 提供多个独立的电源域。RTC 属于 VBAT 域,即通过 VBAT 引脚供电的所有外设的集合。该域专门设计为与电池相连,即使主电源(即 MCU 内核)关闭,属于该域的所有外设也会继续工作。
• OutPut:指定路由到 RTC 输出的信号 I/O。它可以使用值 RTC_OUTPUT_ALARMA、RTC_OUTPUT_ALARMB、RTC_OUTPUT_WAKEUP 和 RTC_OUTPUT_DISABLE 的,以将输出路由到与警报 A、B、唤醒相关的信号或禁用输出信号。请注意,与给定警报关联的实际 GPIO 是在 MCU 开发期间设计的,并且是固定的。根据所使用的封装类型,可能只有一个信号 I/O 可用,并在三个警报源之间共享。例如,所有采用 LQFP-64 封装的 STM32 MCU 都只有一个名为 AF1 的警报 I/O,并连接到 PC13 引脚。
• OutPutPolarity:此字段指定信号的输出极性,它可以采用值 RTC_OUTPUT_POLARITY_HIGH 和 RTC_OUTPUT_POLARITY_LOW。
• OutPutType:此字段指定输出信号的类型,它可以采用值 RTC_OUTPUT_TYPE_OPENDRAIN 和 RTC_OUTPUT_TYPE_PUSHPULL。
要配置 RTC 外设,我们使用函数:
HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);
它接受指向之前看到的 RTC_HandleTypeDef 结构实例的指针。
2.1、设置和检索当前日期/时间
CubeHAL 实现单独的例程和 C 结构来设置和检索当前日期和时间。函数:
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
用于设置/获取当前时间,而函数:
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);
用于设置/获取当前日期。
用于设置/获取当前时间的 RTC_TimeTypeDef 结构体的定义方式如下:
typedef struct {
uint8_t Hours; /* Specifies the RTC Time Hour. This parameter must be a number between 0 and 12 if the 12 hours format is selected. Otherwise, it must be anumber between 0 and 23 if the 24 hours format is selected */
uint8_t Minutes; /* Specifies the RTC Time Minutes. This parameter must be a number between 0 and 59 */
uint8_t Seconds; /* Specifies the RTC Time Seconds. This parameter must be a number 0 and 59 */
uint8_t TimeFormat; /* Specifies the RTC AM/PM Time. */
uint32_t SubSeconds; /* Specifies the RTC_SSR RTC Sub Second register content. Not used when setting the timer */
uint32_t SecondFraction; /* Specifies the range or granularity of Sub Second register */
uint32_t DayLightSaving; /* Specifies DayLight Save Operation. */
uint32_t StoreOperation; /* Specifies Store Operation value */
} RTC_TimeTypeDef;
• Hours, Minutes, Seconds:这些字段用于设置当前时间。
• TimeFormat:用于设置时间格式(12/24 小时),它可以采用值 RTC_HOURFORMAT_12 或 RTC_HOURFORMAT_24。
• SubSeconds:当 HAL_RTC_GetTime() 例程填充结构RTC_TimeTypeDef时,此字段包含当前的亚秒值。它被 HAL_RTC_SetTime() 例程忽略。此字段对应于 [0-1] 秒之间的时间单位范围,粒度等于 1s /(SecondFraction+1)。
• SecondFraction:指定 SubSeconds 字段的粒度,它与 Synchronous prescaler factor 值相对应。此字段将仅由 HAL_RTC_GetTime() 函数使用。
• DayLightSaving:此字段指定夏令时,它可以采用值 RTC_DAYLIGHTSAVING_SUB1H、RTC_DAYLIGHTSAVING_ADD1H RTC_DAYLIGHTSAVING_NONE。
用于设置/获取当前日期的 RTC_DateTypeDef 结构体的定义方式如下:
typedef struct {
uint8_t WeekDay; /* Specifies the RTC Date WeekDay. */
uint8_t Month; /* Specifies the RTC Date Month (in BCD format). */
uint8_t Date; /* Specifies the RTC Date. */
uint8_t Year; /* Specifies the RTC Date Year. */
} RTC_DateTypeDef;
所有四个时间/日期相关函数都接受时间/日期相关字段的格式作为最后一个参数。此参数可以采用 RTC_FORMAT_BIN 和 RTC_FORMAT_BCD 的值。如果传递RTC_FORMAT_BIN常量,则时间/日期相关字段以常规二进制格式表示。例如,时间 “12:45” 按原样表示。相反,如果传递了 RTC_FORMAT_BCD 常量,则值以 BCD 表示。这意味着每个时间/日期相关字段(占用一个字节)都必须解释为两个 半字节,它们对应于十进制数的数字。因此,按照前面的相同示例,我们有十进制数 “12” 以二进制格式表示为 18,这对应于十六进制表示中的 12(见下图)。
2.1.1、读取日期/时间值的正确方法
当前日期/时间值无法自由读取,但有一个定义明确的过程可以遵循。这是因为,默认情况下,我们不直接访问 RTC 内部日期/时间寄存器。RTC 是一个独立运行的外设,它不通过 APB 总线进行计时。当代码读取日历字段时,它会访问影子寄存器,其中包含由 RTC clock (RTCCLK) 驱动的实际日历时间和日期的副本。每两个 RTCCLK 周期执行一次复制,与系统时钟 (SYSCLK) 同步。此外,我们必须在 HAL_RTC_GetTime() 之后调用 HAL_RTC_GetDate(),即使我们对当前日期不感兴趣。这是因为对 HAL_RTC_GetDate() 的调用会解锁高阶日历影子寄存器中的值,以确保时间和日期值之间的一致性。读取 RTC 当前时间会锁定日历影子寄存器中的值,直到读取当前日期。这是 STM32 平台的新手经常犯的一个错误:当使用 HAL_RTC_GetTime() 访问与时间相关的字段时,除非我们在相应的影子寄存器中读取与日期相关的字段的内容,否则我们会收到最后传输的时间。
在系统重置或退出低功耗模式后,应用程序必须等待 RTC 内部寄存器和影子寄存器之间的同步,然后才能读取日历影子寄存器。为了执行此操作,CubeHAL 提供了以下函数:
HAL_StatusTypeDef HAL_RTC_WaitForSynchro(RTC_HandleTypeDef* hrtc);
但是,当且仅当我们想在系统重置或从低功耗模式唤醒后立即访问影子寄存器,此时 SYSCLK 速度仍处于其最小频率(因为它由 HSI 馈送),则需要调用此函数。如果 HCLK 速度至少比 RTCCLK 快八倍,那么影子寄存器的同步发生在几个 clock cycles 内。使用 HAL_RTC_WaitForSynchro() 例程时,请务必记住,默认情况下,在写入模式下对所谓的备份域(包括 RTC 外设)的访问是禁用的,以防止由于电源不稳定而损坏外设寄存器。但是,HAL_RTC_WaitForSynchro() 例程需要在写模式下访问 RTC 寄存器,因此我们需要使用宏 __HAL_RTC_WRITEPROTECTION_DISABLE() 来启用对备份域的写模式访问,如下所示:
/* Disable the write-protection */
__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);
/* Wait until the shadow registers are synchronized */
HAL_RTC_WaitForSynchro(&hrtc);
/* Enable again the write-protection to prevent registers corruption */
__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);
最后,可以绕过对影子寄存器的访问。在这种情况下,不是必须等待同步时间,但必须由软件检查日历寄存器的一致性。用户必须读取所需的日历字段值两次。然后比较两个读取序列的结果。如果结果匹配,则读取结果正确。如果它们不匹配,则必须再次读取字段,并且第三次读取结果有效。为了绕过影子寄存器,CubeHAL 提供了以下函数:
HAL_StatusTypeDef HAL_RTCEx_EnableBypassShadow(RTC_HandleTypeDef* hrtc);
要再次重新启用影子寄存器访问,我们可以使用函数:
HAL_StatusTypeDef HAL_RTCEx_DisableBypassShadow(RTC_HandleTypeDef* hrtc);
2.2、配置闹钟
STM32 RTC 提供了两个闹钟,分别名为 Alarm A 和 Alarm B,它们具有相同的功能。闹钟可以在用户编程的给定时间或/和日期生成。要设置闹钟,我们使用以下函数:
HAL_StatusTypeDef HAL_RTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format);
我们最终可以使用以下函数轮询闹钟,直到事件发生:
HAL_StatusTypeDef HAL_RTC_PollForAlarmAEvent(RTC_HandleTypeDef *hrtc, uint32_t Timeout);
可以配置闹钟,以便在触发时断言专用中断。
与两个闹钟关联的 IRQ 是 RTC_Alarm_IRQn,要在中断模式下配置闹钟,我们可以使用以下专用例程:
HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format);
与所有 CubeHAL 中断处理程序例程一样,我们需要从 RTC_Alarm_IRQn ISR 调用 HAL_RTC_AlarmIRQHandler()。要收到闹钟事件的通知,我们可以实现相应的回调:
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
可以使用函数停用闹钟:
HAL_StatusTypeDef HAL_RTC_DeactivateAlarm(RTC_HandleTypeDef *hrtc, uint32_t Alarm);
用于设置闹钟的 struct RTC_AlarmTypeDef 按以下方式定义:
typedef struct {
RTC_TimeTypeDef AlarmTime; /* Specifies the RTC Alarm Time members */
uint32_t AlarmMask; /* Specifies the RTC Alarm Masks. */
uint32_t AlarmSubSecondMask; /* Specifies the RTC Alarm SubSeconds Masks. */
uint32_t AlarmDateWeekDaySel; /* Specifies the RTC Alarm is on Date or WeekDay. */
uint8_t AlarmDateWeekDay; /* Specifies the RTC Alarm Date/WeekDay. */
uint32_t Alarm; /* Specifies the alarm (A or B). */
} RTC_AlarmTypeDef;
• AlarmTime:此字段是之前看到的 RTC_TimeTypeDef 结构的实例,用于设置闹钟时间。
• AlarmMask:闹钟由一个与 RTC 时间计数器长度相同的寄存器组成。当 RTC 计数器与闹钟寄存器中配置的值匹配时,它会生成一个事件。AlarmMask 字段定义闹钟和 RTC 时间寄存器之间的比较标准。它可以从下表中报告的值中假定一个或多个值(通过位掩码)。例如,如果我们希望闹钟发生在 12:45:03,则使用 RTC_ALARMMASK_NONE 值。相反,如果我们想在给定的分钟和秒内每小时生成一次警报,则可以使用值 RTC_ALARMMASK_HOURS。
• AlarmDateWeekDaySel:指定闹钟是设置在某个日期 (月份中的某一天) 还是工作日 (星期一、星期二等)。它可以采用值 RTC_ALARMDATEWEEKDAYSEL_DATE 或 RTC_ALARMDATEWEEKDAYSEL_WEEKDAY。
• AlarmDateWeekDay:如果 AlarmDateWeekDaySel 字段设置为 RTC_ALARMDATEWEEKDAYSEL_DATE,则此字段必须设置为 1-31 范围内的值。相反,如果 AlarmDateWeekDaySel 字段设置为 RTC_ALARMDATEWEEKDAYSEL_WEEKDAY,则此字段必须设置为符号常量 RTC_WEEKDAY_MONDAY、RTC_WEEKDAY_TUESDAY 等。
• AlarmSubSecondMask:RTC 时间的亚秒级寄存器,可用于生成粒度小于秒的事件。通过屏蔽亚秒寄存器的单个位,可以每 1/128 秒、1/64 秒等生成一次事件。有关掩蔽可能性及其对闹钟行为的影响的更多信息,请参阅http://bit.ly/2fcR1uE。例如,此功能允许将 RTC 用作 HAL 的时基生成器。ST 在 CubeHAL 项目中提供了这样一个示例。
• Alarm:它指定配置的闹钟,并且可以采用值 RTC_ALARM_A 和 RTC_ALARM_B。
2.3、定期唤醒单元
我们将看到 STM32 微控制器提供了选择性地禁用内部功能以降低功耗的能力。几种低功耗模式使程序员可以决定最适合其需求的功耗水平,尤其是在开发电池供电设备时。STM32 RTC 具有周期性时基和唤醒单元,当微控制器在低功耗模式下运行时,可以唤醒系统。该装置是一个可编程的 16 位下降计数和自动重新加载定时器。当此计数器达到零时,将设置一个标志并生成中断(如果启用)。唤醒装置具有以下特点:
• 可编程的倒计时自动重新加载定时器。
• 特定标志和中断能够将设备从低功耗模式唤醒。
• 唤醒备用功能输出,可路由到 RTC 闹钟输出(输出在闹钟 A、闹钟 B 或唤醒单元之间共享),极性可配置。
• 一整套预分频器,用于选择所需的等待期。
唤醒计数器计数频率可以通过 RTCCLK 源得出,并最终进一步预分频,也可以通过日历时钟(即,在 asynchronous 和 synchronous prescaler 之后)得出。当为 LSE 振荡器选择外部时钟时,这提供了生成频率范围为 122μs 到超过 48 天的唤醒事件的可能性。
为了设置唤醒事件,CubeHAL 提供了以下函数:
HAL_StatusTypeDef HAL_RTCEx_SetWakeUpTimer(RTC_HandleTypeDef *hrtc, uint32_t WakeUpCounter, uint32_t WakeUpClock);
其中 WakeUpCounter 参数设置唤醒计数器的自动重新加载值 (即周期) ,WakeUpClock 参数设置计数器频率,它可以采用下表中列出的值之一。
独立的 IRQ (RTC_WKUP_IRQn) 与唤醒计数器相关联,可以使用以下函数启用它:
HAL_RTCEx_SetWakeUpTimer_IT(RTC_HandleTypeDef *hrtc, uint32_t WakeUpCounter, uint32_t WakeUpClock);
像往常一样,我们必须从 ISR 调用 HAL_RTCEx_WakeUpTimerIRQHandler(),并准备好通过实现 HAL_RTCEx_WakeUpTimerEventCallback() 来收到唤醒事件的通知。否则,如果在轮询模式下使用唤醒计数器,我们可以使用 HAL_RTCEx_PollForWakeUpTimerEvent() 来检测唤醒事件(老实说,没什么用处)。
2.4、时间戳生成和篡改检测
RTC 外设根据所使用的封装,硬连线到多个信号 I/O。这些 I/O 可用于在其状态更改时生成时间戳。当前日期/时间保存在专用 registers 中,如果启用,也会触发相应的中断。为了设置时间戳生成,CubeHAL 提供了以下函数:
HAL_RTCEx_SetTimeStamp(RTC_HandleTypeDef *hrtc, uint32_t TimeStampEdge, uint32_t RTC_TimeStampPin);
TimeStampEdge 参数指定激活时间戳的引脚边缘。此参数可以是以下值之一:RTC_TIMESTAMPEDGE_RISING 和 RTC_TIMESTAMPEDGE_FALLING。RTC_TimeStampPin 指定了用于生成时间戳的 I/O,它可以采用值 RTC_TIMESTAMPPIN_DEFAULT (通常对应于 PC13 pin),或者值 RTC_TIMESTAMPPIN_PA0 或 RTC_TIMESTAMPPIN_POS1 来表示替代引脚(通常为 PA0 或 PI8)。
要启用与专用 IRQ 关联的相应中断TAMP_STAMP_IRQn我们可以使用函数:
HAL_RTCEx_SetTimeStamp_IT(RTC_HandleTypeDef *hrtc, uint32_t TimeStampEdge, uint32_t RTC_TimeStampPin);
HAL_RTCEx_TamperTimeStampIRQHandler() 是从 ISR 调用的处理程序,而 HAL_RTCEx_TimeStampEventCallback() 是相应的回调。
相反,如果我们想在轮询模式下使用时间戳功能,我们可以使用函数:
HAL_RTCEx_PollForTimeStampEvent(RTC_HandleTypeDef *hrtc, uint32_t Timeout);
轮询时间戳事件。
要检索保存在时间戳寄存器中的日期/时间,我们可以使用以下函数:
HAL_RTCEx_GetTimeStamp(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTimeStamp, RTC_DateTypeDef *sTimeStampDate, uint32_t Format);
可以配置相同的 I/O 来检测篡改。CubeHAL 提供专用例程和 C 结构来对此功能进行编程。我们不会在这里讨论它们。
2.5、RTC 校准
可以校准 RTC 以补偿 RTCCLK 源的不精确性。这对于需要提高 RTC 精度的应用程序以及在温度变化时需要 RTC 稳定性的应用程序特别有用。RTC 外设提供两种类型的校准:粗校准和平滑校准。
2.5.1、RTC 粗调校准
数字粗调校准可用于通过在异步预分频器的输出端添加(正校准)或屏蔽(负校准)时钟周期来补偿晶体的不精确性。可以以约 2 ppm 的分辨率执行负校准,以约 4 ppm 的分辨率执行正校准。最大校准范围为 -63 ppm 至 126 ppm。
我们可以通过在 Asynchronous prescaler 之前将 output frequency 路由到专用 pin (通常与 AF1 pin 重合)来测量 output frequency 。当此 I/O 用于此类操作时,也称为 AFO_CALIB pin。通过使用示波器测量输出频率,我们可以评估 RTCCLK 的质量。预计 AFO_CALIB 将发射具有固定 512Hz 频率的方波。
为了设置粗略校准,HAL 提供了以下函数:
HAL_RTCEx_SetCoarseCalib(RTC_HandleTypeDef *hrtc, uint32_t CalibSign, uint32_t Value);
CalibSign 参数可以接受值 RTC_CALIBSIGN_POSITIVE 和 RTC_CALIBSIGN_NEGATIVE,而值参数的范围可以是 0 到 63,使用负号时为 2 ppm,使用正号时,范围可以是 0 到 126,步长为 4 ppm。
当使用粗略校准校准 RTC 时,强调以下几点很重要。
• 无法检查校准结果,因为 512Hz 输出在校准块之前(见上图http://bit.ly/2fcR1uE)。可以在某些 STM32 MCU 中检查校准,因为 1Hz CK_Spre 输出在粗略校准模块之后。
• 校准设置只能在初始化期间更改。因此,请仅对静态校正使用粗略校准。
2.5.2、RTC 细调校准
RTC 频率可以校准,分辨率约为 0.954 ppm,范围为 -487.1 ppm 至 +488.5 ppm。频率的校正是通过一系列的小调整来进行的(添加和/或同时减去单个 RTCCLK 脉冲)。这些调整在几秒钟(8 秒、16 秒或 32 秒)的范围内分布良好,因此即使在短时间内观察 RTC 也能得到很好的校准。
两个名为 CALP 和 CALM 的 RTC 寄存器用于在所选范围(8、16 或 32 秒)内添加和/或减少给定数量的 RTCCLK 脉冲。CALM 允许 RTC 频率以精细的分辨率降低高达 487.1 ppm,位 CALP 可用于将频率提高 488.5 ppm。将 CALP register 设置为 '1' 可以有效地每 2¹¹ 个 RTCCLK 周期插入一个额外的 RTCCLK 脉冲,这意味着在每 32 秒周期内添加 512 个 clocks 脉冲。通过使用 CALM 寄存器指定在 32 秒周期内要屏蔽的 RTCCLK 脉冲数,要添加的 clock 脉冲数可以减少到 0。
将 CALM 与 CALP 结合使用,可以在 32 秒的周期内添加 -511 至 +512 个 RTCCLK 周期的偏移,这意味着校准范围为 -487.1 ppm 至 +488.5 ppm,分辨率约为 0.954 ppm。给定输入频率 (FRTCCLK) 的有效校准频率 (FCAL) 的计算公式如下:
为了设置平滑校准,HAL 提供了以下函数:
HAL_RTCEx_SetSmoothCalib(RTC_HandleTypeDef* hrtc, uint32_t SmoothCalibPeriod, uint32_t SmoothCalibPlusPulses, uint32_t SmouthCalibMinusPulsesValue);
SmoothCalibPeriod 参数可以假定下表中列出的值,并定义分布区间。
SmoothCalibPlusPulses 参数可以假设 RTC_SMOOTHCALIB_PLUSPULSES_SET 和 RTC_SMOOTHCALIB_PLUSPULSES_RESET 的值,用于设置/重置 CALP 寄存器内的单个位。
SmouthCalibMinusPulsesValue 参数设置要减去的时钟脉冲数,它可以是 0 到 511 之间的任何值。
与 RTC 粗校准不同,通过检查 AFO_CALIB 引脚的输出,可以很容易地检查日历时钟(RTC 时钟)上的细调校准效果。还可以动态执行细调校准,以便在温度变化或检测到其他因素时进行更改。
2.5.3、参考时钟检测
在某些应用中,可以使用外部参考时钟主动校准 RTC。参考时钟(50Hz 或 60Hz - 典型的电源频率)应比 32.768kHz LSE 时钟具有更高的精度。这就是为什么具有较高引脚数的 STM32 MCU 中的 RTC 提供参考时钟输入(名为 RTC_50Hz pin)的原因,该输入可用于补偿日历频率 (1Hz) 的不精确性。
RTC_50Hz pin 应配置为 input floating mode。这种机制使日历能够像参考时钟一样精确。参考时钟检测使用以下函数启用:
HAL_StatusTypeDef HAL_RTCEx_SetRefClock(RTC_HandleTypeDef* hrtc);
当 reference clock detection 使能时,asynchronous 和 synchronous prescalers 都必须设置为其默认值: 0x7F 和 0xFF。当 reference clock detection 启用时,每个 1Hz clock edge 将与最近的 reference clock edge 进行比较(如果在给定的时间窗口内找到一个)。在大多数情况下,两个 clock edges 正确对齐。当 1Hz clock 由于 LSE clock的不精确性而变得未对齐时, RTC 会将 1Hz clock 稍微移动,以便将来的 1Hz clock edges 对齐。更新窗口为三个ck_calib周期(ck_calib是粗调校准块的输出 - 见前图)。
如果参考时钟停止,则日历将仅根据 LSE 时钟持续更新。然后,RTC 使用以 synchronous prescaler output clock (ck_spre) 边沿为中心的检测窗口等待 reference clock。检测窗口为 7 个 ck_calib 周期。
参考时钟可能具有较大的局部偏差(例如在 500ppm 范围内),但从长远来看,它必须比 32kHz 石英精确得多。仅当需要在 loss 后检测 reference clock 时,才使用 detection system。由于检测窗口比 reference clock period 稍大,因此该检测系统带来了 1 ck_ref周期的不确定性(对于 50Hz reference clock,则为 20ms),因为我们在检测窗口中可以有 2 ck_ref 边沿。然后使用 update window ,它不会带来任何错误,因为它小于 reference clock period。我们假设 ck_ref 每天的损失不会超过一次。因此,每月的总不确定度为 20 毫秒 * 1 * 30 = 0.6 秒,这比典型石英的不确定度(35ppm 石英每月 1.53 分钟)要小得多。
3、使用备份 SRAM
大多数 STM32 微控制器提供了一个额外的内存区域,称为备份内存(或 RTC 备份数据内存)。如果 VBAT 引脚连接到备用电源,则当 VDD 关闭时,该存储器由 VBAT 上电,因此其内容在系统重置时不会丢失。即使设备在低功耗模式下运行,备份内存的内容仍然有效。相反,当 tamper detection 事件发生时,备份寄存器将被重置。
默认情况下,在系统重置后,对所谓的备份域(包括备份存储器和 RTC 寄存器)的写入模式访问被禁用,以保护它免受由于电源不稳定而可能和不需要的写入访问。要修改整个域,从而修改备份内存,我们需要明确遵循以下过程:
• 使用宏 __HAL_RCC_PWR_CLK_ENABLE() 启用电源接口时钟;
• 调用 HAL_PWR_EnableBkUpAccess() 函数以启用对备份域(RTC 寄存器、RTC 备份数据存储器)的访问。
• 使用函数 HAL_RTCEx_BKUPWrite() 和 HAL_RTCEx_BKUPRead() 函数在可用的备份寄存器内进行写入/读取(寄存器的数量因几个 STM32 MCU 而异)。