19-BKP备份寄存器与RTC实时时钟.md 15 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(扩展篇)V1.0.0 — BKP/RTC章节 + 配套代码21~26" author: "尚硅谷研究院" date: 2026-07-15

created: 2026-07-15

BKP备份寄存器与RTC实时时钟

用生活理解:BKP 就像一块随身带的防水笔记本——即使主人在游泳池里(主电源断电),笔记本也不会泡坏(VBAT 电池供电)。RTC 就像手表上的秒针——一直在走(32.768KHz 晶振驱动),永远不会停。两个配合使用,可以做到断电后时间不丢失、数据不丢失。


备份域(Backup Domain)

备份域是 STM32 中一个独立的供电区域,由 VBAT 引脚供电。

VDD(3.3V) ────┐
               ├──→ 选择器 ──→ 备份域
VBAT(电池) ────┘
  • VDD 正常时:备份域由 VDD 供电
  • VDD 断电时:备份域自动切换到 VBAT(外接电池)
  • LSE 晶振(32.768KHz)也属于备份域——RTC 靠它走时

参考:参考手册 §6(BKP 寄存器)、§17(RTC 寄存器) 参考:原理图 VBAT 电路

备份域包含的内容

模块 说明
BKP 寄存器 42 个 16 位寄存器(共 84 字节)
RTC 实时时钟(日历/闹钟)
LSE 振荡器 32.768KHz 晶振
PC13~PC15 备份域 I/O(由 BKP 控制)

BKP 备份寄存器

BKP(Backup)寄存器共 42 个 16 位寄存器(总容量 84 字节),可在系统复位/待机/主电源断电时保持内容。

数据存储(写入 BKP 寄存器)

文件:stm32/21_bkp_test_register/Hardware/BKP/bkp.h

#ifndef __BKP_H
#define __BKP_H

#include "stm32f10x.h"

// 初始化
void BKP_Init(void);

#endif

文件:stm32/21_bkp_test_register/Hardware/BKP/bkp.c

#include "bkp.h"

// 初始化
void BKP_Init(void)
{
    // 1. 开启PWR时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    // 2. 解锁备份域写保护
    PWR->CR |= PWR_CR_DBP;

    // 3. 开启BKP时钟
    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;

    // // 4. 复位备份域寄存器
    // RCC->BDCR |= RCC_BDCR_BDRST;

    // // 5. 结束备份域复位
    // RCC->BDCR &= ~RCC_BDCR_BDRST;
}

文件:stm32/21_bkp_test_register/User/main.c

#include "usart.h"
#include "delay.h"
#include "key.h"
#include "bkp.h"

int main(void)
{
	// 初始化
	USART_Init();
	KEY_Init();
	BKP_Init();

	printf("尚硅谷备份寄存器实验...\n");

	// 向备份数据寄存器写入数据值
	// BKP->DR1 = 9999;

	while (1)
	{
	}
}

防侵入功能(TAMPER)

BKP 支持防侵入检测——当 TAMPER 引脚被触发时,自动清除所有 BKP 寄存器内容(防止篡改)。

// 配置 TAMPER 引脚(PC13)为防侵入模式
BKP->CR |= BKP_CR_TPE;        // TPE=1: 使能防侵入
// 当 PC13 检测到上升沿时 → 自动清除 BKP 寄存器 → 产生中断

RTC 实时时钟

RTC(Real-Time Clock)提供精确的日历/时钟功能,具有闹钟中断、秒中断等功能。

RTC 功能框图

时钟源选择 → RTC 预分频器 → 32位计数器 → 比较器 → 闹钟中断
                ↓                              ↓
              1Hz 基准时钟                  秒中断

RTC 时钟源选择

RTC 有三个时钟源可选:

时钟源 频率 精度 特点
LSE 32.768KHz 推荐,需外部晶振
LSI ~40KHz 差(±10%) 不需要外部元件
HSE 8MHz(/128=62.5KHz) 需要外部晶振

推荐 LSE:32.768KHz 经过 32768 分频可精确获得 1Hz,最适合 RTC。

RTC 预分频器

RTC 预分频器由 20 位组成(7 位异步 + 13 位同步),STM32F1 的 RTC 只使用一个 16 位 PRL 寄存器(低 13 位有效)。

