名词解释:
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)实现时间/日期数据的原子性读取。其核心规则如下:
影子寄存器锁定:
- 当读取 RTC_SSR(亚秒) 或 RTC_TR(时间) 时,硬件会立即锁定当前影子寄存器的值,直到 RTC_DR(日期) 被读取后,影子寄存器才会更新。
- 目的:确保读取的时间、日期、亚秒数据来自同一时刻,避免因寄存器更新导致数据不一致(例如读取时间后日期突变)。
同步标志(RSF):
- RSF位(位于 RTC_ISR 寄存器的Bit 5)指示影子寄存器的更新状态:
- RSF=1:影子寄存器已与真实计数器同步,可安全读取。
- RSF=0:影子寄存器正在更新或未同步,读取可能不准确。
- RSF位(位于 RTC_ISR 寄存器的Bit 5)指示影子寄存器的更新状态:
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