当前位置: 首页 > news >正文

BKP相关知识点

1. BKP 备份域是什么?能做什么?

  • 备份域(Backup domain)是一块断电不丢的小区域(前提是 VBAT 供电不断),包含:

    • BKP 数据寄存器:F103C8T6 有 10 个 16 位备份寄存器:BKP_DR1…DR10(高密度才是 42 个)。

    • **RTC(实时时钟)**相关寄存器与时钟选择控制(在 RCC 的 BDCR 等)。

    • Tamper 防拆/防篡改引脚与相关控制(通常在 PC13)。

    • RTC 输出/校准时钟可复用到 PC13。

  • 供电:备份域由 VBAT 引脚供电(F103C8T6 封装上有 VBAT),当主电源 VDD 掉电但 VBAT 仍在,BKP 寄存器与 RTC 计数仍然保留

  • 典型用途

    • 断电不丢的小数据(校准参数、设备 ID、上次状态、Boot 计数、密钥片段等)。

    • RTC 时钟与秒/报警输出。

    • Tamper 触发时自动清空备份域并产生中断/标志(防止被动过手脚的数据被保留)。

2. 访问/配置备份域的基本步骤

备份域默认写保护,要写它(含配置 LSE/RTC),必须按顺序“开门”:

HAL 版:

// 1) 开 PWR/BKP 外设时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();// 2) 允许访问备份域(设置 PWR->CR.DBP)
HAL_PWR_EnableBkUpAccess();  // 或:PWR->CR |= PWR_CR_DBP;// ——接下来才可以写:RCC->BDCR、BKP->DRx、RTC 配置等——

SPL/标准库版:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);   // 允许访问备份域

读 BKP_DRx 通常也建议先开 BKP 时钟;只有写操作必须先 DBP=1 解锁。

3. RTC 与备份域的关系(时钟源、选择与复位)

  • RTC 时钟源(三选一):

    1. LSE 32.768 kHz(外部 32k 晶振,高精度,推荐)

    2. LSI ~40 kHz(内部 RC,精度一般)

    3. HSE/128(由外部高速晶振分频,受系统时钟影响)

  • 选择/开启在 RCC->BDCR

    • LSEON/LSERDY/LSEBYP/LSEDRV 控制 LSE;

    • RTCSEL[1:0] 选择 RTC 时钟源;

    • RTCEN 使能 RTC;

    • BDRST备份域复位(包含 RTC 与 BKP 寄存器全部清零)。

  • 首次上电备份域被复位后需要:

    1. 选择 RTC 时钟源并等待就绪;

    2. 使能 RTC;

    3. 配置 RTC 预分频(例如 LSE 时设为 32767 得到 1 Hz 计数);

    4. 等待 RTC 写忙标志清除(F1 经典的 RTC_CRL 标志流程)。

HAL 典型初始化片段(LSE 作 RTC 源):

// 假设系统时钟已 OK
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();// 1) 启动 LSE
RCC_OscInitTypeDef osc = {0};
osc.OscillatorType = RCC_OSCILLATORTYPE_LSE;
osc.LSEState = RCC_LSE_ON;
HAL_RCC_OscConfig(&osc);// 2) 选择 RTC 时钟源并使能
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
__HAL_RCC_RTC_ENABLE();// 3) 初始化 RTC,设预分频得到 1Hz
RTC_HandleTypeDef hrtc = {0};
hrtc.Instance = RTC;
HAL_RTC_Init(&hrtc);   // 对于 F1,HAL 会内部做 PRL/CRL 的配置

稳定性:若使用 LSI,当温度/电压变化较大时 RTC 精度会明显漂移。LSE 外部 32k 晶振更稳定。

4. BKP 数据寄存器(断电不丢)

  • F103C8T6 有 10 个 16-bitBKP_DR1BKP_DR10

  • 常见技巧:存 32 位数就用两个寄存器(高 16/低 16)。

  • 首次启动检测:放个 Magic 值(如 0xA5A5),用于区分“冷启动/数据有效”。

HAL 版读写(通过 RTCEx 宏):

// 写
HAL_PWR_EnableBkUpAccess();
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xA5A5);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)(value & 0xFFFF));
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)(value >> 16));// 读
uint16_t magic = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
uint32_t value = ((uint32_t)HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3) << 16) |(uint32_t)HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);

