STM32后备寄存器在OTA升级中的妙用
一、后备寄存器介绍
1、STM32后备寄存器概述
后备寄存器(Backup Registers)是STM32微控制器中一组特殊的存储单元,用于在系统掉电或复位时保存关键数据。这些寄存器通过后备电源(如电池)供电,确保数据在低功耗模式下或主电源失效时不会丢失。
2、后备寄存器的特点
- 掉电保持:通过VBAT引脚连接后备电源(如3V电池),在主电源关闭时保持数据。
- 独立访问:需通过RTC或备份域接口访问,避免意外修改。
- 数量与位宽:不同型号的STM32后备寄存器数量不同(如STM32F1系列有20个16位寄存器,STM32F4系列有32个32位寄存器)。
3、启用后备寄存器的步骤
-
使能电源和备份接口时钟
通过RCC寄存器使能PWR和BKP(或BKPSRAM)时钟:__HAL_RCC_PWR_CLK_ENABLE();
-
允许访问备份域
调用库函数解除备份域的写保护:HAL_PWR_EnableBkUpAccess();
-
配置RTC时钟(可选)
若需RTC功能,需配置时钟源:RCC_RTCCLKConfig(RCC_RTCCLKSource_HSE_Div128); RCC_RTCCLKCmd(ENABLE);
-
读写后备寄存器
通过库函数或直接访问寄存器读写数据:HAL_RTCEx_BKUPWrite(BKP_DR1, 0xA5A5); // 写入数据 uint16_t data = HAL_RTCEx_BKUPRead(BKP_DR1); // 读取数据
4、注意事项
- 电源管理:确保VBAT电源稳定,避免数据丢失。
- 复位影响:除电源复位或VBAT断电外,后备寄存器数据通常不会清除。
- 寄存器用途:部分寄存器可能被RTC或硬件占用,需查阅手册确认。
5、典型应用场景
- 系统配置保存:存储校准参数或设备设置。
- RTC相关数据:保存时间戳或闹钟配置。
- 低功耗模式:在待机模式下记录唤醒事件。
二、OTA升级中的妙用
在OTA升级中我们可以使用后备寄存器在程序复位后值不复位的特性来判断是否进入bootloader更新程序。具体流程如下:
1、升级逻辑对比
一般 Bootloader 实现的逻辑如下:
这种方式适合于简单的裸机程序或可控的 OS 程序(即所有外设硬件都可把控),在准备环境的时候将其全部关闭。
但对于一些复杂的或者 OS 中轮子已造好的程序,有一些因素不花时间研究无法把控,在准备环境时很可能就会遗漏一些未关闭导致出各种各样的问题。
使用后备寄存器逻辑如下:
该方法可以使 Bootloader 就作为一个 OS 应用程序开发,需要跳转的时候就操作一下寄存器并软件复位即可。不会造成bootloader
和app
同时设置寄存器值的情况。
2、实现是否进入bootloader判断
值定义:
typedef enum
{
bkpPowerOn = 0x00000000, //初次上电
bkpStayBoot = 0x5555AAAA, //从APP层跳转过来需要停留
bkpJumpToApp = 0xA5A55555, //直接跳转到APP
bkpUpgrade = 0x5555A5A5, //需要进入bootloader升级
bkpMax,
}bkpTypedef;
typedef void (*boot_app_func)(void);
void ota_start_application(void)
{
__HAL_RCC_RTC_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
RTC_HandleTypeDef RTC_Handler = {0};
RTC_Handler.Instance = RTC;
uint32_t bkpData = HAL_RTCEx_BKUPRead(&RTC_Handler, BKP_UPGRADE_ADDR);
//HAL_RTCEx_BKUPWrite(&RTC_Handler, OTA_BKP_ADDR, bkpPowerOn);
if (bkpData != bkpJumpToApp) return;
boot_app_func app_func = NULL;
uint32_t app_addr = APP_START_ADDR;
if (((*(__IO uint32_t *)(app_addr + 4)) & 0xff000000) != 0x08000000)
{
HAL_RTCEx_BKUPWrite(&RTC_Handler, BKP_UPGRADE_ADDR, bkpStayBoot);
return;
}
/* 栈顶地址在 128K RAM 间 */
if (((*(__IO uint32_t *)app_addr) - 0x20000000) >= (STM32_SRAM_SIZE * 1024))
{
HAL_RTCEx_BKUPWrite(&RTC_Handler, BKP_UPGRADE_ADDR, bkpStayBoot);
return;
}
app_func = (boot_app_func) * (__IO uint32_t *)(app_addr + 4);
/* Configure main stack */
__set_MSP(*(__IO uint32_t *)app_addr);
/* jump to application */
app_func();
}
然后在void SystemInit(void)
函数中调用此函数:
void SystemInit(void)
{
extern void ota_start_application(void);
ota_start_application();
#if defined(USER_VECT_TAB_ADDRESS)
/* Configure the Vector Table location -------------------------------------*/
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
#endif
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 20U)|(3UL << 22U)); /* set CP10 and CP11 Full Access */
#endif
}
这样做的好处是系统上电如果不需要更新程序可以直接跳转到APP
,避免在bootloader
中对一些寄存器的初始化。
防止跳转到APP重复对寄存器赋值造成的问题。
SystemInit函数执行时机:
SystemInit
函数通常在STM32启动过程中由启动文件(startup_stm32fxxx.s
)调用,具体执行时机如下:
-
启动阶段执行
SystemInit
函数在处理器复位后,由启动代码(startup_stm32xxxx.s
)自动调用,在进入main函数之前完成基本的时钟系统配置。 -
调用位置
该函数定义在system_stm32fxxx.c
文件中,由汇编启动文件中的Reset_Handler
直接调用。以下是典型调用流程:
-
执行内容
SystemInit
函数主要完成以下初始化工作:
- 设置中断向量表位置
- 配置FPU(如果启用)
- 初始化时钟系统(HSE/MSI/HSI等时钟源选择)
- 配置Flash等待周期
- 设置系统时钟频率
3、bootloader更新完成后怎么做
当bootloader
更新完程序以后我们需要清除后备寄存器中的更新标记值,直接执行复位就可以,如下:
static void ota_jump_app(void)
{
__disable_irq();
RTC_HandleTypeDef RTC_Handler = {0};
RTC_Handler.Instance = RTC;
HAL_RTCEx_BKUPWrite(&RTC_Handler, BKP_UPGRADE_ADDR, bkpJumpToApp);
HAL_NVIC_SystemReset();
}
程序复位以后其他寄存器的值就会被初始化,然后重新进入bootloader
执行ota_start_application
函数直接跳转到APP
。