STM32H7 双 Bank 模式下在线固件升级:地址映射与跳转陷阱深度解析

一、双 Bank 模式概述

STM32H7 系列(如 STM32H743、H750)内置双 Bank Flash,每个 Bank 容量相等(如 1MB+1MB)。双 Bank 模式允许在运行一个 Bank 中的程序时,对另一个 Bank 进行擦写操作,从而实现无缝的固件升级。这种架构天然支持 A/B 分区,即 Bootloader 和 APP 可分别位于不同 Bank,或两个 APP 互为备份。

关键特性:

  • 双 Bank 可独立擦写,支持同时读写(RWW)
  • 支持硬件自动切换 Bank 启动(通过选项字节)
  • 每个 Bank 拥有独立的地址空间,但映射方式受选项字节控制

二、地址映射与启动机制

2.1 默认映射(单 Bank 模式)

默认情况下,STM32H7 的 Flash 从 0x08000000 开始连续映射,Bank1 和 Bank2 依次排列。例如,1MB+1MB 的芯片,Bank1 地址为 0x08000000-0x080FFFFF,Bank2 为 0x08100000-0x081FFFFF

2.2 双 Bank 映射(SWAP 模式)

通过设置选项字节 nSWAP_BANK,可以交换 Bank1 和 Bank2 的映射地址。当 SWAP_BANK=1 时,Bank2 被映射到 0x08000000,而 Bank1 被映射到 0x08100000。这种机制允许 Bootloader 在 Bank1 中运行,同时将新固件写入 Bank2,然后通过软件触发交换,使新固件在下次复位后从 0x08000000 启动。

地址映射示意:

默认模式:
0x08000000 - 0x080FFFFF : Bank1 (Bootloader)
0x08100000 - 0x081FFFFF : Bank2 (APP)

SWAP模式:
0x08000000 - 0x080FFFFF : Bank2 (APP)
0x08100000 - 0x081FFFFF : Bank1 (Bootloader)

三、Bootloader 与 APP 的地址规划