SPL/直接寄存器版:

PWR_BackupAccessCmd(ENABLE);
BKP->DR1 = 0xA5A5;
uint16_t magic = BKP->DR1;

5. Tamper(防拆)与 PC13 的“多重身份”

  • PC13 默认是普通 GPIO,但在备份域里还有两个“特殊功能”:

    1. Tamper 引脚:检测到设定的电平即触发“篡改事件”;

      • 触发后可自动清空所有 BKP_DR 寄存器(硬件行为),并置位标志,可选产生中断

      • 相关配置在 BKP->CR/CSR/RTCCR(F1 架构)。

    2. RTC 输出/校准时钟输出:可把秒脉冲/报警校准时钟输出到该脚。

      • 一旦使能此输出,PC13 将由备份域接管,不再是普通 GPIO

也就是说:Tamper 与 RTC/校准输出都“占用”PC13,互斥关系要注意。很多蓝色小板 PC13 上挂了 LED(低电平亮),使能这些功能会改变 LED 行为。

SPL 版 Tamper 快速配置示例(低电平触发 + 中断):

// 1) 开时钟 & 解锁备份域
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);// 2) 配置触发极性、开启 Tamper
BKP_TamperPinLevelConfig(BKP_TamperPinLevel_Low); // 低电平为事件
BKP_TamperPinCmd(ENABLE);// 3) 允许 Tamper 中断(可选)
BKP_ITConfig(ENABLE);// 4) NVIC 里打开 TAMPER_IRQn,并实现中断服务函数
void TAMPER_IRQHandler(void)
{if (BKP_GetITStatus() == SET) {// 这里说明发生了篡改事件,BKP_DR 已被硬件清零BKP_ClearITPendingBit();BKP_ClearFlag(); // 清除事件/标志// TODO: 做上报/保护动作}
}

清空行为:Tamper 事件发生时,硬件自动清零所有 BKP_DRx;不需要手动逐个清零(反而要注意别误以为数据还在)。

6. 在 PC13 输出 RTC 秒脉冲或校准时钟

  • BKP_RTCCR 里可以配置:

    • CCO(Calibration Clock Output):把校准时钟输出(典型 512 Hz/1 Hz,取决于校准设置)。

    • ASOE/ASOS:选择并使能把 RTC 秒脉冲(1 Hz)或 RTC Alarm 输出到 PC13。

  • 一旦启用这些输出,PC13 不再作为 GPIO。请勿再用普通 GPIOC->CRH 去配置它。

SPL 示例:输出 1 Hz 秒脉冲到 PC13:

PWR_BackupAccessCmd(ENABLE);
// 关闭 Tamper,避免冲突
BKP_TamperPinCmd(DISABLE);// 选择“秒脉冲”并使能输出(名称可能因库版本不同略有差异)
BKP_RTCOutputConfig(BKP_RTCOutputSource_Second); // 选择秒脉冲
BKP_RTCOutputCmd(ENABLE);                         // 使能输出
// 现在 PC13 上能看到 1Hz 脉冲(蓝板 LED 会每秒闪烁)

7. 备份域“复位/丢失”的几种情形

  • BDRST = 1:软件手动复位备份域 → RTC/BKP 全清零。

  • VBAT 断电(且无 VDD)→ 备份域失电 → 数据丢失。

  • Tamper 事件 → 自动清空 BKP_DR。

  • 普通系统复位/看门狗复位/上电复位:只要 VBAT 始终存在,BKP_DR/RTC 内容会保留

建议每次上电用 Magic + 校验值判断数据是否有效。

8. 常见易错点与排查

  1. 忘记解锁 DBP:写 BKP/BDCR/RTC 没反应 → 先 HAL_PWR_EnableBkUpAccess()PWR_BackupAccessCmd(ENABLE)

  2. 没开外设时钟:BKP/PWR 时钟未开 → 访问失败或 HardFault。

  3. Tamper 与 PC13 冲突:你要输出秒脉冲/校准时钟就别开 Tamper(反之亦然),且 PC13 不再是普通 GPIO。

  4. 误以为复位会清 BKP:普通复位不会清;只有 BDRST、Tamper、VBAT 失电才清。

  5. HAL 回退到 LSI 未注意:LSE 启动失败(晶振或焊接问题)→ 若代码回退 LSI,RTC 精度会飘;建议打印 LSERDY 状态。

  6. F1 特有的 RTC 写忙时序:若你直接操作 RTC 寄存器,需要按 RM0008 的 RTC_CRL 标志流程来写(RTOFF/RSF 等)。

9. 速查表(你常用到的寄存器/位)

  • RCC->BDCRLSEON/LSERDY/LSEBYP/LSEDRV, RTCSEL, RTCEN, BDRST

  • PWR->CRDBP(允许写备份域)

  • BKP->DR1…DR10:10 个 16 位备份寄存器

  • BKP->RTCCRCCO(校准时钟输出)、ASOE/ASOS(秒/报警输出控制)

  • BKP->CR/CSRTPE/TPAL(Tamper 使能/极性)、TPIE(Tamper 中断)、TEF/TIF(事件/中断标志)、清标志位(写 1 清)

  • RTC(F1 经典寄存器组)PRLH/PRLL(预分频)、CNTH/CNTL(计数)、ALRH/ALRL(闹钟)、CRH/CRL(中断/状态)

读写实验:

rtc.c

// rtc.c: 提供 RTC(实时时钟)与 BKP(备份寄存器)相关的最小化封装
#include "rtc.h"// 声明一个全局的 RTC 句柄(HAL 的外设句柄对象)
// 初始化时需要把它的 Instance 指向 RTC 外设基址,并设置若干初始化参数。
RTC_HandleTypeDef rtc_handle = {0};/*** @brief  初始化 RTC 子系统与备份域访问权限*         注意:这里的实现只做了“允许访问备份域 + 调用 HAL_RTC_Init”,*               并没有显式选择 RTC 时钟源(LSE/LSI/HSE),*               但对“仅读写 BKP 寄存器”的场景已经足够。*               如果你需要让 RTC 真的跑秒,请参考文末“改进建议”开启 LSE 等。*/
void rtc_init(void)
{// 1) 打开 PWR(电源控制)外设时钟:否则无法修改 PWR->CR.DBP 位__HAL_RCC_PWR_CLK_ENABLE();// 2) 打开 BKP(备份接口)外设时钟:否则无法访问 BKP 寄存器组__HAL_RCC_BKP_CLK_ENABLE();// 3) 取消备份域写保护(设置 PWR->CR.DBP=1),//    之后才能写 RCC->BDCR / BKP->DRx / RTC 寄存器等备份域资源HAL_PWR_EnableBkUpAccess();// 4) 配置 RTC 句柄的实例为 F1 系列的 RTC 外设rtc_handle.Instance = RTC;// 5) 预分频设置(F1 里这是 20bit 的 PRL 值,HAL 统一称 AsynchPrediv)//    如果 RTC 时钟源为 32768 Hz 的 LSE,把预分频设为 32767 可得到 1Hz 计数(32768/(32767+1)=1)rtc_handle.Init.AsynchPrediv = 32767;// 6) 关闭 RTC 输出(PC13 上不输出秒脉冲/报警/校准时钟)//    仅使用 BKP 时通常关闭;否则会占用 PC13,与普通 GPIO 冲突rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;// 7) 调用 HAL 初始化//    HAL_RTC_Init 内部会根据系列做必要的寄存器设置(不同系列流程不同)//    这里未显式选择 RTC 时钟源,若 HAL 未找到有效的 RTC 时钟,可能返回 HAL_ERROR;//    但“读写 BKP 寄存器”通常不依赖 RTC 计数,依然能工作。HAL_RTC_Init(&rtc_handle);
}/*** @brief  从 BKP 备份寄存器读取 16 位数据* @param  bkrx  备份寄存器编号参数(注意:HAL 的 API 期望传入的是宏 RTC_BKP_DR1..DR10,*                而不是数字 1..10,见文末改进建议)* @retval uint16_t 读到的数据*/
uint16_t rtc_read_bkr(uint8_t bkrx)
{uint32_t data = 0;// HAL_RTCEx_BKUPRead:// 第1个参数:RTC 句柄指针// 第2个参数:备份寄存器选择(应为 RTC_BKP_DR1..DR10 这类宏)// 返回值:以 32 位数返回,但 F103 的 BKP 寄存器本质为 16 位,这里我们再强转回 16 位data = HAL_RTCEx_BKUPRead(&rtc_handle, bkrx);// 仅保留低 16 位返回return (uint16_t)data;
}/*** @brief  往 BKP 备份寄存器写 16 位数据* @param  bkrx  备份寄存器编号参数(注意:应使用 RTC_BKP_DR1..DR10 宏)* @param  data  待写入的数据(16 位)*/
void rtc_write_bkr(uint8_t bkrx, uint16_t data)
{// HAL_RTCEx_BKUPWrite:// 第1个参数:RTC 句柄指针// 第2个参数:备份寄存器选择(应为 RTC_BKP_DR1..DR10 宏)// 第3个参数:要写入的数据(HAL 用 32 位形参,但对 F1 的 BKP_DRx 其实只有 16 位有效)HAL_RTCEx_BKUPWrite(&rtc_handle, bkrx, data);
}

非常关键的说明HAL_RTCEx_BKUPRead/Write() 的第 2 个参数不是“序号 1..10”,而是 RTC_BKP_DR1 ~ RTC_BKP_DR10 这些宏常量
示例里把 bkrx 作为 uint8_t 传入(例如传了 1),如果碰巧 HAL 的实现使用“地址宏”,传 1 可能导致访问错误地址或 HardFault,只是某些库/编译器下“看起来能跑”。规范写法见文末“改进建议”。

main.c

// main.c: 演示如何初始化 BKP/RTC 并在 BKP 寄存器中写入与读取一个 16 位数
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "rtc.h"int main(void)
{// 1) 初始化 HAL 库(配置 SysTick、NVIC 优先级分组、Flash 加速等)HAL_Init();// 2) 配置系统时钟到 72 MHz(外部 HSE * 9),保证后续外设工作频率正确stm32_clock_init(RCC_PLL_MUL9);// 3) LED 初始化(用于指示,具体实现见你的 led.c)led_init();// 4) 串口1初始化,波特率 115200,用于打印调试信息uart1_init(115200);// 5) 初始化 RTC / 备份域访问(允许写 BKP、调用 HAL_RTC_Init)rtc_init();// 6) 启动时打印一句话,表明程序已经运行printf("hello world!\\r\n");// 7) 往“某个”备份寄存器里写入 0xA5A5//    这里你的调用传的是 1(uint8_t),按 HAL 规范应传 RTC_BKP_DR1 宏,见文末建议rtc_write_bkr(1, 0xA5A5);// 8) 读取同一个备份寄存器并打印(%X 以十六进制大写输出)//    printf 的整型默认提升规则:uint16_t 会提升为 int 传入,%X 可正常打印printf("读出来的值为:%X\\r\n", rtc_read_bkr(1));while(1){ // 主循环空转;BKP 中的数据会一直保留(VBAT 不断电且未触发清除的情况下)}
}

实验现象

1.上电后串口输出:

hello world!
读出来的值为:A5A5

说明写入 BKP 寄存器(示例中意图是 DR1)成功,并读回了 0xA5A5

2.如果只做“普通复位”(NRST / 看门狗 / 软件复位)

  • 由于备份域由 VBAT 供电且不会被普通复位清空,再次运行仍会打印:

读出来的值为:A5A5

即数据被跨复位保留

3.如果完全断电(VDD 与 VBAT 都断)再上电

  • 备份域失电,BKP 寄存器内容丢失,读出的值可能变为 0x0000(或不可预期)。

  • 再写一次后才会恢复为 A5A5

4.如果你启用了 Tamper(未在本代码中启用)且发生篡改事件

    • 硬件会自动清空所有 BKP_DRx;

    • 读出的值会变成 0x0000,并置位 Tamper 标志(需要你的中断/轮询代码去清标志)。

总结:正常情况下,只要 VBAT 不断、不触发 BDCR 复位/不发生 Tamper,BKP 寄存器里的 0xA5A5跨复位、跨上电(VDD)保留

重要注意点

1) 正确传入“备份寄存器编号”

