tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(扩展篇)V1.0.0 — BKP/RTC章节 + 配套代码21~26" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:BKP 就像一块随身带的防水笔记本——即使主人在游泳池里(主电源断电),笔记本也不会泡坏(VBAT 电池供电)。RTC 就像手表上的秒针——一直在走(32.768KHz 晶振驱动),永远不会停。两个配合使用,可以做到断电后时间不丢失、数据不丢失。
备份域是 STM32 中一个独立的供电区域,由 VBAT 引脚供电。
VDD(3.3V) ────┐
├──→ 选择器 ──→ 备份域
VBAT(电池) ────┘
参考:参考手册 §6(BKP 寄存器)、§17(RTC 寄存器) 参考:原理图 VBAT 电路
| 模块 | 说明 |
|---|---|
| BKP 寄存器 | 42 个 16 位寄存器(共 84 字节) |
| RTC | 实时时钟(日历/闹钟) |
| LSE 振荡器 | 32.768KHz 晶振 |
| PC13~PC15 | 备份域 I/O(由 BKP 控制) |
BKP(Backup)寄存器共 42 个 16 位寄存器(总容量 84 字节),可在系统复位/待机/主电源断电时保持内容。
文件: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)
{
}
}
BKP 支持防侵入检测——当 TAMPER 引脚被触发时,自动清除所有 BKP 寄存器内容(防止篡改)。
// 配置 TAMPER 引脚(PC13)为防侵入模式
BKP->CR |= BKP_CR_TPE; // TPE=1: 使能防侵入
// 当 PC13 检测到上升沿时 → 自动清除 BKP 寄存器 → 产生中断
RTC(Real-Time Clock)提供精确的日历/时钟功能,具有闹钟中断、秒中断等功能。
时钟源选择 → RTC 预分频器 → 32位计数器 → 比较器 → 闹钟中断
↓ ↓
1Hz 基准时钟 秒中断
RTC 有三个时钟源可选:
| 时钟源 | 频率 | 精度 | 特点 |
|---|---|---|---|
| LSE | 32.768KHz | 高 | 推荐,需外部晶振 |
| LSI | ~40KHz | 差(±10%) | 不需要外部元件 |
| HSE | 8MHz(/128=62.5KHz) | 中 | 需要外部晶振 |
推荐 LSE:32.768KHz 经过 32768 分频可精确获得 1Hz,最适合 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_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 标志同步。
文件: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;
}
项目路径: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)
{
}
}
项目路径: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);
}
}
项目路径: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_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 库自动处理 |