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_SELBANK_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初始化与回滚判断

// 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 应用程序中的喂狗与升级触发

// 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的可靠性。开发者可根据实际需求调整超时时间和回滚逻辑,例如增加重试次数或记录错误日志。希望本文能为你的嵌入式项目提供有价值的参考。