STM32H7双Bank Flash在线升级失败后自动回滚的硬件看门狗联动设计

1. 为什么需要双Bank + 看门狗联动?

在线升级(OTA)是嵌入式产品的核心功能,但升级过程中断电、通信中断或固件校验失败都可能导致系统无法启动。传统单Bank方案只能依赖外部备份,而STM32H7的双Bank Flash允许在运行当前固件的同时,将新固件写入另一个Bank,并通过硬件机制快速切换启动。

然而,仅靠双Bank还不够——如果新固件本身存在缺陷(如死循环),系统可能卡死在App中。此时,硬件看门狗(IWDG)成为最后防线:它独立于主时钟,超时后强制复位,让Bootloader有机会回滚到旧固件。

核心思想:升级过程分为两个阶段——

  • 阶段1(写入新固件):暂停喂狗,允许长时间写入,但若卡死则看门狗复位。
  • 阶段2(验证并切换):复位后Bootloader检查新固件有效性,失败则回滚旧Bank。

2. 硬件与原理基础

2.1 STM32H7双Bank Flash特性

  • H7系列(如STM32H743)Flash容量通常为2MB,分为两个1MB的Bank(Bank0和Bank1)。
  • 通过选项字节FLASH_OPTCRSWAP_BANK位,可动态交换两个Bank的地址映射。
  • 支持“无感切换”:在运行时设置交换位,复位后系统从新Bank启动。

2.2 硬件看门狗(IWDG)

  • 独立于主时钟(LSI约32kHz),超时时间可配置(如0.5~32秒)。
  • 一旦启动,必须定期“喂狗”(写0xAAAA到IWDG_KR),否则系统复位。
  • 在升级期间,如果喂狗中断,看门狗会复位系统,从而触发回滚机制。

3. 系统架构与升级流程

Bootloader (Bank0)  <--->  App (Bank1)
  • Bootloader:位于固定地址(如0x08000000),负责启动、升级、回滚。
  • App:运行在另一个Bank,通过中断向量表重映射执行。

升级流程

  1. App收到新固件,写入非活动Bank(如Bank1)。
  2. 写入完成后,设置“升级待验证”标志(存于备份寄存器或Flash)。
  3. 软件复位,进入Bootloader。
  4. Bootloader检查新固件CRC/签名,若通过则交换Bank并启动新App;否则回滚到旧Bank。
  5. 新App启动后,喂狗并清除标志,若App崩溃,看门狗复位,Bootloader再次回滚。

4. 关键配置步骤

4.1 使能IWDG并配置超时

void IWDG_Config(void) {
    // 使能IWDG(LSI时钟约32kHz)
    IWDG->KR = 0x5555; // 解锁
    IWDG->PR = 0x06;   // 分频256,得到125Hz
    IWDG->RLR = 1250;  // 重载值1250,超时10秒
    IWDG->KR = 0xAAAA; // 喂狗
    IWDG->KR = 0xCCCC; // 启动
}

4.2 双Bank切换(选项字节操作)

void Flash_SwapBank(void) {
    FLASH_OptKeyPrg(0x45670123, 0xCDEF89AB); // 解锁选项字节
    FLASH->OPTSR_CUR |= FLASH_OPTSR_SWAP_BANK; // 设置交换位
    FLASH_OptKeyPrg(0x45670123, 0xCDEF89AB); // 重新上锁
    NVIC_SystemReset(); // 复位生效
}

4.3 升级期间暂停喂狗

在App中,当开始写入新固件时,停止喂狗(或延长超时),但需确保写入操作能在看门狗超时前完成。若写入时间过长,可分段喂狗,但一旦进入校验阶段,必须停止喂狗以允许复位。

void OTA_Start(void) {
    // 暂停喂狗:不再调用IWDG_Refresh()
    // 写入新固件到非活动Bank
    Flash_WriteBank(BANK1, new_fw, size);
    // 设置待验证标志
    BackupReg_Write(0x5A5A);
    NVIC_SystemReset(); // 进入Bootloader
}

