# 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)关键部分:** ```c FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K ``` **APP 的链接脚本(.ld)关键部分:** ```c 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 完整跳转代码示例 ```c // 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.c` 的 `VECT_TAB_OFFSET` 设置为 `0x100000`(即 Bank2 偏移),并在跳转前正确设置 `SCB->VTOR`。 ```c // APP 工程中设置向量表偏移 #define VECT_TAB_OFFSET 0x100000U /*!< Vector Table base offset field */ ``` #### 陷阱2:跳转后系统时钟异常 **现象**:APP 运行但外设工作不正常,如串口乱码。 **原因**:Bootloader 配置了时钟,APP 启动时重新初始化时钟,但若 Bootloader 中启用了看门狗或低功耗模式,可能导致 APP 初始化失败。 **解决**:跳转前关闭所有外设时钟(`__HAL_RCC_DEINIT()`),并复位时钟配置。 ```c // 跳转前复位时钟 HAL_RCC_DeInit(); ``` #### 陷阱3:双 Bank 交换后跳转地址错误 **现象**:使用 SWAP 功能后,APP 无法启动。 **原因**:SWAP 操作后,Bank2 映射到 `0x08000000`,但 APP 的链接脚本仍指向 `0x08100000`,导致跳转地址错误。 **解决**:若使用 SWAP,APP 应始终链接到 `0x08000000`(即 Bank1 的地址),但实际存储在 Bank2。Bootloader 跳转时,需根据当前映射状态决定跳转地址。 ```c // 检查当前映射状态(通过选项字节) 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 字节。 ```c .isr_vector : { . = ALIGN(256); KEEP(*(.isr_vector)) . = ALIGN(256); } >FLASH ``` ## 五、完整 Bootloader 升级流程示例 以下是一个简化的 Bootloader 主程序,演示了接收固件、写入 Bank2、校验并跳转的完整流程。 ```c // 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 系统。