HAL_RTCEx_BKUPRead/Write() 的第二个参数必须是下面这些宏之一,而不是 1..10 的普通数字:

// 对于 STM32F103C8T6(10 个 16 位备份寄存器)
RTC_BKP_DR1,  RTC_BKP_DR2,  RTC_BKP_DR3,  RTC_BKP_DR4,  RTC_BKP_DR5,
RTC_BKP_DR6,  RTC_BKP_DR7,  RTC_BKP_DR8,  RTC_BKP_DR9,  RTC_BKP_DR10

推荐把“序号 1..10”映射为宏,这样主程序仍可用“人类友好”的 1..10 调用:

// rtc.c 内部增加一个映射表(示例)
static const uint32_t s_bkp_idx_map[11] = {0,RTC_BKP_DR1, RTC_BKP_DR2, RTC_BKP_DR3, RTC_BKP_DR4, RTC_BKP_DR5,RTC_BKP_DR6, RTC_BKP_DR7, RTC_BKP_DR8, RTC_BKP_DR9, RTC_BKP_DR10
};uint16_t rtc_read_bkr(uint8_t bkr_index_1_to_10)
{if (bkr_index_1_to_10 == 0 || bkr_index_1_to_10 > 10) return 0;return (uint16_t)HAL_RTCEx_BKUPRead(&rtc_handle, s_bkp_idx_map[bkr_index_1_to_10]);
}void rtc_write_bkr(uint8_t bkr_index_1_to_10, uint16_t data)
{if (bkr_index_1_to_10 == 0 || bkr_index_1_to_10 > 10) return;HAL_RTCEx_BKUPWrite(&rtc_handle, s_bkp_idx_map[bkr_index_1_to_10], data);
}