5. 完整代码示例(Bootloader部分)

#include "stm32h7xx.h"

#define APP_BANK0_ADDR  0x08000000
#define APP_BANK1_ADDR  0x08100000
#define VALID_FLAG      0xA5A5A5A5

void IWDG_Config(void) {
    IWDG->KR = 0x5555;
    IWDG->PR = 0x06;
    IWDG->RLR = 1250;
    IWDG->KR = 0xAAAA;
    IWDG->KR = 0xCCCC;
}

void IWDG_Refresh(void) {
    IWDG->KR = 0xAAAA;
}

uint32_t Check_FW_CRC(uint32_t addr, uint32_t size) {
    // 简化:计算CRC32,这里省略实现
    return 0x12345678; // 假设校验通过
}

void Jump_To_App(uint32_t addr) {
    uint32_t msp = *(volatile uint32_t*)addr;
    void (*app_main)(void) = (void (*)(void))(*(volatile uint32_t*)(addr+4));
    __set_MSP(msp);
    app_main();
}

int main(void) {
    IWDG_Config();
    uint32_t active_bank = (FLASH->OPTSR_CUR & FLASH_OPTSR_SWAP_BANK) ? 1 : 0;
    uint32_t new_bank = (active_bank == 0) ? 1 : 0;
    uint32_t new_addr = (new_bank == 0) ? APP_BANK0_ADDR : APP_BANK1_ADDR;

    // 检查升级标志
    if (BackupReg_Read() == 0x5A5A) {
        // 验证新固件
        if (Check_FW_CRC(new_addr, 0x100000) == VALID_FLAG) {
            // 交换Bank
            FLASH_OptKeyPrg(0x45670123, 0xCDEF89AB);
            FLASH->OPTSR_CUR |= FLASH_OPTSR_SWAP_BANK;
            FLASH_OptKeyPrg(0x45670123, 0xCDEF89AB);
            NVIC_SystemReset();
        } else {
            // 校验失败,回滚:清除标志,启动旧App
            BackupReg_Write(0);
            // 不交换Bank,直接启动当前Bank
            Jump_To_App((active_bank == 0) ? APP_BANK0_ADDR : APP_BANK1_ADDR);
        }
    } else {
        // 正常启动当前App
        Jump_To_App((active_bank == 0) ? APP_BANK0_ADDR : APP_BANK1_ADDR);
    }

    while(1) {
        IWDG_Refresh(); // 喂狗,防止Bootloader卡死
    }
}

6. 注意事项与陷阱

  • 看门狗超时设置:必须大于固件写入和校验的最坏情况时间,否则升级中途复位。建议超时10~30秒,并在写入时每写一页喂一次狗。
  • 选项字节操作:修改SWAP_BANK位前,必须先解锁选项字节,且操作后需要复位才生效。注意,选项字节编程会擦除整个扇区,务必确保电源稳定。
  • 中断向量表重映射:App中必须设置SCB->VTOR为当前Bank的起始地址,否则中断会跳转到错误位置。
  • 备份寄存器:用于存储升级标志,但备份寄存器在VDD掉电时会丢失。若需持久化,可写入Flash专用区域。
  • 回滚策略:建议在App启动后延迟一段时间(如5秒)再清除升级标志,若App崩溃,看门狗复位后Bootloader仍能回滚。
  • 双Bank大小:确保两个Bank容量一致,且固件大小不超过单个Bank。

7. 总结

通过硬件看门狗与双Bank Flash联动,STM32H7实现了升级失败的自愈能力:写入阶段看门狗兜底,校验阶段复位触发回滚,App运行阶段崩溃也能恢复。这套设计极大提升了OTA的可靠性,适用于电力、医疗、汽车等对稳定性要求极高的场景。实际项目中,还需结合CRC校验、签名验证和日志记录,构建完整的升级安全体系。