一、前言
1.RTC简介
RTC(Real Time Clock,实时时钟),是一个掉电 后仍然可以继续运行 的独立定时器。
RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期。RTC还包含用于管理低功耗模式的自动唤醒单元。
2.硬件架构
STM32 RTC 的核心架构分为 3 层,保证独立运行和低功耗:
- 备份域:包含 RTC 寄存器、备份寄存器(用户可存少量掉电不丢数据),由 VBAT 供电,主电源掉电后数据不丢失;
- 时钟模块:选择 LSE/LSI 作为时钟输入,经分频后驱动 RTC 计数核心;
- 中断 / 唤醒模块:闹钟、周期唤醒事件触发中断,可直接唤醒 CPU(停止 / 待机模式),无需 CPU 预运行。

2.1主电源与备用电源的区别
STM32 芯片的供电分为两个核心部分,两者分工明确:

VBAT 的唯一作用是「主电源掉电时给备份域(RTC + 备份寄存器)供电」;如果完全不需要这个 "掉电保持" 功能,VBAT 就失去了 "备用供电" 的意义,只需让它和主电源共用同一路电即可。

3 RTC功能框图


RTC实时时钟会产生两路信号,一个是为日历服务的秒信号,还有一路为闹钟服务,闹钟信号也来自于秒信号
4 RTC时钟源

RTC有3路时钟来源:HSE(8MHz)/128、LSE(32.768KHz)、LSI(40KHz)。其中,如果使用HSE或LSI的话,当主电源掉电的话,这两个始终都会受到影响,RTC就无法正常工作。所以,一般的通用做法是使用LSE 。2个原因,一是LSE不受主电源掉电的影响(在BKP中),二是它的频率是32768Hz,正好是2^15,分频容易实现。
1)APB1接口
用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作。APB1接口由APB1总线时钟驱动,用来与APB1总线连接。
通过APB1接口可以访问RTC的相关寄存器(预分频值、计数器值、闹钟值)。
2)RTC预分频模块
这个模块是RTC预分频模块,属于后备区域,VDD掉电后,可以在VBAT下继续运行。包含了一个20位的可编程分频器(RTC预分频器)。它可编程产生 1 秒的 RTC 时间基准 TR_CLK。
秒信号获取的方式: RTCCLK = 32768 Hz

3)32位可编程计数器
这个模块也属于后备区域,是一个32位的可编程计数器,可被初始化为当前的系统时间。一个32位的时钟计数器,

4)中断
从图中可以看到一共有3个中断:
- 秒中断:每计时1s产生一次中断。
- 计数器溢出中断。136年才会产生溢出,一般用不上。
RTC闹钟中断。RCT_CN和RTC_ALR会比较相等,如果相等表示闹钟时间到,会产生闹钟中断

二、代码
需求 : 测试STM32芯片进入低功耗 - 待机模式 RTC唤醒芯片
Dri_RTC.c:(1)初始化RTC函数(2)设置闹钟
cpp
/**
* 实时(Real Time)时钟 - 初始化
*/
void Dri_RTC_Init(void) {
// 1. 开启时钟
// ● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// ● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
PWR->CR |= PWR_CR_DBP;
// 2. 配置RTC
// 配置RTC时钟源
// Enable LSE
RCC->BDCR |= RCC_BDCR_LSEON;
// LSE is Ready
while ( ( RCC->BDCR & RCC_BDCR_LSERDY ) == 0 );
// ● 可以选择以下三种RTC的时钟源:
// ─ HSE时钟除以128;
// ─ LSE振荡器时钟;
// ─ LSI振荡器时钟
// RCC->BDCR &= ~RCC_BDCR_RTCSEL_1;
// RCC->BDCR |= RCC_BDCR_RTCSEL_0;
RCC->BDCR |= RCC_BDCR_RTCSEL_LSE; // 32.768K Hz
RCC->BDCR |= RCC_BDCR_RTCEN;
// 配置秒信号:RTC_PRL
// 配置过程:
// 1. 查询RTOFF位,直到RTOFF的值变为'1'
while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
// 2. 置CNF值为1,进入配置模式
RTC->CRL |= RTC_CRL_CNF;
// 3. 对一个或多个RTC寄存器进行写操作
// 32.768K Hz => PRL(32768) => 1HZ
// 16 bit
RTC->PRLL = 0x7FFF; // 0x7FFF + 1 = 0x8000 => 1 0000 0000 0000 000 = 65535 + 1
// 4 bit
RTC->PRLH = 0;
// 4. 清除CNF标志位,退出配置模式
RTC->CRL &= ~RTC_CRL_CNF;
// 5. 查询RTOFF,直至RTOFF位变为'1'以确认写操作已经完成。
while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
}
/**
* 实时(Real Time)时钟 - 设定闹钟
*/
void Dri_RTC_SetAlarm(uint32_t sec) {
// 配置闹钟信号
// 配置RTC_CNT
// 配置RTC_ALR
// 1. 查询RTOFF位,直到RTOFF的值变为'1'
while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
// 2. 置CNF值为1,进入配置模式
RTC->CRL |= RTC_CRL_CNF;
// 3. 对一个或多个RTC寄存器进行写操作
RTC->CNTL = 0;
RTC->CNTH = 0;
RTC->ALRL = sec; // Low 16 bit
RTC->ALRH = sec >> 16; // High 16 bit
// 4. 清除CNF标志位,退出配置模式
RTC->CRL &= ~RTC_CRL_CNF;
// 5. 查询RTOFF,直至RTOFF位变为'1'以确认写操作已经完成。
while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
}
main函数:待机模式常规代码以及rtc设置闹钟函数

