一、RTC原理简介
RTC (Real Time Clock):实时时钟。STM32F4 的 RTC,是一个独立的 BCD 定时器/计数器。 STM32F4 的 RTC 提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A 和 ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。 RTC 还包含用于管理低功耗模式的自动唤醒单元。两个 32 位寄存器(TR 和 DR) 包含二进码十进数格式(BCD) 的秒、 分钟、小时(12 或 24 小时制) 、星期、日期、月份和年份。此外, 还可提供二进制格式的亚秒值。STM32F4 的实时时钟(RTC)相对于 STM32F1 来说,改进了不少,自带了日历功能,让软件编程更加简单。
RTC 模块和时钟配置是在后备区域,无论器件状态如何(运行模式、低功耗模式或处于复位状态) ,只要保证后备区域供电正常,RTC 便不会停止工作,所以通常会在后备区域供电端加一个纽扣电池,即使主电源停止供电,后备电源也会启动供电,从而保证 RTC 时钟不停的运行,只有当主电源和后备纽扣电池都没有电的时,RTC 才停止工作。RTC结构框图如图所示:
可把 RTC 结构框图分成 以下5 个子模块:
(1) 标号 1: 时钟源
RTC 时钟源(RTCCLK)可通过时钟控制器从 LSE 时钟、LSI 振荡器时钟以及HSE 时钟三者中选择。其中使用最多的是 LSE,即外部低速时钟, 通常选择一个大小为 32.768KHZ 的晶振提供(配合 2 个谐振电容)。LSI 是芯片内部的 30KHZ晶体,精度比较低,对于时钟精度要求高的场合不建议使用。HSE_RTC 由 HSE 分频得到,最高是 4M。
(2) 标号 2: 预分频器
预分频器 PRER 由 7 位的异步预分频器 APRE 和 15 位的同步预分频器 SPRE组成。为最大程度地降低功耗,预分频器分为 2 个可编程的预分频器。一个通过RTC_PRER寄存器的 PREDIV_A 位配置的 7 位异步预分频器,另一个通过RTC_PRER 寄存器的 PREDIV_S 位配置的 15 位同步预分频器。异步预分频器时钟计算公式为:
fCK_APRE=fRTC_CLK/(PREDIV_A+1)
ck_apre 时钟用于为RTC亚秒递减计数器(RTC_SSR)提供时钟。当该计数器计数到 0 时, 会使用 PREDIV_S 的内容重载 RTC_SSR 。而 PREDIV_S 一般为255,这样,我们得到亚秒时间的精度是:1/256 秒,即 3.9ms 左右,有了这个亚秒寄存器 RTC_SSR,就可以得到更加精确的时间数据。同步预分频器时钟计算公式为:
fCK_SPRE=fRTC_CLK/[(PREDIV_S+1)*(PREDIV_A+1)]
ck_spre 时钟既可以用于更新日历,也可以用作 16 位唤醒自动重载定时器的时基 。通 常 的 情 况 下,我 们 会 选 择 LSE 作 为 RTC 的 时 钟 源,即fRTC_CLK==32.768KHZ。然后经过预分频器 PRER 分频生成 1HZ 的时钟用于更新日历。使用两个预分频器分频的时候,为了最大程度的降低功耗,我们一般把异步预分频器(PREDIV_A)设置成较大的值,为了生成 1HZ 的同步预分频器时钟CK_SPRE,通常我们设置:PREDIV_A=0X7F,即 128 分频;PREDIV_S=0XFF,即 256分频,代入上述同步预分频器时钟计算公式即可得到 fCK_SPRE=1Hz。RTC_CLK 经过预分频器后,有一个 512HZ 的 CK_APRE 和 1 个 1HZ 的CK_SPRE,这两个时钟可以成为校准的时钟输出 RTC_CALIB,RTC_CALIB 最终要输出则需映射到 RTC_AF1 引脚,即 PC13 输出,用来对外部提供时钟。
(3) 标号 3: 实时时钟和日历
STM32F4 的 RTC 时间信息是存放在日历时间( RTC_TR)和日期( RTC_DR)寄存器中, 可以读取也可以写入。亚秒值存放在 RTC 亚秒寄存器(RTC_SSR)中,只能读取不能写入。这些寄存器都具有写保护,所以需要使能后备寄存器访问功能才能读取或写入时间。RTC_TR 时间寄存器存放的是时分秒,寄存器说明如下:
RTC_DR 日期寄存器存放的是年月日星期,寄存器说明如下:
RTC_SSR 亚秒寄存器存放的是 RTC 亚秒值, 寄存器说明如下:
STM32F4 的 RTC 日历时间(RTC_TR)和日期(RTC_DR)寄存器可通过与PCLK1( APB1 时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。每隔两个 RTC_CLK 周期,当前日历值便会复制到影子寄存器,并将 RTC_ISR寄存器的 RSF 位置 1。在停机和待机模式下不会执行复制操作。退出这两种模式时,影子寄存器会在最长 2 个 RTCCLK 周期后进行更新。
当应用读取日历寄存器时,它会访问影子寄存器的内容。也可以通过将RTC_CR 寄存器的 BYPSHAD 控制位置 1 来直接访问日历寄存器。默认情况下,该位被清零,用户访问影子寄存器。读取 RTC_TR 和 RTC_DR 来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以 BCD 码的格式存储的,读出来要转换一下,才可以得到十进制的数据。
(4) 标号 4: 可编程闹钟
RTC 单元提供两个可编程闹钟,即闹钟 A 和闹钟 B。可通过将 RTC_CR 寄存器中的 ALRAE 和 ALRBE 位置 1 来使能可编程闹钟功能。如果日历亚秒、秒、分钟、小时、日期或日分别与闹钟寄存器 RTC_ALRMASSR/RTC_ALRMAR 和RTC_ALRMBSSR/RTC_ALRMBR 中编程的值相匹配,则 ALRAF 和 ALRBF 标志会被置为 1。可 通 过 RTC_ALRMAR 和 RTC_ALRMBR 寄 存 器 的 MSKx 位 以 及RTC_ALRMASSR 和 RTC_ALRMBSSR 寄存器的 MASKSSx 位单独选择各日历字段。可通过 RTC_CR 寄存器中的 ALRAIE 和 ALRBIE 位使能闹钟中断。闹钟 A 和闹钟B(如果已通过 RTC_CR 寄存器中的位 OSEL[0:1] 使能)可连接到 RTC_ALARM输出,RTC_ALARM 最终连接到 RTC 的外部引脚RTC_AF1。可通过RTC_CR 寄存器的 POL 位配置 RTC_ALARM 极性, 可以为输出高电平或低电平。
(5) 标号 5: 周期性自动唤醒
STM32F4 的 RTC 不带秒钟中断了,但是多了一个周期性自动唤醒功能。周期性唤醒功能由 16 位可编程自动重载递减计数器生成,唤醒定时器范围可扩展至 17 位,可用于周期性中断/唤醒。可通过 RTC_CR 寄存器中的 WUTE 位来使能此唤醒功能。唤醒定时器的时钟输入可以是:
唤醒定时器的时钟输入可以是:2、4、8 或 16 分频的 RTC 时钟 (RTCCLK)。
当 RTCCLK 为 LSE (32.768 kHz) 时,可配置的唤醒中断周期介于 122 μ s和 32 s 之间,且分辨率低至 61 μ s。
ck_spre(通常为1Hz内部时钟) 当 ck_spre 频率为1Hz 时,可得到的唤醒时间为 1s 到 36h 左右,分辨率为 1 秒。这一较大的可编程时间范围分为两部分:
— 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_CR2 寄存器中的 WUTIE 位置 1来使能周期性唤醒中断时,它会使器件退出低功耗模式。如果已通过 RTC_CR 寄存器中的位 OSEL[0:1] 使能周期性唤醒标志,则该标志可连接到 RTC_ALARM 输出。可通过 RTC_CR 寄存器的 POL 位配置 RTC_ALARM 极性。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响,所以唤醒定时器,可以用于周期性唤醒 STM32F4。
(7) 标号 7: 入侵检测
STM32F4 的 RTC 有两个入侵检测输入引脚可用,分别是 RTC_AF1( PC13)和RTC_AF2( PI8)。这两个输入既可配置为边沿检测,也可配置为带过滤的电平检测。备份寄存器(RTC_BKPxR) 包括 20 个 32 位寄存器,用于存储 80 字节的用户应用数据。这些寄存器在备份域中实现,可在 VDD 电源关闭时通过 VBAT 保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在器件从待机模式唤醒时复位。只有当发生入侵检测事件时,备份寄存器将复位。通过将RTC_TAFCR 寄存器中的 TAMPIE 位置 1,可在发生入侵检测事件时生成中断。
二、CubeMx工程创建
功能要求:STM32 RTC对时,串口打印当前时间。
1. 芯片选型(本文选用STM32F407IGT6)
2. SYS配置(启用SW下载接口)
3. RCC配置(开启外部低速时钟和高速时钟)
4. 串口配置(UART4,波特率115200,8bits)
开启串口中断。
5. RTC时钟配置
6. GPIO配置(用于验证系统运行状态)
三、关键代码实现
//rtc.c
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;
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();
}
/* USER CODE BEGIN Check_RTC_BKUP */
// 2024/1/17 add
//检查后备寄存器
uint32_t iSetFlag = 0x5053;// 这个0x5053值自己随意
if(iSetFlag != HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0))
{
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x0e;
sTime.Minutes = 0x0b;
sTime.Seconds = 00;
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_THURSDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 18;
sDate.Year = 54;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
// 设置完时钟,设置标志
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, iSetFlag);
/* USER CODE END RTC_Init 2 */
}
else
{ //设置完时钟,设置标志
RTC_TimeTypeDef sTime; //获取一次时间显示
HAL_StatusTypeDef status = HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
RTC_DateTypeDef sDate;
status = HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
}
//主函数
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
/* Private define ------------------------------------------------------------*/
#define LENGTH 3
RTC_DateTypeDef GetData; //获取日期结构体
RTC_TimeTypeDef GetTime; //获取时间结构体
void SystemClock_Config(void);
int main(void)
{
uint8_t RxBuf[LENGTH];
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RTC_Init();
MX_UART4_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
LED_BLUE_ON;
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* Get the RTC current Time */
/*************2024/1/18 add*******************/
HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
/* Get the RTC current Date */
HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);
if(HAL_UART_Receive(&huart4,RxBuf,sizeof(RxBuf),500) == HAL_OK)
//if(HAL_UART_Receive(&huart4,RxBuf,sizeof(RxBuf),300) == HAL_OK)
{
LED_BLUE_OFF;
LED_GREEN_ON;
printf("系统当前时间为:\r\n");
/* Display date Format : yy/mm/dd */
printf("%02d-%02d-%02d %02d:%02d:%02d\r\n",1970 + GetData.Year, GetData.Month, GetData.Date,GetTime.Hours, GetTime.Minutes, GetTime.Seconds);
/* Display time Format : hh:mm:ss */
// printf("%02d:%02d:%02d\r\n",GetTime.Hours, GetTime.Minutes, GetTime.Seconds);
printf("\r\n");
}
HAL_Delay(400);
LED_GREEN_OFF;
LED_BLUE_ON;
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
四、上板验证
1. 程序烧录。
2. 串口功能验证。