3.1 典型分区方案

  • Bootloader:位于 Bank1 起始地址 0x08000000,大小 32KB(0x08000000 - 0x08007FFF
  • APP:位于 Bank2 起始地址 0x08100000(默认映射),或通过 SWAP 后映射到 0x08000000

3.2 链接脚本配置

Bootloader 的链接脚本(.ld)关键部分:

FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K

APP 的链接脚本(.ld)关键部分:

FLASH (rx) : ORIGIN = 0x08100000, LENGTH = 1M - 32K
RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K

注意:APP 的起始地址必须与 Bank2 的基地址对齐,且中断向量表需重定位。

四、跳转实现与陷阱规避

4.1 跳转原理

Bootloader 在收到完整固件并校验通过后,需要跳转到 APP 执行。跳转的核心步骤:

  1. 关闭全局中断
  2. 设置主栈指针(MSP)为 APP 的初始栈顶值
  3. 设置向量表偏移寄存器(VTOR)指向 APP 的中断向量表
  4. 跳转到 APP 的复位处理函数(Reset_Handler)

4.2 完整跳转代码示例

// Bootloader 跳转函数
void jump_to_app(uint32_t app_addr)
{
    // 检查 APP 栈顶地址是否合法(RAM 范围内)
    uint32_t app_stack = *(volatile uint32_t *)app_addr;
    if ((app_stack & 0xFFF00000) != 0x20000000)
    {
        return; // 非法地址,拒绝跳转
    }

    // 关闭全局中断
    __disable_irq();

    // 关闭所有外设中断(可选,根据实际外设情况)
    for (int i = 0; i < 8; i++)
    {
        NVIC->ICER[i] = 0xFFFFFFFF;
        NVIC->ICPR[i] = 0xFFFFFFFF;
    }

    // 设置主栈指针
    __set_MSP(app_stack);

    // 设置向量表偏移(仅适用于 Cortex-M7)
    SCB->VTOR = app_addr;

    // 获取复位处理函数地址
    uint32_t reset_handler = *(volatile uint32_t *)(app_addr + 4);

    // 跳转
    void (*jump_func)(void) = (void (*)(void))reset_handler;
    jump_func();

    // 不会执行到这里
    while (1);
}

4.3 常见陷阱与解决方案

陷阱1:VTOR 设置无效

现象:跳转后中断无法触发或死机。

原因:STM32H7 的 VTOR 要求偏移量按 256 字节对齐(VTOR_TBLBASE 位域),且 APP 的链接脚本中 VECT_TAB_OFFSET 必须与 Flash 地址偏移一致。

解决:确保 APP 工程中 system_stm32h7xx.cVECT_TAB_OFFSET 设置为 0x100000(即 Bank2 偏移),并在跳转前正确设置 SCB->VTOR

// APP 工程中设置向量表偏移
#define VECT_TAB_OFFSET  0x100000U /*!< Vector Table base offset field */

陷阱2:跳转后系统时钟异常

现象:APP 运行但外设工作不正常,如串口乱码。

原因:Bootloader 配置了时钟,APP 启动时重新初始化时钟,但若 Bootloader 中启用了看门狗或低功耗模式,可能导致 APP 初始化失败。

解决:跳转前关闭所有外设时钟(__HAL_RCC_DEINIT()),并复位时钟配置。

// 跳转前复位时钟
HAL_RCC_DeInit();

陷阱3:双 Bank 交换后跳转地址错误

现象:使用 SWAP 功能后,APP 无法启动。

原因:SWAP 操作后,Bank2 映射到 0x08000000,但 APP 的链接脚本仍指向 0x08100000,导致跳转地址错误。

解决:若使用 SWAP,APP 应始终链接到 0x08000000(即 Bank1 的地址),但实际存储在 Bank2。Bootloader 跳转时,需根据当前映射状态决定跳转地址。

// 检查当前映射状态(通过选项字节)
if (FLASH->OPTSR_CUR & FLASH_OPTSR_SWAP_BANK)
{
    // 已交换,APP 在 0x08000000
    jump_to_app(0x08000000);
}
else
{
    // 未交换,APP 在 0x08100000
    jump_to_app(0x08100000);
}

陷阱4:中断向量表对齐问题

现象:跳转后,中断服务函数无法执行。

原因:Cortex-M7 要求向量表地址按 256 字节对齐,且向量表大小需为 2 的幂次。

解决:在 APP 的链接脚本中,确保 .isr_vector 段对齐到 256 字节。

.isr_vector :
{
  . = ALIGN(256);
  KEEP(*(.isr_vector))
  . = ALIGN(256);
} >FLASH

五、完整 Bootloader 升级流程示例

以下是一个简化的 Bootloader 主程序,演示了接收固件、写入 Bank2、校验并跳转的完整流程。

// Bootloader 主程序(简化)
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    UART_Init();

    // 检查是否需要升级(如通过串口命令)
    if (check_update_request())
    {
        // 擦除 Bank2
        erase_bank2();

        // 接收固件并写入 Bank2
        receive_firmware_to_bank2();

        // 校验固件(CRC或SHA)
        if (verify_firmware())
        {
            // 设置 SWAP 位(可选,若使用硬件交换)
            set_swap_bank();

            // 跳转到 APP(根据映射状态)
            if (FLASH->OPTSR_CUR & FLASH_OPTSR_SWAP_BANK)
                jump_to_app(0x08000000);
            else
                jump_to_app(0x08100000);
        }
        else
        {
            // 校验失败,回滚或重试
            error_handler();
        }
    }
    else
    {
        // 直接跳转到现有 APP
        jump_to_app(0x08100000);
    }

    while (1);
}

六、注意事项与最佳实践

  • 固件校验:务必在跳转前进行完整性校验(如 CRC32),避免执行损坏的固件。
  • 看门狗处理:跳转前关闭看门狗,或在 APP 中尽快重新初始化。
  • 外设状态:跳转前将所有外设恢复到默认状态,避免 APP 初始化冲突。
  • 调试接口:在跳转后,调试器可能无法正常工作,建议在 APP 中重新配置调试端口。
  • 双 Bank 交换:使用 SWAP 时,需确保 APP 链接地址与映射后的地址一致,否则会跳转失败。

七、总结

STM32H7 双 Bank 模式为 OTA 提供了强大的硬件支持,但地址映射和跳转逻辑是其中的核心难点。理解默认映射与 SWAP 映射的区别,正确配置链接脚本和向量表偏移,并规避常见陷阱,才能实现稳定可靠的在线升级。希望本文的剖析和代码示例能帮助你少走弯路,构建出健壮的 Bootloader 系统。