cpp
#include "USART.h"
#include "SysTick.h"
#include "stm32f10x.h"
#include "LED.h"
#include <stdio.h>
#include "KEY.h"
#include "Dri_RTC.h"
/**
* 需求 : 测试STM32芯片进入低功耗 - 待机模式
*/
/**
* 待机模式
*/
void enter_standby_mode() {
// 深睡眠
SCB->SCR |= SCB_SCR_SLEEPDEEP;
// 掉电
PWR->CR |= PWR_CR_PDDS;
// 清除唤醒标志位
PWR->CR |= PWR_CR_CWUF;
// 使能唤醒引脚
//PWR->CSR |= PWR_CSR_EWUP;
__wfi();
}
/**
* 停止模式
*/
void enter_stop_mode() {
// 开启时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 深睡眠
SCB->SCR |= SCB_SCR_SLEEPDEEP;
// 掉电
PWR->CR &= ~PWR_CR_PDDS;
// 低功耗
PWR->CR |= PWR_CR_LPDS;
__wfi();
}
/**
* 睡眠模式
*/
void enter_sleep_mode() {
// 浅睡眠
SCB->SCR &= ~SCB_SCR_SLEEPDEEP;
SCB->SCR &= ~SCB_SCR_SLEEPONEXIT; // 立即进入睡眠
//SCB->SCR |= SCB_SCR_SLEEPONEXIT; // 所有中断程序处理完后再进入睡眠
__wfi();
}
void system_clock_reset(void) {
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
int main(void)
{
USART_Init();
Dri_RTC_Init();
KEY_Init();
LED_Init();
LED_On(LED_BLUE);
// 开启时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// !获取芯片待机状态,判断之前是否曾经进入过待机模式
if ( (PWR->CSR & PWR_CSR_SBF) != 0 ) {
printf("STM32芯片从待机模式被");
// 复位状态
PWR->CR |= PWR_CR_CSBF;
if ( (PWR->CSR & PWR_CSR_WUF) == 0 ) {
printf("Reset复位键唤醒 \n");
} else {
// 复位状态
PWR->CR |= PWR_CR_CWUF;
printf("RTC闹钟唤醒 \n");
}
}
printf("测试STM32芯片进入低功耗 - 待机模式 \n");
printf("5s后STM32芯片进入待机模式 \n");
SysTick_DelayS(2);
printf("3s后STM32芯片进入待机模式 \n");
SysTick_DelayS(1);
printf("2s后STM32芯片进入待机模式 \n");
SysTick_DelayS(1);
printf("1s后STM32芯片进入待机模式 \n");
SysTick_DelayS(1);
// !在进入待机模式前设定闹钟
Dri_RTC_SetAlarm(3);
// !进入待机模式
enter_standby_mode();
while(1)
{
}
}
三、寄存器
RTC_PRLH/RTC_PRLL 低位16:高位4:


RTC_CNTH/RTC_CNTL 高16位 低16位


RTC_ALRH/RTC_ALRL

四、HAL库
4.1配置RCC\SYS\USART1
4.2配置Timers-RTC-

4.3VSCODE
倒计时3s闹钟唤醒程序


五、拓展延伸
需求:设定当前时间
分析:需要设置当前时间,然后在获得当前时间。
cpp
/**
* 实时(Real Time)时钟 - 设定当前时间(秒)
*/
void Dri_RTC_SetTime(uint32_t sec) {
// 1. 查询RTOFF位,直到RTOFF的值变为'1'
while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
// 2. 置CNF值为1,进入配置模式
RTC->CRL |= RTC_CRL_CNF;
// 3. 对一个或多个RTC寄存器进行写操作
RTC->CNTL = sec;
RTC->CNTH = sec >> 16;
// 4. 清除CNF标志位,退出配置模式
RTC->CRL &= ~RTC_CRL_CNF;
// 5. 查询RTOFF,直至RTOFF位变为'1'以确认写操作已经完成。
while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
}
/**
* 实时(Real Time)时钟 - 设定时间(秒)
*/
uint32_t Dri_RTC_GetTime() {
return (RTC->CNTH << 16) | RTC->CNTL;
}
main.c:
(1)获取1900到现在有多少s。将 RTC 输出的 Unix 时间戳(秒级,从 1970-01-01 00:00:00 UTC 起的秒数)转换为「本地时区的年月日时分秒结构化数据

(2)sprintf函数进行拼接字符串, 获取系统的几个函数,放到while里面 每隔1s刷新获取最新的时间
先拆解代码功能
这段代码的核心是:将 RTC 输出的 Unix 时间戳(秒级,从 1970-01-01 00:00:00 UTC 起的秒数)转换为「本地时区的年月日时分秒结构化数据」 ,其中
localtime(&sec)是实现这一转换的核心函数。二、
localtime()函数详解1. 函数基本信息
- 所属库 :C 标准库
<time.h>(嵌入式系统中需确认编译器 / SDK 是否支持,如 STM32 的 ARMCC/ARMClang 均兼容);- 函数原型 :
struct tm *localtime(const time_t *timer);
- 入参:
const time_t *timer------ 指向 Unix 时间戳的指针(time_t本质是long/uint32_t类型,代表秒级时间戳);- 返回值:
struct tm *------ 指向结构化时间数据的指针(内存通常为静态分配,无需手动释放,但会被后续调用覆盖)。2. 核心作用
将「秒级时间戳」(纯数字)解析为人类可读的「本地时区日历时间」,并填充到
struct tm结构体中。
- "本地时区":函数会自动根据系统 / 编译器配置的时区偏移(如东八区 UTC+8),将 UTC 时间戳转换为本地时间;
- 结构化解析:把单调递增的秒数,拆解为年、月、日、时、分、秒、星期等独立字段。
cpp
int main(void)
{
Dri_RTC_Init();
Int_LCD_Init();
Int_LCD_ClearScreen(BLUE);
uint8_t date_string[20] = {0};
// 1. 如何获取到当前秒
Dri_RTC_SetTime(1765966710);
while(1)
{
// 2. 如何根据当前时间生成时间字符串
//uint8_t *date_string = "2025-01-17 09:58:00";
uint32_t sec = Dri_RTC_GetTime();
struct tm *rtc_date = localtime(&sec);
sprintf((char *)date_string, "%4d-%02d-%02d %02d:%02d:%02d",
rtc_date->tm_year + 1900,
rtc_date->tm_mon + 1,
rtc_date->tm_mday,
rtc_date->tm_hour,
rtc_date->tm_min,
rtc_date->tm_sec
);
Int_LCD_DisplayASCIIString(0,0,16,32,date_string,WHITE,BLUE);
SysTick_DelayS(1);
}
}
六、归纳总结
RTC和TIM对比

应用场景
- 时间戳记录:记录传感器采集数据的时间(如温湿度采集、故障报警时间);
- 定时唤醒:低功耗设备周期性唤醒(如每 10 分钟采集一次数据,其余时间休眠);
- 闹钟提醒:工业设备的定时任务(如定时开关机、定时上报数据);
- 掉电计时:主电源掉电后,备用电池维持 RTC 运行,恢复供电后可追溯掉电时长;
- 人机交互:显示屏显示实时日期 / 时间(如智能电表、手持终端)。