这样,在 main.c 中仍然可以用 rtc_write_bkr(1, 0xA5A5);,但底层会传入正确的 RTC_BKP_DR1

备份域的“清空”场景

  • RCC->BDCR.BDRST=1 会把 RTC 与 BKP 整个备份域复位(全清空)。

  • VBAT 失电也会丢数据。

  • Tamper 事件会自动清空所有 BKP_DRx。

再强调一次:只读写 BKP,不一定需要 RTC 走时

很多项目仅把 BKP 当作“掉电不丢的小 NVRAM”,不需要 RTC 真正计时
这时只要:

  • 开 PWR/BKP 时钟;

  • 取消备份域写保护;

  • 使用 HAL_RTCEx_BKUPRead/Write 即可。

http://www.dtcms.com/a/339165.html

相关文章:

  • 从机器视觉到图像识别:计算机视觉的多维探索
  • LINUX819 shell:for for,shift ,{} ,array[0] array[s] ,declare -x -a
  • 服务注册与服务发现原理与实现
  • CentOS 8开发测试环境:直接安装还是Docker更优?
  • Docker核心---数据卷(堵门秘籍)
  • 应用控制技术、内容审计技术、AAA服务器技术
  • 深入理解Redis持久化:让你的数据永不丢失
  • 电子电气架构 ---SDV技术基础与传统E/E架构有何不同?
  • 利用图数据库高效解决 Text2sql 任务中表结构复杂时占用过多大模型上下文的问题
  • 智慧养老中IPTV融合对讲:重塑养老沟通新生态
  • t12 low power design: power plan脚本分享(4) power stripe
  • spring声明式事务,finally 中return对事务回滚的影响
  • 数据资产入表——图解《企业数据资源相关会计处理暂行规定》《数据资产评估指导意见》【附全文阅读】
  • 企业架构是什么?解读
  • 功能组状态的独立性以及 进程启动在状态管理中的设计意图
  • 用“新”突围,康师傅布局增量市场
  • latex|算法algorithm宏包和注意事项
  • 检测设备为手机或电脑来跳转不同网页
  • GaussDB 八种常规锁介绍
  • Redis的使用(初阶)
  • 命令行如何更改参数?
  • 化学反应中的贝叶斯优化
  • Spring RestTemplate的postForObject()方法详解与实践指南
  • 从一个ctf题中学到的多种php disable_functions bypass 姿势
  • 网络聚合链路与软件网桥配置指南
  • 效果图只是起点:深挖3D可视化在家装建筑中的隐藏金矿
  • Unity进阶--C#补充知识点--【C#各版本的新功能新语法】C#1~4与C#5
  • mycat分库分表实验
  • 安全设计-防止非法移机
  • Java多线程:线程创建、安全、同步与线程池