STM32H7双Bank Flash在线升级失败后自动回滚的硬件级保障机制详解

在嵌入式系统OTA升级中,升级失败(如断电、通信中断、校验错误)可能导致设备无法启动,即“变砖”。STM32H7系列凭借其双Bank Flash架构,提供了硬件级的自动回滚保障,无需额外外部电路即可实现安全升级。本文深入剖析双Bank Flash的工作原理、配置步骤及完整代码示例,帮助开发者构建高可靠性的在线升级方案,确保系统在异常情况下能自动恢复至上一版本,大幅提升产品稳定性。

一、双Bank Flash架构概述

STM32H7系列(如H743、H750等)内置的Flash存储器被划分为两个独立的Bank:Bank1和Bank2。每个Bank的容量通常为1MB(具体取决于型号),且支持独立的擦除和编程操作。关键特性包括:

  • 双Bank独立操作:两个Bank可同时执行读/写/擦除操作,但同一时刻只能有一个Bank作为启动区域(通过BOOT位选择)。
  • 硬件自动切换:当系统从Bank1启动时,若Bank1的程序校验失败(如CRC错误),硬件可自动切换至Bank2启动,无需软件干预。
  • 原子性切换:通过设置Flash选项字节中的BOR_LEVBOOT_SW位,可实现启动Bank的硬件级切换,切换过程在复位后立即生效。

这种架构天然支持“双镜像”升级策略:一个Bank运行当前固件,另一个Bank存储新固件。升级时,将新固件写入非活动Bank,完成后切换启动Bank;若新固件无法运行,硬件自动回滚至旧Bank。

二、硬件级自动回滚原理

自动回滚的核心在于STM32H7的启动配置Flash选项字节。具体机制如下:

  1. 启动Bank选择:复位后,CPU根据选项字节中的BOOT_SW位决定从Bank1还是Bank2启动。默认情况下,BOOT_SW=0选择Bank1。
  2. 固件有效性检查:在启动Bank的起始地址(如0x08000000)处,硬件会检查栈顶指针(SP)和复位向量(Reset_Handler)是否合法。若SP值不在RAM范围内或Reset_Handler为0xFFFFFFFF,则视为无效固件,硬件自动切换至另一个Bank启动。
  3. 切换过程:硬件切换通过FLASH_OPTCR寄存器的SWAP_BANK位实现。当检测到启动Bank无效时,硬件自动将SWAP_BANK置1,并触发系统复位,复位后从另一个Bank启动。

因此,开发者只需确保在升级过程中,新固件写入非活动Bank,并在写入完成后设置SWAP_BANK位,即可实现硬件级回滚。若新固件本身损坏(如写入不完整),硬件在下次复位时自动回滚至旧Bank。

三、配置步骤

1. 内存布局规划

假设Flash总容量为2MB(Bank1和Bank2各1MB),规划如下:

  • Bank1:地址0x08000000 ~ 0x080FFFFF,用于当前运行固件(版本A)。
  • Bank2:地址0x08100000 ~ 0x081FFFFF,用于新固件(版本B)。

在编译时,需为两个Bank分别生成独立的固件镜像,并设置不同的链接脚本。

2. 启用双Bank模式

默认情况下,STM32H7可能处于单Bank模式。需通过选项字节启用双Bank模式:

// 启用双Bank模式(在系统初始化时调用)
void Flash_EnableDualBank(void) {
    FLASH_OBProgramInitTypeDef ob;
    HAL_FLASHEx_OBGetConfig(&ob);
    if (ob.Banks != FLASH_BANK_BOTH) {
        ob.Banks = FLASH_BANK_BOTH; // 设置为双Bank
        HAL_FLASH_Unlock();
        HAL_FLASH_OB_Unlock();
        HAL_FLASHEx_OBProgram(&ob);
        HAL_FLASH_OB_Launch(); // 触发选项字节重载
        HAL_FLASH_OB_Lock();
        HAL_FLASH_Lock();
    }
}

3. 固件写入与切换

升级流程如下:

