STM32H7双Bank Flash在线升级失败自动回滚:硬件看门狗联动策略深度解析

一、为什么需要双Bank Flash与看门狗联动?

在嵌入式OTA升级场景中,最令人头疼的问题莫过于升级过程中断电、通信中断或新固件本身存在缺陷,导致设备无法启动。传统单Bank方案通常依赖Bootloader+App分区,但升级过程中若App区被破坏,系统将陷入死循环。

STM32H7系列(如H743、H750)内置双Bank Flash(每个Bank独立,可分别擦写),配合硬件看门狗(IWDG),可实现原子级固件切换

  • 双Bank特性:两个Bank可独立执行,支持从任一Bank启动。
  • IWDG特性:一旦启用,若主程序未及时喂狗,系统将强制复位,且不受软件干扰。

联动策略的核心思想:升级新固件时,先写入非活动Bank,校验通过后,利用IWDG触发复位,在Bootloader中切换启动Bank。若新固件运行异常(如死机、崩溃),IWDG将不断复位,Bootloader检测到连续复位后自动回滚至旧Bank。

二、系统架构与升级流程

2.1 内存布局

假设使用H743,Flash起始地址0x08000000,每个Bank大小为1MB(共2MB)。

| 区域 | 地址范围 | 用途 | |------|----------|------| | Bootloader | 0x08000000 - 0x0800FFFF | 启动引导、升级控制 | | Bank0 (App1) | 0x08010000 - 0x080FFFFF | 当前运行固件 | | Bank1 (App2) | 0x08100000 - 0x081FFFFF | 待升级固件或备份 |

2.2 升级流程

  1. Bootloader 启动,检查标志位(如备份寄存器或Flash特定地址)。
  2. 若存在待升级固件,则将其写入非活动Bank(如Bank1)。
  3. 写入完成后,设置启动Bank切换标志,并触发软件复位。
  4. 复位后,Bootloader根据标志位从新Bank启动。
  5. 新固件运行,若正常则清除标志位;若异常,IWDG复位,Bootloader检测到连续复位次数超限,自动回滚至旧Bank。

三、硬件看门狗配置详解

IWDG使用独立时钟(LSI),即使主时钟故障也能工作。配置要点:

  • 预分频器:设置分频系数,典型值64,则计数时钟为LSI/64(LSI约32kHz,则约0.5kHz)。
  • 重装载值:决定超时时间,公式:Timeout = (重装载值 / 计数频率) * 1000 ms
  • 使能:一旦写入0x5555到IWDG_KR,再写入0xCCCC,即启动,且不可关闭。

配置代码(HAL库)

void IWDG_Config(void) {
    IWDG_HandleTypeDef hiwdg;
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   // 分频64
    hiwdg.Init.Reload = 4095;                   // 重装载值,超时约8.2秒
    hiwdg.Init.Window = IWDG_WINDOW_DISABLE;
    if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
        Error_Handler();
    }
}

// 喂狗函数,在主循环中周期性调用
void IWDG_Feed(void) {
    HAL_IWDG_Refresh(&hiwdg);
}

四、双Bank Flash切换实现

4.1 关键寄存器与选项字节

  • Flash选项字节:通过修改FLASH_OPTR寄存器中的BOR_LEVDBANK位,可控制双Bank模式。默认H7为双Bank,无需额外配置。
  • 启动Bank选择:通过FLASH_OPTRSWAP_BANK位,置1则从Bank1启动,置0从Bank0启动。修改后需复位生效。

4.2 切换Bank的代码示例

void Flash_SwitchBank(void) {
    FLASH_OBProgramInitTypeDef pOBInit;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    
    // 读取当前选项字节
    HAL_FLASHEx_OBGetConfig(&pOBInit);
    
    // 切换Bank:若当前从Bank0启动,则改为Bank1,反之亦然
    if (pOBInit.USERConfig & FLASH_OPTR_SWAP_BANK) {
        pOBInit.USERConfig &= ~FLASH_OPTR_SWAP_BANK;  // 清除位,回Bank0
    } else {
        pOBInit.USERConfig |= FLASH_OPTR_SWAP_BANK;   // 置位,切到Bank1
    }
    
    HAL_FLASHEx_OBProgram(&pOBInit);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
    
    // 触发系统复位,使选项字节生效
    NVIC_SystemReset();
}

