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 字节),可在系统复位/待机/主电源断电时保持内容。
// 写 BKP 前必须使能对备份域的写访问
// PWR_CR 的 DBP 位控制备份域的写保护
void BKP_Write(uint16_t reg_index, uint16_t data)
{
// 1. 使能对备份寄存器和 RTC 的访问
RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 开启 PWR 时钟
RCC->APB1ENR |= RCC_APB1ENR_BKPEN; // 开启 BKP 时钟
// 2. 解锁备份域写保护
PWR->CR |= PWR_CR_DBP; // DBP=1: 允许写备份域
// 3. 写入备份寄存器(BKP_DR1~BKP_DR42)
// BKP 寄存器地址偏移: 0x04 + (reg_index-1) * 4
BKP->DR[reg_index - 1] = data; // 写入
// 4. 重新加锁(可选的,安全考虑)
// PWR->CR &= ~PWR_CR_DBP;
}
uint16_t BKP_Read(uint16_t reg_index)
{
// 读取不需要 DBP 解锁
return BKP->DR[reg_index - 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 预分频器由两部分组成:
// 异步预分频(7 位,最大 128 分频)
// 同步预分频(15 位,最大 32768 分频)
// 总预分频 = (ASYNC_PREDIV + 1) × (SYNC_PREDIV + 1)
// LSE = 32.768KHz 时:
RTC->PRLH = 0x0000; // 异步预分频高(很少用)
RTC->PRLL = 127; // 异步 = 127+1 = 128
RTC->DIVH = 0x0000; // 同步预分频高(很少用)
RTC->DIVL = 255; // 同步 = 255+1 = 256
// 总 = 128 × 256 = 32768 → 1Hz 基准时钟
| 寄存器 | 说明 |
|---|---|
| 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 写入流程(RTC 寄存器属于备份域,写入需特殊时序)
void RTC_SetCounter(uint32_t count)
{
// 1. 电源和时钟使能
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
PWR->CR |= PWR_CR_DBP; // 允许写备份域
// 2. RTC 配置模式
RTC->CRL |= RTC_CRL_CNF; // CNF=1: 进入配置模式
// 3. 写入新值
RTC->CNTH = count >> 16; // 高 16 位
RTC->CNTL = count & 0xFFFF; // 低 16 位
// 4. 退出配置模式
RTC->CRL &= ~RTC_CRL_CNF; // CNF=0
// 5. 等待写入完成
while ((RTC->CRL & RTC_CRL_RTOFF) == 0) {} // RTOFF=1 才完成
}
uint32_t RTC_GetCounter(void)
{
// 读取 RTC 不必进配置模式,但需要等待同步
// 等待 RSF 标志
RTC->CRL |= RTC_CRL_RSF;
while ((RTC->CRL & RTC_CRL_RSF) == 0) {}
// 读取计数器
uint16_t high = RTC->CNTH;
uint16_t low = RTC->CNTL;
// 如果高 16 位在读低 16 位时改变了(进位),重新读
if (high != RTC->CNTH)
{
high = RTC->CNTH;
low = RTC->CNTL;
}
return ((uint32_t)high << 16) | low;
}
RTC 计数器存储的是从指定基准时间(如 1970-01-01)开始的秒数(Unix 时间戳)。
// Unix 时间戳 → 年月日时分秒(标准库 time.h 处理)
#include <time.h>
time_t raw_time = RTC_GetCounter(); // 读取 RTC 秒数
struct tm *info = localtime(&raw_time); // 转换为日期时间
printf("%04d-%02d-%02d %02d:%02d:%02d\r\n",
info->tm_year + 1900, // 年份
info->tm_mon + 1, // 月份 (0~11)
info->tm_mday, // 日
info->tm_hour, // 时
info->tm_min, // 分
info->tm_sec); // 秒
// 反向:年月日→Unix 时间戳
// 使用 mktime() 函数
项目路径:stm32/21_bkp_test_register ~ stm32/26_rtc_calendar_hal
// RTC + BKP 初始化
void RTC_BKP_Init(void)
{
// 1. 开启 PWR 和 BKP 时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
PWR->CR |= PWR_CR_DBP; // 解锁备份域
// 2. 如果 LSE 未启动,初始化 RTC
if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0)
{
// 复位备份域
RCC->BDCR |= RCC_BDCR_BDRST;
RCC->BDCR &= ~RCC_BDCR_BDRST;
// 配置 LSE 并等待就绪
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY));
// 选择 LSE 为 RTC 时钟源
RCC->BDCR |= RCC_BDCR_RTCSEL_0; // RTCSEL=01: LSE
// 配置 RTC 预分频器
RTC->CRL |= RTC_CRL_CNF; // 进入配置
RTC->PRLL = 32768 - 1; // 32.768KHz → 1Hz
RTC->CRL &= ~RTC_CRL_CNF; // 退出配置
while (!(RTC->CRL & RTC_CRL_RTOFF));
// 使能 RTC
RCC->BDCR |= RCC_BDCR_RTCEN;
// 设置初始时间(示例:2026-01-01 00:00:00)
RTC_SetCounter(1767225600); // Unix 时间戳
}
}
| 操作 | 代码 |
|---|---|
| 解锁备份域 | `PWR->CR |
| 写 BKP 寄存器 | BKP->DR[i] = data |
| 读 BKP 寄存器 | data = BKP->DR[i] |
| 配置 LSE | `RCC->BDCR |
| 选择 RTC 时钟 | `RCC->BDCR |
| 使能 RTC | `RCC->BDCR |
| 读 RTC 秒数 | RTC_GetCounter() |
| 写 RTC 秒数 | RTC_SetCounter(value)(需 CNF 模式) |