tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(扩展篇)V1.0.0 — BKP/RTC章节 + 配套代码21~26" author: "尚硅谷研究院" date: 2026-07-16
用生活理解: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
文件: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)
{
RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 开启 PWR 时钟
PWR->CR |= PWR_CR_DBP; // 解锁备份域写保护
RCC->APB1ENR |= RCC_APB1ENR_BKPEN; // 开启 BKP 时钟
}
文件: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) { }
}
项目路径:stm32/22_bkp_test_hal
文件:stm32/22_bkp_test_hal/Core/Src/main.c
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
printf("尚硅谷备份寄存器实验...\n");
// HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 6666); // HAL 库写 BKP
while (1) { }
}
// 按键中断读取 BKP 数据
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KEY_Pin)
{
HAL_Delay(100);
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET)
{
printf("DR1 = %d\n", HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1));
}
}
}
BKP 支持防侵入检测——当 TAMPER 引脚被触发时,自动清除所有 BKP 寄存器内容(防止篡改)。
BKP->CR |= BKP_CR_TPE; // TPE=1: 使能防侵入
// 当 PC13 检测到上升沿时 → 自动清除 BKP 寄存器 → 产生中断
RTC(Real-Time Clock)提供精确的日历/时钟功能,具有闹钟中断、秒中断等功能。
时钟源选择 → RTC 预分频器 → 32位计数器 → 比较器 → 闹钟中断
↓ ↓
1Hz 基准时钟 秒中断
| 时钟源 | 频率 | 精度 | 特点 |
|---|---|---|---|
| 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->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
文件: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);
void RTC_SetAlarm(uint32_t s);
void RTC_SetTimestamp(uint32_t ts);
void RTC_GetDateTime(DateTime * dateTime);
#endif
文件:stm32/25_rtc_calendar_register/Hardware/RTC/rtc.c
#include "rtc.h"
void RTC_Init(void)
{
// 1. 开启 PWR 时钟 + 解锁备份域
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_DBP;
// 2. 使能 LSE → 等待就绪 → 选择 RTC 时钟源
RCC->BDCR |= RCC_BDCR_RTCEN;
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY)) { }
RCC->BDCR &= ~RCC_BDCR_RTCSEL;
RCC->BDCR |= RCC_BDCR_RTCSEL_0;
// 3. 等待 RTOFF → 进入配置模式 → 设 PRL=32767 → 退出配置
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
RTC->CRL |= RTC_CRL_CNF;
RTC->PRLH = 0;
RTC->PRLL = 0x7fff;
RTC->CRL &= ~RTC_CRL_CNF;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
}
void RTC_SetAlarm(uint32_t s)
{
RTC->CRL &= ~RTC_CRL_ALRF;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
RTC->CRL |= RTC_CRL_CNF;
RTC->CNTH = 0;
RTC->CNTL = 0;
s -= 1;
RTC->ALRH = (s >> 16) & 0xffff;
RTC->ALRL = (s >> 0) & 0xffff;
RTC->CRL &= ~RTC_CRL_CNF;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
}
void RTC_SetTimestamp(uint32_t ts)
{
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
RTC->CRL |= RTC_CRL_CNF;
RTC->CNTH = (ts >> 16) & 0xffff;
RTC->CNTL = (ts >> 0) & 0xffff;
RTC->CRL &= ~RTC_CRL_CNF;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
}
void RTC_GetDateTime(DateTime *dateTime)
{
while (!(RTC->CRL & RTC_CRL_RSF)) { }
uint32_t second = RTC->CNTH << 16 | RTC->CNTL;
struct tm* ptm = localtime(&second);
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/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)
{
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/26_rtc_calendar_hal
文件:stm32/26_rtc_calendar_hal/Core/Src/main.c
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
void read_stored_date(RTC_HandleTypeDef * hrtc);
void write_stored_date(RTC_HandleTypeDef * hrtc);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
printf("尚硅谷RTC实验:RTC实时时钟...\n");
RTC_DateTypeDef date;
RTC_TimeTypeDef time;
while (1)
{
// 从 BKP 寄存器恢复保存的日期
read_stored_date(&hrtc);
HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN);
HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
// 将当前日期写入 BKP 寄存器
write_stored_date(&hrtc);
printf("20%02d年%02d月%02d日 %02d:%02d:%02d\n",
date.Year, date.Month, date.Date,
time.Hours, time.Minutes, time.Seconds);
HAL_Delay(1000);
}
}
void read_stored_date(RTC_HandleTypeDef * hrtc)
{
if (HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR1) == 0)
return;
hrtc->DateToUpdate.Year = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR2);
hrtc->DateToUpdate.Month = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR3);
hrtc->DateToUpdate.Date = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR4);
hrtc->DateToUpdate.WeekDay = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR5);
}
void write_stored_date(RTC_HandleTypeDef * hrtc)
{
HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR1, 1);
HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR2, hrtc->DateToUpdate.Year);
HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR3, hrtc->DateToUpdate.Month);
HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR4, hrtc->DateToUpdate.Date);
HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR5, hrtc->DateToUpdate.WeekDay);
}
项目路径:stm32/23_rtc_alarm_standby_register
文件:stm32/23_rtc_alarm_standby_register/Hardware/RTC/rtc.h
#ifndef __RTC_H
#define __RTC_H
#include "stm32f10x.h"
void RTC_Init(void);
void RTC_SetAlarm(uint32_t s);
#endif
文件:stm32/23_rtc_alarm_standby_register/Hardware/RTC/rtc.c(初始化和闹钟设置)
#include "rtc.h"
void RTC_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_DBP;
// 复位备份域寄存器(与日历版不同:此处主动复位)
RCC->BDCR |= RCC_BDCR_BDRST;
RCC->BDCR &= ~RCC_BDCR_BDRST;
RCC->BDCR |= RCC_BDCR_RTCEN;
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY)) { }
RCC->BDCR &= ~RCC_BDCR_RTCSEL;
RCC->BDCR |= RCC_BDCR_RTCSEL_0;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
RTC->CRL |= RTC_CRL_CNF;
RTC->PRLH = 0;
RTC->PRLL = 0x7fff;
RTC->CRL &= ~RTC_CRL_CNF;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
}
void RTC_SetAlarm(uint32_t s)
{
RTC->CRL &= ~RTC_CRL_ALRF;
while (!(RTC->CRL & RTC_CRL_RTOFF)) { }
RTC->CRL |= RTC_CRL_CNF;
RTC->CNTH = 0;
RTC->CNTL = 0;
s -= 1;
RTC->ALRH = (s >> 16) & 0xffff;
RTC->ALRL = (s >> 0) & 0xffff;
RTC->CRL &= ~RTC_CRL_CNF;
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");
LED_On(LED_2);
Delay_s(2);
while (1)
{
printf("正常运行执行完毕,3s后进入待机模式...\n");
Delay_s(3);
printf("进入待机模式,5s后闹钟唤醒...\n");
Delay_ms(1);
RTC_SetAlarm(5); // 设置 5 秒后闹钟
enter_standby_mode();
printf("从待机模式唤醒...\n");
Delay_s(2);
}
}
void enter_standby_mode(void)
{
SCB->SCR |= SCB_SCR_SLEEPDEEP;
PWR->CR |= PWR_CR_PDDS; // 待机模式
__WFI();
}
项目路径:stm32/24_rtc_alarm_standby_hal
文件:stm32/24_rtc_alarm_standby_hal/Core/Src/main.c
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "rtc.h"
uint8_t ch;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_RTC_Init();
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB))
{
printf("从待机模式唤醒:\n");
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
}
if (__HAL_PWR_GET_FLAG(PWR_FLAG_WU))
{
printf("产生了唤醒事件:\n");
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
}
printf("尚硅谷低功耗实验:待机模式...\n");
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
HAL_Delay(2000);
while (1)
{
printf("正常运行执行完毕,3s后进入待机模式...\n");
HAL_Delay(3000);
printf("进入待机模式,等待5s后RTC闹钟唤醒...\n");
// 使用自定义 RTC 闹钟函数(与寄存器版共用逻辑)
RTC_SetAlarm(5);
HAL_PWR_EnterSTANDBYMode();
printf("从待机模式唤醒...\n");
HAL_Delay(2000);
}
}
// CubeMX 生成 RTC 配置:LSE 时钟源,启用日历
RTC_HandleTypeDef hrtc;
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
sTime.Hours = 12;
sTime.Minutes = 0;
sTime.Seconds = 0;
sTime.TimeFormat = RTC_HOURFORMAT_24;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
sDate.Year = 26;
sDate.Month = 7;
sDate.Date = 15;
sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // 必须先读时间再读日期!
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
uint16_t data = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
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() |
| 设置闹钟 | RTC_SetAlarm(s) → CNT=0, ALR=s-1 |
自定义 RTC_SetAlarm() |
| 配置 LSE | RCC->BDCR 逐位操作 |
CubeMX 自动配置 |
| 写注意事项 | 需 CNF 模式 + 等待 RTOFF | HAL 库自动处理 |
GetTime() 再读 GetDate(),否则日期可能不刷新