# STM32H7双Bank模式下在线固件升级失败后自动回滚的硬件看门狗联动策略 ## 1. 背景与挑战 在线固件升级(OTA)是物联网设备的核心功能,但升级过程中的任何意外(如断电、通信中断、程序跑飞)都可能使设备无法启动。传统单Bank方案通常需要Bootloader引导,且升级失败后只能依赖外部干预。STM32H7的双Bank闪存架构(如STM32H743的2MB Flash分为两个1MB Bank)允许在运行Bank A时擦写Bank B,并通过硬件机制快速切换启动Bank,这为自动回滚提供了理想平台。 然而,仅靠双Bank还不够——若升级后的固件本身存在致命Bug(如启动即崩溃),系统将反复重启,无法进入回滚流程。此时,硬件看门狗(IWDG)成为关键:它独立于主时钟,一旦超时未喂狗,将强制复位MCU。通过巧妙设计,IWDG可触发Bootloader执行回滚操作。 ## 2. 双Bank机制与IWDG联动原理 ### 2.1 双Bank启动逻辑 STM32H7通过选项字节(Option Bytes)中的`nBOOT_SEL`和`BANK_SEL`位控制启动Bank。当`nBOOT_SEL=0`时,启动Bank由`BANK_SEL`决定(0=Bank1,1=Bank2)。此外,`SWAP_BANK`位可动态交换两个Bank的地址映射,无需修改启动引脚。 ### 2.2 IWDG回滚策略 设计思路如下: - **升级流程**:Bootloader将新固件写入非活动Bank,并设置一个“升级标志”(存储在备份寄存器或Flash末尾)。然后,通过`SWAP_BANK`切换启动Bank并复位。 - **运行监控**:新固件启动后,必须在规定时间内(如5秒)完成初始化并喂狗。若新固件崩溃或死机,IWDG超时复位。 - **回滚触发**:Bootloader在启动时检查升级标志。若标志存在且上次复位由IWDG产生,则判定升级失败,清除标志并恢复原启动Bank,从而回滚至旧固件。 该策略无需外部通信,完全由硬件自动完成,可靠性极高。 ## 3. 配置步骤(以STM32H743为例) ### 3.1 硬件连接 - 使用STM32H743-Nucleo开发板,板载ST-Link,无需额外硬件。 - IWDG使用LSI时钟(约32kHz),无需外部晶振。 ### 3.2 软件架构 - **Bootloader**:位于Bank1起始地址(0x08000000),负责固件接收、写入、启动切换和回滚判断。 - **App1**:位于Bank1偏移0x10000处(假设Bootloader占64KB)。 - **App2**:位于Bank2起始地址(0x08100000)。 ### 3.3 关键代码实现 #### 3.3.1 Bootloader中的IWDG初始化与回滚判断 ```c // bootloader.c #include "stm32h7xx_hal.h" #define APP1_ADDR 0x08010000 #define APP2_ADDR 0x08100000 #define UPGRADE_FLAG_ADDR 0x0800FF00 // 存储在Bootloader末尾 void IWDG_Init(uint32_t timeout_ms) { // 使能LSI时钟 RCC->CSR |= RCC_CSR_LSION; while ((RCC->CSR & RCC_CSR_LSIRDY) == 0); // 配置IWDG,超时时间约5秒(LSI=32kHz,预分频256,重载值625) IWDG->KR = 0x5555; // 解锁 IWDG->PR = 6; // 预分频256 IWDG->RLR = 625; // 625*256/32000 ≈ 5s IWDG->KR = 0xCCCC; // 启动 } void IWDG_Feed(void) { IWDG->KR = 0xAAAA; } uint32_t CheckUpgradeFlag(void) { return (*(volatile uint32_t*)UPGRADE_FLAG_ADDR == 0xA5A5A5A5); } void ClearUpgradeFlag(void) { FLASH_Unlock(); FLASH_Erase_Sector(FLASH_SECTOR_1, VOLTAGE_RANGE_3); // 假设标志在Sector1 FLASH_Lock(); } void JumpToApp(uint32_t app_addr) { // 设置MSP和跳转 uint32_t msp = *(volatile uint32_t*)app_addr; void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4)); __set_MSP(msp); app_reset(); } int main(void) { HAL_Init(); IWDG_Init(5000); // 检查升级标志和复位原因 if (CheckUpgradeFlag() && (RCC->CSR & RCC_CSR_IWDGRSTF)) { // 升级失败,回滚 ClearUpgradeFlag(); // 确保启动Bank为原Bank(假设升级时从Bank1切到Bank2,现在切回) FLASH_Unlock(); FLASH->OPTSR_CUR &= ~FLASH_OPTSR_SWAP_BANK; // 清除SWAP位 FLASH->CR |= FLASH_CR_OPTSTRT; while (FLASH->CR & FLASH_CR_OPTSTRT); FLASH_Lock(); // 复位系统 NVIC_SystemReset(); } // 正常启动流程:检查标志,若有则跳转到新Bank,否则跳转旧Bank if (CheckUpgradeFlag()) { // 升级完成,跳转到新Bank(假设当前Bank1,新固件在Bank2) JumpToApp(APP2_ADDR); } else { JumpToApp(APP1_ADDR); } while(1); } ``` #### 3.3.2 应用程序中的喂狗与升级触发 ```c // app.c(以App1为例) #include "stm32h7xx_hal.h" extern void IWDG_Feed(void); // 在Bootloader中实现,但App中需重新初始化IWDG?不,IWDG一旦启动无法关闭,App需继续喂狗。 void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); // 重新配置IWDG(因为复位后IWDG会重新加载,但需确保在App中也能喂狗) // 注意:IWDG在复位后自动启动,但预分频和重载值会保持?实际上复位后IWDG重新加载默认值,需重新配置。 // 因此,App中需再次初始化IWDG,并定期喂狗。 IWDG_Init(5000); while(1) { // 业务逻辑 IWDG_Feed(); HAL_Delay(1000); } } // 升级函数示例:接收新固件并写入Bank2,然后设置标志并切换 void OTA_Upgrade(uint8_t* data, uint32_t len) { // 擦写Bank2(需解锁Flash) // ... // 写入完成后,设置升级标志 FLASH_Unlock(); FLASH_Program_Word(UPGRADE_FLAG_ADDR, 0xA5A5A5A5); FLASH_Lock(); // 切换启动Bank(设置SWAP_BANK位) FLASH_Unlock(); FLASH->OPTSR_CUR |= FLASH_OPTSR_SWAP_BANK; FLASH->CR |= FLASH_CR_OPTSTRT; while (FLASH->CR & FLASH_CR_OPTSTRT); FLASH_Lock(); // 复位系统 NVIC_SystemReset(); } ``` ## 4. 注意事项 - **IWDG配置一致性**:Bootloader和App中必须使用相同的IWDG超时时间,否则可能导致误复位或回滚失效。建议将IWDG初始化代码封装为公共库。 - **升级标志的存储**:标志应放在独立扇区,避免与代码冲突。使用备份寄存器(如RTC的BKP)更安全,但需注意备份域电源。 - **Flash操作时序**:在擦写Bank2时,应关闭全局中断,防止中断服务程序干扰Flash操作,导致写入失败。 - **回滚条件**:仅当IWDG复位且升级标志存在时才回滚。若用户手动复位(如按键),不应回滚,否则可能误判。可通过`RCC->CSR`中的复位标志区分。 - **测试验证**:建议在开发阶段模拟升级失败(如故意在App中死循环),确认回滚机制正常工作。 ## 5. 总结 通过STM32H7的双Bank架构与IWDG联动,我们实现了一个无需外部干预的自动回滚系统。该策略不仅适用于工业设备,也可用于消费类产品,显著提升OTA的可靠性。开发者可根据实际需求调整超时时间和回滚逻辑,例如增加重试次数或记录错误日志。希望本文能为你的嵌入式项目提供有价值的参考。