STM32H7双Bank模式下的固件升级失败自动回滚机制实现

1. 为什么需要自动回滚?

在OTA(空中升级)或现场升级中,固件写入过程可能因断电、通信错误或校验失败而中断。若新固件损坏,设备将无法启动。传统单Bank方案只能依赖外部备份或人工干预,而STM32H7的双Bank闪存允许同时存储两个固件副本,配合硬件支持的切换机制,可实现无缝回滚,极大提升系统可靠性。

2. 双Bank模式原理

STM32H7系列(如H743、H750)内置双Bank闪存,每个Bank容量相同(如1MB时每个512KB)。通过配置选项字节(Option Bytes)中的DBANK位,可切换为双Bank模式。此时,闪存地址空间被划分为两个独立区域:

  • Bank 1:通常存放当前运行固件(Active)
  • Bank 2:存放新固件(Pending)

系统复位后,硬件根据BOOT_ADD0BOOT_ADD1选项字节决定从哪个Bank启动。若启动失败(如看门狗复位),可通过软件切换启动地址,实现回滚。

3. 实现方案设计

3.1 整体流程

  1. 设备运行在Bank 1,接收新固件并写入Bank 2。
  2. 写入完成后,设置标志位(如备份寄存器或Flash特定区域),标记新固件待验证。
  3. 切换启动地址到Bank 2并软复位。
  4. 若Bank 2固件运行正常(如应用层上报心跳),则清除标志位,完成升级。
  5. 若Bank 2启动失败(如看门狗超时),系统自动回滚到Bank 1。

3.2 关键配置

  • 选项字节设置:使用STM32CubeProgrammer或代码配置DBANK=1,并设置BOOT_ADD0BOOT_ADD1
  • 看门狗:启用独立看门狗(IWDG),在启动早期喂狗,若固件异常则触发复位。
  • 标志位存储:使用备份寄存器(BKP)或Flash末尾扇区存储升级状态。

4. 代码实现

以下代码基于STM32H743,使用HAL库,演示核心逻辑。

4.1 配置双Bank模式(初始化代码)

/* 启用双Bank模式 */
void Flash_EnableDualBank(void)
{
    FLASH_OBProgramInitTypeDef ob;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    
    ob.OptionType = OPTIONBYTE_BANK;
    ob.Bank = FLASH_BANK_1; // 配置Bank1选项
    ob.BankConfig = FLASH_BANK_DUAL; // 双Bank模式
    HAL_FLASH_OBProgram(&ob);
    
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
    
    /* 需要重新上电生效 */
    HAL_NVIC_SystemReset();
}

4.2 固件写入Bank 2

/* 将新固件写入Bank2,地址从0x08100000开始(假设Bank2基址) */
void Write_FirmwareToBank2(uint32_t *data, uint32_t len)
{
    uint32_t addr = 0x08100000;
    HAL_FLASH_Unlock();
    
    for (uint32_t i = 0; i < len; i += 4) {
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, *(uint64_t*)(data+i)) != HAL_OK) {
            // 错误处理
        }
        addr += 8; // 双字写入
    }
    HAL_FLASH_Lock();
}

4.3 切换启动Bank并复位

/* 设置启动地址为Bank2,并软复位 */
void SwitchToBank2AndReset(void)
{
    /* 设置标志位,表示新固件待验证 */
    Set_UpdateFlag(0xAA55); // 写入备份寄存器
    
    /* 修改选项字节BOOT_ADD1为Bank2起始地址 */
    FLASH_OBProgramInitTypeDef ob;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    ob.OptionType = OPTIONBYTE_USER;
    ob.UserType = OB_USER_BOOT_ADD1;
    ob.BOOT_ADD1 = 0x08100000; // Bank2地址
    HAL_FLASH_OBProgram(&ob);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
    
    HAL_NVIC_SystemReset();
}

4.4 启动时检查回滚条件

void Check_And_Rollback(void)
{
    uint16_t flag = Get_UpdateFlag();
    
    if (flag == 0xAA55) { // 有未验证的更新
        /* 检查当前运行Bank */
        if (__HAL_FLASH_GET_BANK() == FLASH_BANK_2) {
            /* 运行在Bank2,说明新固件已启动,但需验证 */
            if (System_Check() == PASS) {
                /* 验证通过,清除标志 */
                Clear_UpdateFlag();
                // 正常继续运行
            } else {
                /* 验证失败,回滚到Bank1 */
                SwitchToBank1();
            }
        } else {
            /* 运行在Bank1,说明Bank2启动失败,自动回滚 */
            Clear_UpdateFlag();
            // 继续运行Bank1固件
        }
    }
}

void SwitchToBank1(void)
{
    /* 设置BOOT_ADD0为Bank1地址 */
    // 类似SwitchToBank2,但地址改为0x08000000
    HAL_NVIC_SystemReset();
}

4.5 看门狗配合

/* 初始化IWDG,超时1秒 */
void IWDG_Init(void)
{
    IWDG_HandleTypeDef hiwdg;
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
    hiwdg.Init.Reload = 4095; // 约1秒
    HAL_IWDG_Init(&hiwdg);
}

/* 在主循环中喂狗 */
while (1) {
    HAL_IWDG_Refresh(&hiwdg);
    // 应用逻辑
}

5. 注意事项

  • 地址映射:确认具体型号的Bank基址(H743的Bank1为0x08000000,Bank2为0x08100000)。
  • 选项字节修改:修改后需复位生效,且注意保护其他选项字节。
  • 固件大小:确保固件不超过Bank容量,否则需压缩或分块。
  • 验证机制:建议在应用层增加CRC校验或版本检查,避免误判。
  • 看门狗:在启动早期喂狗,防止正常启动被误复位。
  • 中断向量表:若固件从Bank2启动,需重定位中断向量表(SCB->VTOR)。

6. 总结

通过STM32H7的双Bank模式,我们实现了升级失败后的自动回滚,无需外部硬件,仅靠内部闪存和看门狗即可保证设备始终可运行。该方案适用于对可靠性要求高的工业、汽车等嵌入式场景。开发者可根据实际需求调整验证逻辑和回滚策略,构建更健壮的OTA系统。