STM32H7双Bank Flash在线升级失败后自动回滚的启动流程设计

一、为什么需要双Bank Flash?

在传统的单Bank Flash方案中,升级过程中一旦发生断电、写入错误或校验失败,设备将因固件损坏而无法启动,即“变砖”。STM32H7系列(如H743、H750)提供双Bank Flash,将Flash划分为两个独立的Bank(Bank1和Bank2),每个Bank可独立擦写。通过硬件支持的Bank切换机制,系统可以在两个固件镜像之间无缝切换,从而在升级失败时自动回滚到上一个稳定版本。

二、双Bank启动原理

2.1 Flash布局

STM32H7的Flash通常为1MB或2MB,分为两个Bank,每个Bank大小相等。例如,H743的1MB Flash分为Bank1(0x08000000-0x0807FFFF)和Bank2(0x08080000-0x080FFFFF)。每个Bank可独立存放一个完整的固件镜像。

2.2 硬件交换机制

STM32H7的Flash控制器(FLASH)提供FLASH_CR寄存器中的BKSWAP位。当该位被置1时,硬件会将Bank1和Bank2的地址映射互换。这意味着:

  • 物理Bank1的地址变为0x08080000,物理Bank2的地址变为0x08000000。
  • CPU从0x08000000启动时,实际执行的是物理Bank2中的代码。

这一机制使得启动时无需复制代码,只需切换映射即可选择运行哪个Bank的固件。

2.3 启动流程设计

设计目标:

  • 正常启动:从当前活动Bank启动。
  • 升级过程:将新固件写入非活动Bank,校验成功后切换活动Bank。
  • 升级失败:启动时检测到固件无效,自动切换到另一个Bank(回滚)。

具体流程如下:

  1. 上电启动:CPU从0x08000000读取向量表,执行启动代码(Bootloader)。
  2. 检查标志:Bootloader读取备份寄存器(如RTC备份寄存器)或Flash特定区域,获取当前活动Bank信息及升级状态。
  3. 验证固件:对活动Bank中的固件进行CRC或SHA校验。若校验通过,跳转到该Bank的应用程序;若失败,则切换Bank并再次校验。
  4. 升级流程:应用程序收到新固件后,将其写入非活动Bank,写入完成后设置“待切换”标志,并触发软复位。
  5. 切换确认:Bootloader检测到“待切换”标志,执行Bank切换(置位BKSWAP),并清除标志。若新固件启动后自检失败,可再次切换回旧Bank。

三、配置步骤

3.1 内存布局配置

在STM32CubeIDE中,为Bootloader和两个应用程序分别设置链接脚本。Bootloader占用Flash起始区域(如0x08000000,大小32KB),应用程序A位于Bank1(0x08008000),应用程序B位于Bank2(0x08088000)。注意:当BKSWAP置位后,Bank2的物理地址映射到0x08000000,因此应用程序B的链接地址应设为0x08000000(但实际物理地址是0x08080000)。

3.2 使能双Bank模式

STM32H7的Flash默认是单Bank模式,需通过选项字节或运行时配置切换为双Bank。在系统初始化时,调用以下代码:

void Flash_EnableDualBank(void) {
    FLASH_OBProgramInitTypeDef ob;
    HAL_FLASHEx_OBGetConfig(&ob);
    if (ob.Banks != FLASH_BANK_BOTH) {
        ob.Banks = FLASH_BANK_BOTH;
        HAL_FLASH_Unlock();
        HAL_FLASHEx_OBProgram(&ob);
        HAL_FLASH_Lock();
        // 需要复位才能生效
        NVIC_SystemReset();
    }
}

3.3 Bootloader启动代码

Bootloader的核心逻辑如下:

#define APP_A_ADDR   0x08008000  // Bank1 应用程序A
#define APP_B_ADDR   0x08088000  // Bank2 应用程序B(物理地址)
#define BACKUP_REG   RTC->BKP0R   // 使用备份寄存器存储状态

void JumpToApp(uint32_t app_addr) {
    uint32_t msp_val = *(volatile uint32_t*)app_addr;
    if ((msp_val & 0xFFF00000) != 0x20000000) return; // 栈顶地址无效
    void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
    __set_MSP(msp_val);
    app_reset();
}

int main(void) {
    HAL_Init();
    // 读取状态标志
    uint32_t state = BACKUP_REG;
    uint32_t active_bank = (state & 0x1) ? 2 : 1; // 1表示Bank1,2表示Bank2
    uint32_t app_addr = (active_bank == 1) ? APP_A_ADDR : APP_B_ADDR;

    // 校验当前Bank固件
    if (CRC_Check(app_addr) == OK) {
        JumpToApp(app_addr);
    } else {
        // 校验失败,切换Bank并尝试
        if (active_bank == 1) {
            // 切换到Bank2
            FLASH->CR |= FLASH_CR_BKSWAP;
            app_addr = APP_B_ADDR;
        } else {
            FLASH->CR &= ~FLASH_CR_BKSWAP;
            app_addr = APP_A_ADDR;
        }
        if (CRC_Check(app_addr) == OK) {
            BACKUP_REG = (active_bank == 1) ? 0 : 1; // 更新状态
            JumpToApp(app_addr);
        } else {
            // 双Bank均失败,进入错误处理
            Error_Handler();
        }
    }
}

3.4 应用程序升级代码

在应用程序中,接收新固件并写入非活动Bank:

void OTA_Update(uint8_t *data, uint32_t len) {
    uint32_t target_bank = (GetActiveBank() == 1) ? 2 : 1;
    uint32_t target_addr = (target_bank == 1) ? APP_A_ADDR : APP_B_ADDR;

    // 擦除目标Bank
    FLASH_EraseInitTypeDef erase;
    erase.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase.Banks = (target_bank == 1) ? FLASH_BANK_1 : FLASH_BANK_2;
    erase.Sectors = 0;
    erase.NbSectors = 8;
    erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    HAL_FLASHEx_Erase(&erase, &error);

    // 写入数据
    HAL_FLASH_Unlock();
    for (uint32_t i = 0; i < len; i += 8) {
        uint64_t val = *(uint64_t*)(data + i);
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, target_addr + i, val);
    }
    HAL_FLASH_Lock();

    // 设置切换标志并复位
    BACKUP_REG = (target_bank == 1) ? 0 : 1;
    NVIC_SystemReset();
}

四、注意事项

  • 向量表重定位:应用程序编译时需将向量表偏移到对应Bank的起始地址(通过VECT_TAB_OFFSET宏),并在启动时调用SCB->VTOR = APP_ADDR
  • CRC校验:建议使用硬件CRC单元或软件CRC32,在固件末尾附加校验值,Bootloader在跳转前校验。
  • 备份寄存器初始化:确保RTC备份域已使能,且备份寄存器在复位后不丢失(需VBAT供电)。
  • Bank切换时序:BKSWAP操作必须在无Flash访问时进行,建议在复位后立即执行,避免中断干扰。
  • 调试注意:使用ST-Link调试时,若切换了Bank,调试器可能无法识别,需手动复位或使用ST-LINK Utility设置。
  • 双Bank容量:确保每个Bank足够容纳固件,否则需压缩或分块传输。

五、总结

通过STM32H7的双Bank Flash和硬件交换机制,我们可以实现高效的OTA升级回滚方案。Bootloader负责校验和切换,应用程序负责接收和写入新固件,整个过程无需外部存储,且回滚速度极快。该设计显著提升了设备的可靠性和用户体验,是工业级产品的理想选择。