// LSE = 32.768KHz 时,PRL = 32767 → 1Hz
// 配置流程(封装在 RTC_Init 中):
RTC->PRLH = 0;
RTC->PRLL = 0x7fff;        // 32767

RTC 寄存器

寄存器 说明
RTC_CRH 控制寄存器高(OWIE 溢出中断、ALRIE 闹钟中断、SECIE 秒中断)
RTC_CRL 控制寄存器低(CNF 配置模式、RSF 寄存器同步、SECF/RTOFF)
RTC_PRL 预分频装载寄存器(RTC_PRLL 低 16 位)
RTC_DIV 余数寄存器(当前分频计数值)
RTC_CNT 32 位计数器(秒数)
RTC_ALR 闹钟寄存器(与 CNT 匹配时触发闹钟中断)

注意:RTC 寄存器通过 APB1 接口访问,但 APB1 频率可能低于 RTC 频率,因此读取前需等待 RSF 标志同步。

RTC 初始化与配置

文件:stm32/25_rtc_calendar_register/Hardware/RTC/rtc.h

#ifndef __RTC_H
#define __RTC_H

#include "stm32f10x.h"
#include <time.h>

// 自定义日期时间结构体类型
typedef struct
{
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
} DateTime;

// 初始化
void RTC_Init(void);

// 设置闹钟(s秒之后报警)
void RTC_SetAlarm(uint32_t s);

// 设置当前时间(UNIX时间戳)
void RTC_SetTimestamp(uint32_t ts);

// 获取日期时间(年月日时分秒)
void RTC_GetDateTime(DateTime * dateTime);

#endif

文件:stm32/25_rtc_calendar_register/Hardware/RTC/rtc.c(RTC 初始化 + 日历功能)

#include "rtc.h"

// 初始化
void RTC_Init(void)
{
    // 1. 开启PWR时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    // 1.2 解锁备份域写保护
    PWR->CR |= PWR_CR_DBP;

    // // 1.3 复位备份域寄存器
    // RCC->BDCR |= RCC_BDCR_BDRST;
    // // 1.4 结束备份域复位
    // RCC->BDCR &= ~RCC_BDCR_BDRST;

    // 2. 开启RTC时钟源以及使能RTC
    // 2.1 使能RTC时钟
    RCC->BDCR |= RCC_BDCR_RTCEN;

    // 2.2 使能LSE并等待其就绪
    RCC->BDCR |= RCC_BDCR_LSEON;
    while (!(RCC->BDCR & RCC_BDCR_LSERDY))
    {
    }

    // 2.3 选择LSE作为RTC的时钟源
    RCC->BDCR &= ~RCC_BDCR_RTCSEL;
    RCC->BDCR |= RCC_BDCR_RTCSEL_0;

    // 3. RTC寄存器配置操作
    // 3.1 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {
    }

    // 3.2 进入配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 3.3 设置预分频系数 32767,得到1秒计数
    RTC->PRLH = 0;
    RTC->PRLL = 0x7fff;

    // 3.4 退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;

    // 3.5 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {
    }
}

// 设置闹钟(s秒之后报警)
void RTC_SetAlarm(uint32_t s)
{
    // 0. 清除闹钟标志
    RTC->CRL &= ~RTC_CRL_ALRF;

    // 1. 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {
    }

    // 2. 进入配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 3. 设置寄存器
    // 3.1 设置计数器 CNT = 0
    RTC->CNTH = 0;
    RTC->CNTL = 0;

    // 3.2 设置 ALR = s - 1
    s -= 1;
    RTC->ALRH = (s >> 16) & 0xffff;
    RTC->ALRL = (s >> 0) & 0xffff;

    // 4. 退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;

    // 5. 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {
    }
}

// 设置当前时间(UNIX时间戳)
void RTC_SetTimestamp(uint32_t ts)
{
    // 1. 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {
    }

    // 2. 进入配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 3. 设置CNT寄存器
    RTC->CNTH = (ts >> 16) & 0xffff;
    RTC->CNTL = (ts >> 0) & 0xffff;

    // 4. 退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;

    // 5. 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {
    }
}

