# STM32H7 双 Bank Flash 在线升级失败后自动回滚的硬件看门狗联动策略 ## 1. 为什么需要双 Bank + 看门狗联动? 在 STM32H7 系列中,Flash 被划分为两个独立的 Bank(Bank0 和 Bank1),每个 Bank 可独立擦写。利用这一特性,我们可以将当前运行固件放在一个 Bank,将新固件下载到另一个 Bank,然后通过切换启动地址完成升级。然而,升级过程并非绝对可靠: - 固件传输中断(如网络异常)导致写入不完整。 - 固件校验失败(CRC 或哈希不匹配)。 - 新固件本身存在缺陷,启动后立即崩溃。 若没有回滚机制,系统可能永久变砖。硬件看门狗(IWDG)能在程序跑飞或死循环时复位 MCU,但单纯复位无法解决固件损坏问题。因此,我们需要将 IWDG 与双 Bank 状态标志结合,在复位后检测上次启动是否成功,若失败则自动回滚到旧版本。 ## 2. 原理:双 Bank 切换与 IWDG 的协同工作 ### 2.1 双 Bank 启动机制 STM32H7 的 Flash 选项字节(Option Bytes)中的 `nBOOT1` 和 `nBOOT0` 位控制启动 Bank。更灵活的方式是使用 `FLASH_OTP` 或备份寄存器(如 RTC 备份寄存器)存储当前活动 Bank 的标识。我们通常采用以下策略: - **Bank0**:存放当前稳定运行的固件(旧版本)。 - **Bank1**:存放新下载的固件(待验证)。 升级流程: 1. 将新固件写入 Bank1。 2. 校验 Bank1 固件完整性(如 CRC32)。 3. 设置“待切换”标志,并复位。 4. 启动时检查标志,若存在则尝试从 Bank1 启动。 ### 2.2 IWDG 的角色 IWDG 独立于主时钟,即使系统死机也能复位。我们利用它来检测新固件是否“健康”: - 新固件启动后,必须在规定时间内(如 1 秒)喂狗,否则 IWDG 复位。 - 复位后,启动代码检查“启动失败计数”,若超过阈值(如 3 次),则判定新固件不可用,自动回滚到 Bank0。 ### 2.3 状态标志存储 状态标志可存放在备份寄存器(如 `RTC->BKPxR`)或 Flash 的专用区域。备份寄存器在复位后不丢失,且无需擦写 Flash,推荐使用。 ## 3. 配置步骤 ### 3.1 硬件准备 - STM32H743 开发板(或其他 H7 系列)。 - 外部或内部复位源(IWDG 使用 LSI 时钟,无需外部晶振)。 ### 3.2 软件配置 #### 步骤 1:使能 IWDG 在初始化代码中配置 IWDG,超时时间设为 1 秒(根据实际需求调整)。 ```c // 初始化 IWDG,LSI 约 32kHz,预分频 256,重载值 125 => 1s 超时 void IWDG_Init(void) { // 解锁写保护 IWDG->KR = 0x5555; // 设置预分频:256 IWDG->PR = 0x06; // 设置重载值:125 (约 1s) IWDG->RLR = 125; // 等待更新完成 while (IWDG->SR & IWDG_SR_PVU); while (IWDG->SR & IWDG_SR_RVU); // 启动看门狗 IWDG->KR = 0xCCCC; } // 喂狗函数 void IWDG_Feed(void) { IWDG->KR = 0xAAAA; } ``` #### 步骤 2:备份寄存器读写 使用 RTC 备份寄存器存储状态标志。注意需使能 PWR 和 RTC 时钟。 ```c // 使能备份寄存器访问 void BKP_Enable(void) { __HAL_RCC_PWR_CLK_ENABLE(); __HAL_RCC_RTC_ENABLE(); HAL_PWR_EnableBkUpAccess(); } // 写入标志 void BKP_Write(uint32_t reg, uint32_t val) { RTC->BKPxR[reg] = val; } // 读取标志 uint32_t BKP_Read(uint32_t reg) { return RTC->BKPxR[reg]; } ``` #### 步骤 3:启动流程设计 在 `main()` 函数开头,执行以下逻辑: ```c #define BOOT_MAGIC_OK 0xA5A5A5A5 // 新固件启动成功标志 #define BOOT_MAGIC_FAIL 0xDEADBEEF // 新固件启动失败标志 #define MAX_RETRY 3 // 最大尝试次数 int main(void) { // 初始化系统时钟等... BKP_Enable(); // 读取当前启动计数 uint32_t retry_count = BKP_Read(0); // 检查是否有待切换标志 if (BKP_Read(1) == 0x12345678) { // 待切换标志 // 尝试从 Bank1 启动(新固件) if (retry_count < MAX_RETRY) { retry_count++; BKP_Write(0, retry_count); // 设置启动地址为 Bank1 // 注意:实际切换需修改 VTOR 和向量表,或使用系统 Bootloader // 此处简化:直接跳转 void (*jump)(void) = (void (*)(void))0x08100000; // Bank1 基地址 jump(); } else { // 超过重试次数,回滚到 Bank0 BKP_Write(0, 0); BKP_Write(1, 0); // 清除待切换标志 // 继续执行 Bank0 固件(当前代码) } } // 正常启动流程 IWDG_Init(); // 如果是新固件首次启动,设置成功标志 if (BKP_Read(1) == 0x12345678) { // 新固件运行正常,清除待切换标志,并设置成功标志 BKP_Write(1, 0); BKP_Write(2, BOOT_MAGIC_OK); } // 主循环 while (1) { IWDG_Feed(); // 喂狗 // 应用逻辑... } } ``` #### 步骤 4:升级函数示例 在 Bank0 固件中,执行升级操作: ```c void OTA_Upgrade(void) { // 1. 擦除 Bank1 FLASH_Erase_Bank(FLASH_BANK_1); // 2. 写入新固件到 Bank1(通过串口/网络等) // ... 写入过程 ... // 3. 校验固件(如 CRC32) if (CRC_Check(BANK1_ADDR, firmware_size) != 0) { // 校验失败,中止升级 return; } // 4. 设置待切换标志 BKP_Write(1, 0x12345678); BKP_Write(0, 0); // 重置重试计数 // 5. 复位系统 NVIC_SystemReset(); } ``` ## 4. 完整代码示例(简化) 以下是一个整合的启动代码片段,展示了核心逻辑: ```c void SystemInit(void) { // ... 系统时钟初始化 ... BKP_Enable(); uint32_t retry = BKP_Read(0); uint32_t pending = BKP_Read(1); if (pending == 0x12345678) { if (retry < MAX_RETRY) { retry++; BKP_Write(0, retry); // 跳转到 Bank1 JumpToBank1(); } else { // 回滚 BKP_Write(0, 0); BKP_Write(1, 0); // 留在 Bank0 } } IWDG_Init(); // 新固件成功启动后,清除标志 if (pending == 0x12345678 && retry <= MAX_RETRY) { BKP_Write(1, 0); BKP_Write(2, BOOT_MAGIC_OK); } } void JumpToBank1(void) { // 设置主栈指针 uint32_t msp = *(volatile uint32_t*)0x08100000; __set_MSP(msp); // 跳转到复位向量 void (*reset)(void) = (void (*)(void))(*(volatile uint32_t*)0x08100004); reset(); } ``` ## 5. 注意事项 - **IWDG 超时时间**:需大于新固件初始化所需时间,否则可能误判失败。建议设置为 1-2 秒,并在新固件启动后尽早喂狗。 - **备份寄存器初始化**:首次使用前,确保备份寄存器已清零,否则可能误触发回滚。 - **Flash 擦写保护**:操作 Flash 时需解锁,并注意中断优先级,避免擦写过程中被中断导致错误。 - **向量表重定位**:从 Bank1 启动时,必须将 VTOR 设置为 Bank1 的基地址,否则中断向量错误。 - **固件校验**:写入后务必进行 CRC 或哈希校验,防止不完整固件启动。 - **回滚次数限制**:设置最大重试次数(如 3 次),避免无限循环。 - **调试注意事项**:使用调试器时,IWDG 可能因暂停而复位,建议在调试时禁用 IWDG 或延长超时。 ## 6. 总结 通过将 STM32H7 的双 Bank Flash 与硬件看门狗联动,我们构建了一个健壮的 OTA 升级回滚机制。核心思想是:利用备份寄存器记录状态,利用 IWDG 检测新固件是否正常运行,并在多次失败后自动回滚到旧版本。这种方法无需外部存储,成本低且可靠性高,适用于工业控制、物联网设备等对稳定性要求极高的场景。开发者可根据实际需求调整超时和重试策略,并确保在升级过程中严格遵循 Flash 操作规范。