// 将新固件写入Bank2(假设Bank1为当前运行)
void OTA_WriteFirmwareToBank2(uint32_t *data, uint32_t size) {
    uint32_t addr = 0x08100000; // Bank2起始地址
    FLASH_EraseInitTypeDef erase;
    uint32_t sectorError = 0;
    
    HAL_FLASH_Unlock();
    // 擦除Bank2所有扇区
    erase.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase.Banks = FLASH_BANK_2;
    erase.Sector = 0;
    erase.NbSectors = 8; // 假设Bank2有8个扇区
    erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    HAL_FLASHEx_Erase(&erase, &sectorError);
    
    // 编程数据
    for (uint32_t i = 0; i < size; i += 4) {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, data[i/4]);
    }
    HAL_FLASH_Lock();
}

// 切换启动Bank至Bank2
void OTA_SwitchToBank2(void) {
    FLASH_OBProgramInitTypeDef ob;
    HAL_FLASH_OB_Unlock();
    HAL_FLASHEx_OBGetConfig(&ob);
    ob.SWAP_BANK = FLASH_SWAP_BANK_ENABLE; // 使能Bank交换
    HAL_FLASHEx_OBProgram(&ob);
    HAL_FLASH_OB_Launch(); // 触发复位
    // 复位后从Bank2启动
}

4. 自动回滚验证

若Bank2固件损坏,硬件自动回滚。为验证,可在升级后故意破坏Bank2的复位向量,然后复位系统,观察是否回滚至Bank1。

四、完整代码示例

以下是一个简化的OTA升级管理模块,包含写入、校验和切换逻辑:

#include "stm32h7xx_hal.h"

#define BANK1_ADDR 0x08000000
#define BANK2_ADDR 0x08100000
#define APP_SIZE   0x100000 // 1MB

// 检查固件有效性(检查SP和Reset向量)
int IsValidApp(uint32_t addr) {
    uint32_t sp = *(volatile uint32_t*)addr;
    uint32_t reset = *(volatile uint32_t*)(addr + 4);
    if ((sp & 0xFFF00000) != 0x20000000) return 0; // SP必须在RAM区
    if (reset == 0xFFFFFFFF) return 0;
    return 1;
}

// 执行升级
void OTA_PerformUpgrade(uint8_t *newFw, uint32_t size) {
    // 1. 写入Bank2
    OTA_WriteFirmwareToBank2((uint32_t*)newFw, size);
    
    // 2. 校验Bank2固件(可选,增加CRC校验)
    if (!IsValidApp(BANK2_ADDR)) {
        // 写入失败,不切换,继续运行旧固件
        return;
    }
    
    // 3. 切换启动Bank
    OTA_SwitchToBank2();
    // 注意:切换后系统复位,若Bank2无效,硬件自动回滚至Bank1
}

int main(void) {
    HAL_Init();
    // ... 系统时钟配置等
    
    // 启动时检查当前Bank有效性
    if (!IsValidApp(BANK1_ADDR) && !IsValidApp(BANK2_ADDR)) {
        // 两个Bank均无效,进入Bootloader模式
        // 等待串口下载固件
    } else if (!IsValidApp(BANK1_ADDR)) {
        // Bank1无效,但Bank2有效,硬件已自动切换至Bank2,无需处理
    }
    
    // 正常应用运行
    while(1) {
        // 应用逻辑
    }
}

五、注意事项

  • 链接脚本:为Bank1和Bank2分别编译固件时,需调整链接脚本中的FLASH起始地址和长度。例如,Bank1固件使用FLASH = 0x08000000, LENGTH = 1M,Bank2固件使用FLASH = 0x08100000, LENGTH = 1M
  • 中断向量表:每个Bank的固件需在启动时重定位中断向量表至其基地址,使用SCB->VTOR = BANK_ADDR
  • 选项字节操作:修改选项字节需谨慎,操作前务必解锁,并在完成后调用HAL_FLASH_OB_Launch()使更改生效。
  • 升级原子性:写入新固件时,建议先擦除整个Bank,再编程,避免部分写入导致校验失败。
  • 回滚触发条件:硬件回滚仅在启动Bank的SP或Reset向量无效时触发。若新固件能启动但运行异常(如死循环),需软件看门狗配合,在超时后强制复位,从而触发硬件回滚。
  • 性能影响:双Bank模式下,Flash读取速度可能略有下降(因Bank切换),但通常不影响应用性能。

六、总结

STM32H7的双Bank Flash机制为OTA升级提供了强大的硬件级保障。通过合理配置选项字节和利用硬件自动切换,开发者可以轻松实现升级失败后的自动回滚,避免设备变砖。本文提供的原理和代码示例可直接应用于实际项目,显著提升嵌入式系统的可靠性和用户体验。