// 获取日期时间(年月日时分秒)
void RTC_GetDateTime(DateTime *dateTime)
{
    // 1. 等待寄存器同步
    while ( !(RTC->CRL & RTC_CRL_RSF) )
    {}

    // 2. 获取当前秒数并组合
    uint32_t second = RTC->CNTH << 16 | RTC->CNTL;

    // 3. 将时间戳转换为tm结构体变量
    struct tm* ptm = localtime(&second);

    // 4. 将tm数据赋给自定义的结构体变量
    dateTime->year = ptm->tm_year + 1900;
    dateTime->month = ptm->tm_mon + 1;
    dateTime->day = ptm->tm_mday;
    dateTime->hour = ptm->tm_hour;
    dateTime->minute = ptm->tm_min;
    dateTime->second = ptm->tm_sec;
}

实验:BKP 数据存储 + RTC 日历

BKP 实验

项目路径stm32/21_bkp_test_register

文件:stm32/21_bkp_test_register/User/main.c(向 BKP 寄存器写入数据)

#include "usart.h"
#include "delay.h"
#include "key.h"
#include "bkp.h"

int main(void)
{
	// 初始化
	USART_Init();
	KEY_Init();
	BKP_Init();

	printf("尚硅谷备份寄存器实验...\n");

	// 向备份数据寄存器写入数据值
	// BKP->DR1 = 9999;

	while (1)
	{
	}
}

RTC 日历实验

项目路径stm32/25_rtc_calendar_register

文件:stm32/25_rtc_calendar_register/User/main.c

#include "usart.h"
#include "delay.h"
#include "rtc.h"

int main(void)
{
	// 初始化
	USART_Init();
	RTC_Init();

	printf("尚硅谷RTC实验:RTC实时时钟...\n");

	// 设定一次当前的时间戳
	// RTC_SetTimestamp(1736160789);

	DateTime dateTime;

	while (1)
	{
		// 每隔1s获取当前时间并打印一次
		RTC_GetDateTime(&dateTime);

		printf("%04d年%02d月%02d日 %02d:%02d:%02d\n",
			dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second);

		Delay_ms(1000);
	}
}

RTC 闹钟唤醒待机实验

项目路径stm32/23_rtc_alarm_standby_register

文件:stm32/23_rtc_alarm_standby_register/Hardware/RTC/rtc.c(闹钟功能)

#include "rtc.h"

// 初始化
void RTC_Init(void)
{
    // 1. 开启PWR时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    // 1.2 解锁备份域写保护
    PWR->CR |= PWR_CR_DBP;

    // 1.3 复位备份域寄存器
    RCC->BDCR |= RCC_BDCR_BDRST;

    // 1.4 结束备份域复位
    RCC->BDCR &= ~RCC_BDCR_BDRST;

    // 2. 开启RTC时钟源以及使能RTC
    // 2.1 使能RTC时钟
    RCC->BDCR |= RCC_BDCR_RTCEN;

    // 2.2 使能LSE并等待其就绪
    RCC->BDCR |= RCC_BDCR_LSEON;
    while (!(RCC->BDCR & RCC_BDCR_LSERDY))
    {}

    // 2.3 选择LSE作为RTC的时钟源
    RCC->BDCR &= ~RCC_BDCR_RTCSEL;
    RCC->BDCR |= RCC_BDCR_RTCSEL_0;

    // 3. RTC寄存器配置操作
    // 3.1 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {}

    // 3.2 进入配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 3.3 设置预分频系数 32767,得到1秒计数
    RTC->PRLH = 0;
    RTC->PRLL = 0x7fff;

    // 3.4 退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;

    // 3.5 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {}
}

// 设置闹钟(s秒之后报警)
void RTC_SetAlarm(uint32_t s)
{
    // 0. 清除闹钟标志
    RTC->CRL &= ~RTC_CRL_ALRF;

    // 1. 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {}

    // 2. 进入配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 3. 设置寄存器
    // 3.1 设置计数器 CNT = 0
    RTC->CNTH = 0;
    RTC->CNTL = 0;

    // 3.2 设置 ALR = s - 1
    s -= 1;
    RTC->ALRH = (s >> 16) & 0xffff;
    RTC->ALRL = (s >> 0) & 0xffff;

    // 4. 退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;

    // 5. 查询RTOFF位直到为1
    while (!(RTC->CRL & RTC_CRL_RTOFF))
    {}
}

文件:stm32/23_rtc_alarm_standby_register/User/main.c(闹钟唤醒待机)

#include "usart.h"
#include "delay.h"
#include "led.h"
#include "rtc.h"