五、完整回滚策略代码示例

5.1 Bootloader中的核心逻辑

#define APP1_START_ADDR  0x08010000
#define APP2_START_ADDR  0x08100000
#define BOOT_FLAG_ADDR   0x0800FF00  // 存放升级标志和复位计数

// 检查复位原因,判断是否因看门狗复位
uint8_t CheckResetCause(void) {
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) {
        __HAL_RCC_CLEAR_RESET_FLAGS();
        return 1;  // IWDG复位
    }
    return 0;
}

// 读取复位计数,若连续超过3次,则回滚
void CheckAndRollback(void) {
    uint32_t reset_cnt = *(volatile uint32_t*)BOOT_FLAG_ADDR;
    uint8_t is_iwdg_reset = CheckResetCause();
    
    if (is_iwdg_reset) {
        reset_cnt++;
        // 写入新计数(需先擦除标志区)
        FLASH_EraseSector(BOOT_FLAG_ADDR);
        FLASH_WriteWord(BOOT_FLAG_ADDR, reset_cnt);
        
        if (reset_cnt >= 3) {
            // 回滚:切换Bank到旧版本
            Flash_SwitchBank();  // 内部会复位
        }
    } else {
        // 正常启动,清零计数
        FLASH_EraseSector(BOOT_FLAG_ADDR);
        FLASH_WriteWord(BOOT_FLAG_ADDR, 0);
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    IWDG_Config();
    
    CheckAndRollback();
    
    // 根据当前启动Bank跳转至对应App
    if (READ_BIT(FLASH->OPTR, FLASH_OPTR_SWAP_BANK)) {
        JumpToApp(APP2_START_ADDR);
    } else {
        JumpToApp(APP1_START_ADDR);
    }
    
    while(1) { IWDG_Feed(); }
}

5.2 App端升级触发与喂狗

// App中接收新固件,写入非活动Bank
void OTA_Update(uint8_t* data, uint32_t len) {
    // 1. 擦除目标Bank(例如当前从Bank0运行,则擦除Bank1)
    // 2. 写入数据
    // 3. 校验CRC
    // 4. 设置升级标志(如写入0xA5A5到BOOT_FLAG_ADDR)
    // 5. 调用Flash_SwitchBank() 复位
}

// 主循环中周期性喂狗
while(1) {
    IWDG_Feed();
    // 其他任务
}

六、关键注意事项

  • 喂狗时机:IWDG超时时间需大于主循环最坏执行时间,但不宜过长(建议2~10秒),否则无法及时检测死机。
  • 标志区可靠性:存放复位计数的Flash区域需具备擦写寿命,建议使用独立扇区,并采用磨损均衡(如轮换地址)。
  • 选项字节编程:修改SWAP_BANK位时,必须确保Flash已解锁,且编程后需复位,期间不可断电,否则可能损坏选项字节。
  • App启动后清标志:新固件首次启动时,应主动清除升级标志,避免Bootloader误判。
  • 回滚条件:连续复位次数阈值不宜过小(如3次),防止正常启动时偶发复位导致误回滚。
  • 双Bank一致性:确保两个Bank的固件版本兼容,避免回滚后因外设配置不匹配而异常。

七、总结

通过STM32H7的双Bank Flash与IWDG联动,我们构建了一个健壮的OTA升级回滚机制。核心在于利用IWDG的不可屏蔽性,将系统异常转化为可检测的复位事件,再通过Bootloader中的计数逻辑实现自动回滚。此方案无需外部存储,成本低且可靠性高,适用于工业控制、智能仪表等对稳定性要求严苛的场景。实际项目中,还需结合CRC校验、固件签名等安全措施,构建完整的升级闭环。