文章目录
- [一、RTC 简介](#一、RTC 简介)
-
- [1、什么是 RTC](#1、什么是 RTC)
- [2、RTC 核心特点](#2、RTC 核心特点)
- [3、RTC 三种时钟源](#3、RTC 三种时钟源)
- [4、RTC 核心功能](#4、RTC 核心功能)
- [二、RTC 模块详解](#二、RTC 模块详解)
-
- 0、RTC模块框图
- 1、后备区域(灰色部分)
-
- 1)时钟输入与预分频器
- [2)32 位可编程计数器(RTC计时核心)](#2)32 位可编程计数器(RTC计时核心))
- 3)退出待机模式(WKP_STDBY)
- 2、主域部分
-
- [1)RTC_CR 控制与中断逻辑模块](#1)RTC_CR 控制与中断逻辑模块)
- [2)APB1 接口模块](#2)APB1 接口模块)
- 3、工作流程
- [三、CubeMX 配置 RTC](#三、CubeMX 配置 RTC)
-
- 1、基础配置流程
-
- [1)打开 CubeMX → 点击 Timers → RTC。](#1)打开 CubeMX → 点击 Timers → RTC。)
- [2)Activate Clock Source:勾选 Activate LSE Clock。](#2)Activate Clock Source:勾选 Activate LSE Clock。)
- [3)Activate Calendar:开启日历功能。](#3)Activate Calendar:开启日历功能。)
- [4)RCC开启低速时钟RCC→ Low Speed Clock(LSE):Crystal/Ceramic Resonator。](#4)RCC开启低速时钟RCC→ Low Speed Clock(LSE):Crystal/Ceramic Resonator。)
- [5)时钟源选择:LSE 32.768K。](#5)时钟源选择:LSE 32.768K。)
- [6)Tamper 入侵检测配置](#6)Tamper 入侵检测配置)
- 2、参数配置
- 3、中断配置
-
- [1)NVIC 设置:开启 RTC 全局中断。](#1)NVIC 设置:开启 RTC 全局中断。)
- 四、RTC编程模块
-
- [1、RTC 结构体](#1、RTC 结构体)
-
- [1) RTC 句柄(RTC_HandleTypeDef)](#1) RTC 句柄(RTC_HandleTypeDef))
- 2)初始化参数(RTC_InitTypeDef)
- 3)时间结构体(RTC_TimeTypeDef)
- 4)闹钟结构体(RTC_AlarmTypeDef)
- [2、RTC API](#2、RTC API)
-
- 1)初始化与基础控制
- [2)时间 / 日期 设置与读取(最常用)](#2)时间 / 日期 设置与读取(最常用))
- 3)闹钟(Alarm)相关
- [4)秒中断(Second Interrupt)](#4)秒中断(Second Interrupt))
- 5)唤醒定时器(Wakeup,低功耗常用)
- [6)后备寄存器(断电保存标志 / 少量数据)](#6)后备寄存器(断电保存标志 / 少量数据))
- 7)中断通用处理
- [3、常用宏 / Format 取值](#3、常用宏 / Format 取值)
- [五、RTC 应用实例](#五、RTC 应用实例)
-
- 1、读取并打印实时时间
-
- 1)开启外部晶振
- [2)配置 RTC](#2)配置 RTC)
- 3)配置串口(用于打印时间)
- 4)工程生成
- 5)代码
- 6)执行结果
- [2、RTC 闹钟定时中断](#2、RTC 闹钟定时中断)
- 3、BKP备份寄存器保存掉电参数
-
- 1)开启外部晶振
- [2)配置 RTC](#2)配置 RTC)
- 3)配置串口(用于打印时间)
- 4)工程生成
- 5)代码实例
- 4、RTC唤醒低功耗模式
-
- 1)开启外部晶振
- [2)配置 RTC](#2)配置 RTC)
- 3)配置串口(用于打印时间)
- 4)工程生成
- 5)代码实例
- [六、RTC 核心要点与避坑大全](#六、RTC 核心要点与避坑大全)
- 七、全篇总结
一、RTC 简介
1、什么是 RTC
RTC(Real Time Clock,实时时钟)是 STM32 内置的独立硬件时钟外设,专门用于记录年月日时分秒、星期、闹钟时间,是设备计时、日志时间戳、定时唤醒的核心外设。
RTC 拥有独立时钟源、独立供电域,主电源断电后可依靠备用电池继续走时,实现掉电时间不重置。
2、RTC 核心特点
独立工作:不依赖系统主时钟,主程序运行、停机、休眠均可正常计时。
掉电续航:外接纽扣电池(VBAT),断电继续走时。
超低功耗:32.768K低频晶振,功耗极低。
自带日历:自动适配年月日、星期、闰年。
支持闹钟中断:可定时唤醒单片机、触发任务。
配合低功耗:是 Stop/Standby 模式最常用唤醒源。
3、RTC 三种时钟源
LSE 外部32.768K晶振(推荐):精度最高、稳定,工程标配。
LSI 内部40KRC振荡器:无需外部晶振,精度差、会走时不准。
HSE 分频时钟:极少使用,功耗高、不适合计时。
4、RTC 核心功能
万年历计时:年、月、日、时、分、秒、星期。
RTC 闹钟中断:定时触发任务、定时开机。
周期性秒中断:精准1s节拍。
低功耗唤醒:休眠模式定时唤醒设备。
BKP 备份寄存器:掉电保存用户参数。
二、RTC 模块详解
0、RTC模块框图

1、后备区域(灰色部分)
由 VBAT 电池供电,待机、主电源掉电时也持续工作,是 RTC 计时的核心。
1)时钟输入与预分频器
输入信号:RTCCLK(RTC 时钟源,通常是 32.768kHz LSE 晶振)。
预分频器:RTC_DIV,分频计数器,对 RTCCLK 进行分频,输出 TR_CLK(驱动 32 位计数器的时钟)。
重装载寄存器:RTC_PRL,重装载寄存器,配置分频系数,决定最终计时精度。
2)32 位可编程计数器(RTC计时核心)
32 位计数器:
RTC_CNT,对 TR_CLK 脉冲进行计数,本质是一个 "自增寄存器",每来一个 TR_CLK 脉冲就 + 1。
存储时间戳:它存储的不是直观的"年月日",而是从起始点(如 1970-01-01 或自定义时间)开始累计的 总秒数(Unix 时间戳)。
闹钟比较寄存器:
RTC_ALR,硬件实时比较器,不断对比 RTC_CNT 的当前值和预设的闹钟值。
当两者相等时,立刻输出 RTC_Alarm 信号,无需 CPU 干预。
3)退出待机模式(WKP_STDBY)
RTC 闹钟不仅能触发 CPU 中断,还能直接唤醒低功耗模式:
输入信号:
RTC_Alarm、WKUP pin(外部唤醒引脚)。
逻辑功能:
两个信号通过或门后,直接触发 "退出待机模式" 电路,在主 CPU 完全休眠时,也能靠 RTC 闹钟或外部引脚唤醒芯片,实现低功耗定时控制。
2、主域部分
由主电源 VDD 供电,待机 / 掉电时不工作,仅在主系统运行时提供配置、中断、总线交互功能。
1)RTC_CR 控制与中断逻辑模块
输入信号:
RTC_Second(秒脉冲)、RTC_Overflow(计数器溢出)、RTC_Alarm(闹钟匹配)
内部逻辑:
SECF/SECIE:秒中断标志 / 使能,RTC_Second 到来时置位标志,开启中断后触发 CPU 中断。
OWF/OWIE:溢出中断标志 / 使能,计数器溢出时触发,用于处理日历进位。
ALRF/ALRIE:闹钟中断标志 / 使能,RTC_Alarm 信号到来时置位,触发闹钟中断。
输出信号:
所有中断信号通过或门汇总,发送到 NVIC中断控制器,最终通知 CPU 处理。
2)APB1 接口模块
CPU 与 RTC 后备域交互的桥梁:
输入:
来自 APB1总线 的配置指令、读写操作。
功能:
接收 CPU 通过 APB1 总线发来的配置,写入 RTC_PRL、RTC_CNT、RTC_ALR 等后备域寄存器。
将 RTC 的状态数据(当前时间、闹钟状态)反馈给 CPU。
注意:
该模块待机时不供电,因此待机时 CPU 无法直接读写 RTC 寄存器,仅后备域硬件在运行。
3、工作流程
1)计时流程:
RTCCLK → RTC预分频器 → TR_CLK → RTC_CNT 计数 → RTC_Second 秒脉冲 → 驱动日历进位。
2)闹钟触发流程:
RTC_CNT 与 RTC_ALR 匹配 → RTC_Alarm 信号 → 分两路:
一路到 RTC_CR 模块,置位 ALRF 标志,触发 CPU 中断。
一路到 WKP_STDBY 逻辑,直接唤醒待机模式的芯片。
3)CPU 交互流程:
CPU 通过 APB1总线 → APB1接口 → 读写后备域寄存器,配置时间、闹钟、分频参数。
4)计时原理
标准 LSE 32768Hz 时钟,经过分频后得到精准 1Hz 秒脉冲,每1秒计数器+1,硬件自动累加时间、更新日历信息,无需软件累加。
分频公式:RTC秒中断频率 = LSE时钟 / (PRL+1)
三、CubeMX 配置 RTC
1、基础配置流程
1)打开 CubeMX → 点击 Timers → RTC。
2)Activate Clock Source:勾选 Activate LSE Clock。
3)Activate Calendar:开启日历功能。