void enter_standby_mode(void);

int main(void)
{
	// 初始化
	USART_Init();
	LED_Init();
	RTC_Init();

	// 判断标志位来判断是否从待机唤醒
	if (PWR->CSR & PWR_CSR_SBF)
	{
		printf("从待机模式唤醒:\n");
		PWR->CR |= PWR_CR_CSBF;
	}
	if (PWR->CSR & PWR_CSR_WUF)
	{
		printf("产生了唤醒事件:\n");
		PWR->CR |= PWR_CR_CWUF;
	}

	printf("尚硅谷RTC实验:闹钟唤醒待机模式...\n");

	// 1. 点亮LED灯,延时2s,模拟正常运行程序
	LED_On(LED_2);
	Delay_s(2);

	while (1)
	{
		// 2. 进入待机模式
		printf("正常运行执行完毕,3s后进入待机模式...\n");
		Delay_s(3);
		printf("进入待机模式,5s后闹钟唤醒...\n");
		Delay_ms(1);	// 稍作延时确保串口信息成功发送

		// 3. 在进入待机模式前设置闹钟
		RTC_SetAlarm(5);
		enter_standby_mode();

		// 4. 以下代码不会执行
		printf("从待机模式唤醒...\n");
		Delay_s(2);
	}
}

// 进入待机模式的函数
void enter_standby_mode(void)
{
	// 1. 设置深度睡眠模式
	SCB->SCR |= SCB_SCR_SLEEPDEEP;

	// 2. 设置PDDS = 1,表示待机模式
	PWR->CR |= PWR_CR_PDDS;

	// 3. 使用WFI指令,进入待机模式
	__WFI();
}

HAL 库版 RTC/BKP

HAL 库使用 RTC_HandleTypeDef 管理 RTC,提供 SetTime/GetTime/SetAlarm 等高级 API。

// CubeMX 生成 RTC 配置:LSE 时钟源,启用日历
RTC_HandleTypeDef hrtc;

// HAL 库设置时间(使用 RTC_TimeTypeDef / RTC_DateTypeDef 结构体)
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};

sTime.Hours = 12;
sTime.Minutes = 0;
sTime.Seconds = 0;
sTime.TimeFormat = RTC_HOURFORMAT_24;  // 24 小时制
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);   // 设置时间

sDate.Year = 26;    // 2026 年
sDate.Month = 7;
sDate.Date = 15;
sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);   // 设置日期

// HAL 库读取时间
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);   // 读时间(必须先读时间再读日期!)
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);   // 读日期

printf("%04d-%02d-%02d %02d:%02d:%02d\r\n",
       2000 + sDate.Year, sDate.Month, sDate.Date,
       sTime.Hours, sTime.Minutes, sTime.Seconds);

// HAL 库读 BKP 寄存器
uint16_t data = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);

// HAL 库写 BKP 寄存器
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x55AA);

核心速查表

操作 寄存器版 HAL 库
解锁备份域 `PWR->CR = PWR_CR_DBP`
写 BKP 寄存器 BKP->DR[i] = data HAL_RTCEx_BKUPWrite()
读 BKP 寄存器 data = BKP->DR[i] HAL_RTCEx_BKUPRead()
设置时间 RTC_SetTimestamp(UNIX时间戳) HAL_RTC_SetTime() + SetDate()
读取时间 RTC_GetDateTime(&dt) → DateTime 结构体 HAL_RTC_GetTime() + GetDate()
配置 LSE RCC->BDCR 逐位操作 CubeMX 自动配置
写注意事项 需 CNF 模式 + 等待 RTOFF HAL 库自动处理

常见问题与避坑

  1. BKP 写入不成功 → 检查 PWR_CR_DBP 是否置 1(必须解锁备份域)
  2. RTC 不走时 → 检查 LSE 是否起振(LSERDY 标志)、RTCEN 是否使能
  3. RTC 读值不对 → 读取前等待 RSF 同步(APB1 比 RTC 慢时)
  4. VDD 断电 RTC 停走 → 检查 VBAT 引脚是否接电池或 VDD
  5. 写 RTC 后系统挂死 → RTC 写入需等待 RTOFF=1(未完成前不可再次写入)
  6. Unix 时间戳时区问题 → localtime() 使用系统时区,跨时区应用用 gmtime()