4)RCC开启低速时钟RCC→ Low Speed Clock(LSE):Crystal/Ceramic Resonator。

5)时钟源选择:LSE 32.768K。

6)Tamper 入侵检测配置
可配置 Tamper引脚,设置触发边沿(上升 / 下降),用于防拆检测。
一般项目无需配置,仅防盗 / 加密设备使用。

2、参数配置
General:
Auto Predivider Calculation:Enabled(使用自动预分频器计算)
Asynchronous Predivider value:Automatic Predivider Calculation Enabled(异步预分频系数:使用自动)
Output:输出引脚输出选项。

3、中断配置
1)NVIC 设置:开启 RTC 全局中断。

四、RTC编程模块
1、RTC 结构体
1) RTC 句柄(RTC_HandleTypeDef)
typedef struct {
RTC_TypeDef *Instance; // 寄存器基地址(RTC)
RTC_InitTypeDef Init; // 初始化参数
HAL_LockTypeDef Lock; // 锁
__IO HAL_RTC_StateTypeDef State; // 运行状态
} RTC_HandleTypeDef;
2)初始化参数(RTC_InitTypeDef)
typedef struct {
uint32_t HourFormat; // RTC_HOURFORMAT_24 / 12
uint32_t AsynchPrediv; // F103 固定 127
uint32_t SynchPrediv; // F103 固定 255
uint32_t OutPut; // 输出引脚配置
uint32_t OutPutPolarity;
uint32_t OutPutType;
} RTC_InitTypeDef;
3)时间结构体(RTC_TimeTypeDef)
typedef struct {
uint8_t Hours; // 0-23
uint8_t Minutes; // 0-59
uint8_t Seconds; // 0-59
} RTC_TimeTypeDef;
4)闹钟结构体(RTC_AlarmTypeDef)
typedef struct {
RTC_TimeTypeDef AlarmTime; // 闹钟时:分:秒
uint32_t AlarmMask; // 掩码(忽略某些字段)
uint32_t AlarmDateWeekDaySel; // 选日期或星期
uint8_t AlarmDateWeekDay; // 日期/星期值
uint32_t Alarm; // 闹钟开关
} RTC_AlarmTypeDef;
2、RTC API
1)初始化与基础控制
// 初始化 RTC
HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);
// 反初始化
HAL_StatusTypeDef HAL_RTC_DeInit(RTC_HandleTypeDef *hrtc);
// MSP 初始化(底层:开 LSE、开电源备份域)
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc);
void HAL_RTC_MspDeInit(RTC_HandleTypeDef *hrtc);
2)时间 / 日期 设置与读取(最常用)
// 设置时间
HAL_StatusTypeDef HAL_RTC_SetTime(
RTC_HandleTypeDef *hrtc,
RTC_TimeTypeDef *sTime,
uint32_t Format); // RTC_FORMAT_BIN / BCD
// 读取时间
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);
F103 铁律:先 GetTime,再 GetDate。
3)闹钟(Alarm)相关
// 设置闹钟(查询方式)
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,
RTC_AlarmTypeDef *sAlarm,
uint32_t Format);
// 设置闹钟并开启中断(常用)
HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc,
RTC_AlarmTypeDef *sAlarm,
uint32_t Format);
// 关闭闹钟
HAL_StatusTypeDef HAL_RTC_DeactivateAlarm(RTC_HandleTypeDef *hrtc);
// 闹钟中断回调
__weak void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc);
4)秒中断(Second Interrupt)
// 开启秒中断
HAL_StatusTypeDef HAL_RTC_SetSecond_IT(RTC_HandleTypeDef *hrtc);
// 关闭秒中断
HAL_StatusTypeDef HAL_RTC_DisableSecond_IT(RTC_HandleTypeDef *hrtc);
// 秒中断回调(F1 HAL 统一事件回调)
__weak void HAL_RTC_RtcEventCallback(RTC_HandleTypeDef *hrtc);
5)唤醒定时器(Wakeup,低功耗常用)
// 设置唤醒定时器
HAL_StatusTypeDef HAL_RTC_SetWakeupTimer(RTC_HandleTypeDef *hrtc,
uint32_t WakeupCounter,
uint32_t WakeupClock);
// 开启唤醒中断
HAL_StatusTypeDef HAL_RTC_SetWakeupTimer_IT(RTC_HandleTypeDef *hrtc,
uint32_t WakeupCounter,
uint32_t WakeupClock);
// 唤醒回调
__weak void HAL_RTC_WakeupEventCallback(RTC_HandleTypeDef *hrtc);
6)后备寄存器(断电保存标志 / 少量数据)
// 读后备寄存器(1~10)
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BKP_DR);
// 写后备寄存器
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc,
uint32_t BKP_DR,
uint32_t Data);
典型用法:第一次上电写 0x55AA,以后上电读,判断是否需要重设时间。
7)中断通用处理
// RTC 中断服务函数(放在 stm32f1xx_it.c)
void RTC_IRQHandler(void);
// 中断使能(NVIC)
HAL_NVIC_EnableIRQ(RTC_IRQn);
HAL_NVIC_SetPriority(RTC_IRQn, 0, 0);
3、常用宏 / Format 取值
// 格式
#define RTC_FORMAT_BIN 0x00000000U
#define RTC_FORMAT_BCD 0x00000001U
// 小时制
#define RTC_HOURFORMAT_24 0x00000000U
#define RTC_HOURFORMAT_12 0x00000040U
// 中断标志
#define RTC_IT_SEC 0x00000001U // 秒中断
#define RTC_IT_ALRA 0x00000002U // 闹钟A
#define RTC_IT_WUT 0x00000004U // 唤醒
五、RTC 应用实例
1、读取并打印实时时间
1)开启外部晶振
RCC → HSE 选择 Crystal/Ceramic Resonator。
RCC → LSE 选择 Crystal/Ceramic Resonator(RTC 必须用 32.768KHz 低速晶振)。
2)配置 RTC
RTC → 勾选 Active Clock Source。
RTC Out 不勾选。
RTC 参数默认即可(CubeMX 会自动配置分频)。
3)配置串口(用于打印时间)
USART1 → 模式选择 Asynchronous(异步)。。
波特率:115200,8 位数据,无校验,1 位停止位
4)工程生成
项目名(HAL_PrintTime)、路径自行设置。
栈大小建议设置为:0x400。
生成代码后用 Keil 打开。
5)代码
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"
/* 重定向printf */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/* RTC 结构体 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
/* 1. 初始化 RTC 时间:2026年6月6日 星期三 12:00:00 */
sTime.Hours = 12; // 时
sTime.Minutes = 00; // 分
sTime.Seconds = 00; // 秒
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
sDate.Date = 6; // 日
sDate.Month = RTC_MONTH_JUNE; // 月
sDate.Year = 26; // 年(只写后两位)
sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY; // 星期
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
while (1)
{
/* 读取实时时间 */
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
/* 打印 */
printf("20%02d-%02d-%02d %02d:%02d:%02d\r\n",
sDate.Year, // 年
sDate.Month, // 月
sDate.Date, // 日
sTime.Hours, // 时
sTime.Minutes, // 分
sTime.Seconds); // 秒
HAL_Delay(1000);
}
}
6)执行结果
串口助手(115200 波特率)会输出:
2026-06-06 12:00:00
2026-06-06 12:00:01
2026-06-06 12:00:02
2、RTC 闹钟定时中断
1)项目功能
RTC 实时时钟走时。
闹钟定时中断(12:00:10 触发)。
中断触发:翻转 LED + 串口打印。
串口:USART1(PA9/PA10)。
LED:PB5。
中断:RTC 闹钟中断。
2)实例代码(无CubeMX)
#include "stm32f10x.h"
#include "stm32f10x_hal.h"
// 句柄定义
RTC_HandleTypeDef hrtc;
UART_HandleTypeDef huart1;
// 时间结构体
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
RTC_AlarmTypeDef sAlarm;
// 重定向 printf
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}
// 系统时钟初始化(72MHz)
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 启用 HSE + LSE
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 时钟配置
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
// 使能 RTC 时钟
__HAL_RCC_RTC_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess(); // 解锁备份域
}
// USART1 初始化
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
// 手动配置 PA9/PA10
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// PA9 TX
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// PA10 RX
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// RTC 初始化(纯手写)
void MX_RTC_Init(void)
{
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
HAL_RTC_Init(&hrtc);
// 设置初始时间:12:00:00
sTime.Hours = 12;
sTime.Minutes = 00;
sTime.Seconds = 00;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
// 设置日期
sDate.Date = 1;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Year = 26;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
// LED 初始化 PB5
void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
}
// RTC 闹钟中断配置
void RTC_SetAlarm_IT(void)
{
// 闹钟时间:12:00:10
sAlarm.Alarm = RTC_ALARM_A;
sAlarm.AlarmTime.Hours = 12;
sAlarm.AlarmTime.Minutes = 00;
sAlarm.AlarmTime.Seconds = 10;
sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
// 开启中断
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
// 闹钟中断服务函数
void RTC_Alarm_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler(&hrtc);
}
// 闹钟中断回调函数(核心)
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
printf("\r\n===== 闹钟中断触发成功!=====\r\n");
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转LED
}
int main(void)
{
// 初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_RTC_Init();
RTC_SetAlarm_IT();
printf("RTC 闹钟中断系统启动成功\r\n");
printf("闹钟时间:12:00:10\r\n\r\n");
while (1)
{
// 循环读取并打印时间
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
printf("当前时间:20%02d-%02d-%02d %02d:%02d:%02d\r\n",
sDate.Year, sDate.Month, sDate.Date,
sTime.Hours, sTime.Minutes, sTime.Seconds);
HAL_Delay(1000);
}
}
3、BKP备份寄存器保存掉电参数
1)开启外部晶振
RCC → HSE 选择 Crystal/Ceramic Resonator。
RCC → LSE 选择 Crystal/Ceramic Resonator(RTC 必须用 32.768KHz 低速晶振)。
2)配置 RTC
RTC → 勾选 Active Clock Source。
RTC Out 不勾选。
RTC 参数默认即可(CubeMX 会自动配置分频)。
3)配置串口(用于打印时间)
USART1 → 模式选择 Asynchronous(异步)。。
波特率:115200,8 位数据,无校验,1 位停止位
4)工程生成
项目名(HAL_BKP)、路径自行设置。
栈大小建议设置为:0x400。
生成代码后用 Keil 打开。
5)代码实例
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"
/* 重定向printf */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/* 要掉电保存的参数结构体 */
typedef struct {
uint16_t device_id; // 设备ID
uint16_t alarm_hour; // 闹钟小时
uint16_t alarm_min; // 闹钟分钟
uint16_t power_cnt; // 上电次数
uint16_t save_flag; // 保存标志 0xA5A5 = 已初始化
} Backup_Config;
Backup_Config bkp_cfg;
// 从BKP读取参数
void BKP_ReadConfig(Backup_Config *cfg)
{
cfg->device_id = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
cfg->alarm_hour = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
cfg->alarm_min = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
cfg->power_cnt = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
cfg->save_flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
}
// 保存参数到BKP
void BKP_SaveConfig(Backup_Config *cfg)
{
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, cfg->device_id);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, cfg->alarm_hour);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, cfg->alarm_min);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, cfg->power_cnt);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, cfg->save_flag);
}
//上电初始化 BKP 配置
void BKP_Init(void)
{
BKP_ReadConfig(&bkp_cfg);
// 判断是否第一次上电
if(bkp_cfg.save_flag != 0xA5A5)
{
printf("第一次上电,加载默认参数!\r\n");
bkp_cfg.device_id = 1001;
bkp_cfg.alarm_hour = 8;
bkp_cfg.alarm_min = 30;
bkp_cfg.power_cnt = 1;
bkp_cfg.save_flag = 0xA5A5;
BKP_SaveConfig(&bkp_cfg);
}
else
{
printf("从BKP恢复掉电参数!\r\n");
bkp_cfg.power_cnt++; // 上电次数+1
BKP_SaveConfig(&bkp_cfg);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
// ==================== BKP 初始化 ====================
BKP_Init();
// 打印参数
printf("=====================================\r\n");
printf("设备ID : %d\r\n", bkp_cfg.device_id);
printf("闹钟时间 : %02d:%02d\r\n", bkp_cfg.alarm_hour, bkp_cfg.alarm_min);
printf("上电次数 : %d\r\n", bkp_cfg.power_cnt);
printf("=====================================\r\n");
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
while (1)
{
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
printf("时间:20%02d-%02d-%02d %02d:%02d:%02d\r\n",
sDate.Year, sDate.Month, sDate.Date,
sTime.Hours, sTime.Minutes, sTime.Seconds);
HAL_Delay(1000);
}
}
4、RTC唤醒低功耗模式
1)开启外部晶振
RCC → HSE 选择 Crystal/Ceramic Resonator。
RCC → LSE 选择 Crystal/Ceramic Resonator(RTC 必须用 32.768KHz 低速晶振)。
2)配置 RTC
RTC → 勾选 Activate Clock Source。
RTC → 勾选 Activate Calendar。
RTC Out 不勾选。
NVIC Settings 中,勾选 RTC global interrupt 和 RTC Alarm through EXTI line interrupt
RTC 参数默认即可(CubeMX 会自动配置分频)。
3)配置串口(用于打印时间)
USART1 → 模式选择 Asynchronous(异步)。。
波特率:115200,8 位数据,无校验,1 位停止位
4)工程生成
项目名(HAL_RTC_STOP)、路径自行设置。
栈大小建议设置为:0x400。
生成代码后用 Keil 打开。
5)代码实例
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"
/* 重定向printf */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
//全局时间日期变量
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
RTC_AlarmTypeDef sAlarm = {0};
//唤醒后重新初始化系统时钟
void SystemClock_ReConfig(void)
{
SystemClock_Config();
}
//进入 STOP 低功耗模式函数
void Enter_STOP_Mode(void)
{
// 进入低功耗前,关闭串口、SPI等外设,降低功耗
HAL_SuspendTick(); // 暂停系统滴答定时器(防止唤醒)
// 进入 STOP 模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// ========== 唤醒后执行以下代码 ==========
SystemClock_ReConfig(); // 重新配置时钟
HAL_ResumeTick(); // 恢复滴答定时器
}
//闹钟中断回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
/* 清除闹钟标志 */
__HAL_RTC_ALARM_CLEAR_FLAG(hrtc, RTC_FLAG_ALRAF);
}
// 获取当前时间函数
void Get_RTC_Time(void) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
printf("Time: %02d:%02d:%02d\n",
sTime.Hours, sTime.Minutes, sTime.Seconds);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
/* 设置闹钟 */
sAlarm.AlarmTime.Hours = 0; // 0点
sAlarm.AlarmTime.Minutes = 0; // 0分
sAlarm.AlarmTime.Seconds = 10; // 10秒
sAlarm.Alarm = RTC_ALARM_A;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD) != HAL_OK) {
Error_Handler();
}
//获取当前时间
Get_RTC_Time();
printf("即将进入低功耗模式...\r\n");
HAL_Delay(500);
//进入 STOP 低功耗,等待 RTC 唤醒
Enter_STOP_Mode();
while (1)
{
Get_RTC_Time();
HAL_Delay(1000);
}
}
六、RTC 核心要点与避坑大全
1、必考核心知识点
RTC 是独立时钟外设,主电源断电可依靠VBAT电池继续计时。
工程必须使用 LSE 32.768K 晶振保证精度。
RTC 可生成秒中断、闹钟中断,是低功耗唤醒核心。
BKP备份寄存器掉电保存少量参数,无需外部存储。
RTC时间只需初始化一次,重复初始化会重置时间。
2、高频坑点
未焊接32.768K晶振,LSE无法启动,RTC不走时。
未接VBAT备用电池,断电时间清零。
每次开机都初始化时间,导致时间永远固定。
中断回调内使用延时、阻塞函数,导致计时异常。
LSI内部时钟精度差,长时间运行时间严重偏差。
3、工程万能应用场景
智能设备实时时间显示、日志时间戳。
定时开关机、定时采集数据。
低功耗设备定时唤醒工作。
设备参数掉电保存(BKP)。
4、一句话总结
RTC是STM32独立实时时钟外设,依靠32.768K外部晶振精准计时,支持掉电续航、日历计时、闹钟中断、低功耗唤醒,搭配BKP备份寄存器可实现少量参数掉电保存,是智能设备计时与低功耗开发的核心外设。
七、全篇总结
RTC 是嵌入式产品计时与低功耗开发的必备外设,结构简单、稳定性高、功耗极低。
掌握RTC时间读写、闹钟中断、BKP备份、低功耗唤醒,即可满足所有智能仪表、智能家居、低功耗设备、毕设项目的时间功能开发需求。