# STM32H7 双 Bank 模式下在线固件升级的地址映射陷阱与规避 ## 引言 在嵌入式系统中,在线固件升级(OTA)是提升产品可维护性的关键功能。STM32H7 系列凭借其双 Bank Flash 架构,支持在运行一个 Bank 的同时擦写另一个 Bank,从而实现无缝升级。然而,双 Bank 模式下的地址映射机制复杂,稍有不慎便会导致升级失败、程序跑飞甚至变砖。本文基于 STM32H743 平台,深入剖析双 Bank 模式下的地址映射陷阱,并提供实用的规避策略。 ## 双 Bank 模式基础 STM32H7 的 Flash 容量通常为 1MB 或 2MB,被划分为两个 Bank:Bank1 和 Bank2。每个 Bank 可独立擦写,且支持两种映射模式: - **单 Bank 模式**:两个 Bank 连续编址,例如 0x08000000-0x080FFFFF(1MB),0x08100000-0x081FFFFF(1MB)。 - **双 Bank 模式**:两个 Bank 交替映射到低地址区域,例如 0x08000000 对应 Bank1 的扇区 0,0x08020000 对应 Bank2 的扇区 0,以此类推。这种模式使得 CPU 可以在 Bank1 运行时,对 Bank2 进行擦写操作。 双 Bank 模式通过选项字节 `FLASH_OPTCR` 中的 `DBANK` 位配置。默认情况下,STM32H7 处于单 Bank 模式,需在烧录选项字节时启用双 Bank。 ## 地址映射陷阱 ### 陷阱一:Bank 切换后的地址错乱 在双 Bank 模式下,Flash 的物理地址与逻辑地址不再一一对应。例如,Bank1 的扇区 0 在逻辑地址 0x08000000,而 Bank2 的扇区 0 在逻辑地址 0x08020000(假设每个扇区 128KB)。如果固件代码中直接使用物理地址访问 Flash,升级后可能访问到错误的 Bank。 **规避**:始终使用逻辑地址进行 Flash 读写,并通过 `FLASH_Bank1` 或 `FLASH_Bank2` 枚举指定目标 Bank,避免硬编码地址。 ### 陷阱二:中断向量表未重定位 升级后,新固件可能位于 Bank2,但 CPU 默认从 0x08000000 读取中断向量表。若未重定位向量表,中断将跳转到旧固件的处理函数,导致系统崩溃。 **规避**:在启动代码中,根据当前运行的 Bank 动态设置 `SCB->VTOR` 寄存器。例如,若固件运行在 Bank2,则设置 `SCB->VTOR = 0x08020000`。 ### 陷阱三:Flash 重映射(Remap)的误区 STM32H7 支持通过 `FLASH_CR` 寄存器的 `REMAP` 位,将 Bank2 重映射到地址 0x08000000,从而让 CPU 从 Bank2 启动。但此操作必须在 Bank1 完全停止运行后进行,否则会导致总线冲突。 **规避**:在跳转到新固件前,先关闭所有中断,确保无 Flash 访问,再设置重映射位,并执行系统复位。 ## 配置步骤 ### 1. 启用双 Bank 模式 在烧录选项字节时,设置 `DBANK=1`。可使用 STM32CubeProgrammer 或代码中调用 HAL 库函数: ```c HAL_FLASH_Unlock(); FLASH_OB_Program(OB_DBANK_MODE, 0); // 0 表示双 Bank HAL_FLASH_Lock(); // 需复位生效 ``` ### 2. 设计 Bootloader 与 App 分区 建议将 Bootloader 放在 Bank1 起始区域,App1 放在 Bank1 剩余空间,App2 放在 Bank2。例如: - Bootloader: 0x08000000 - 0x0801FFFF (128KB) - App1: 0x08020000 - 0x080FFFFF (896KB) - App2: 0x08100000 - 0x081FFFFF (1MB) 注意:双 Bank 模式下,Bank2 的逻辑地址可能被重映射,因此 App2 的链接地址需根据实际映射调整。 ### 3. Bootloader 跳转逻辑 Bootloader 根据标志位决定跳转到 App1 或 App2。跳转前需重定位向量表: ```c void jump_to_app(uint32_t app_addr) { // 关闭全局中断 __disable_irq(); // 设置向量表偏移 SCB->VTOR = app_addr; // 获取栈顶指针和复位向量 uint32_t stack_top = *(volatile uint32_t*)app_addr; void (*reset_handler)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4)); // 设置 MSP 并跳转 __set_MSP(stack_top); reset_handler(); } ``` ### 4. App 固件中的向量表重定位 App 固件在启动时需再次确认向量表位置,防止 Bootloader 跳转后未正确设置: ```c // 在 main 函数开头 if ((*(volatile uint32_t*)0x08100000) != 0xFFFFFFFF) { SCB->VTOR = 0x08100000; // 若运行在 Bank2 } else { SCB->VTOR = 0x08020000; // 若运行在 Bank1 的 App1 } ``` ## 完整代码示例 以下是一个简化的 Bootloader 跳转示例,包含 Bank 切换和向量表重定位: ```c #include "stm32h7xx_hal.h" #define APP1_ADDR 0x08020000 #define APP2_ADDR 0x08100000 void jump_to_app(uint32_t app_addr) { __disable_irq(); SCB->VTOR = app_addr; uint32_t stack_top = *(volatile uint32_t*)app_addr; void (*reset_handler)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4)); __set_MSP(stack_top); reset_handler(); } int main(void) { HAL_Init(); // 检查升级标志,假设在备份寄存器中 if (__HAL_RCC_GET_FLAG(RCC_FLAG_BORRST) == RESET) { // 默认跳转 App1 jump_to_app(APP1_ADDR); } else { // 跳转 App2 jump_to_app(APP2_ADDR); } while (1); } ``` App 固件中,需在启动文件或 main 开头重定位向量表: ```c // 在 main 函数最前面 void SystemInit(void) { // 检查当前运行地址 if ((uint32_t)&main > 0x08100000) { SCB->VTOR = 0x08100000; } else { SCB->VTOR = 0x08020000; } } ``` ## 注意事项 - **Flash 擦写时序**:在双 Bank 模式下,擦写 Bank2 时,Bank1 仍可执行代码,但需确保中断服务函数不访问 Flash,否则会触发总线错误。 - **选项字节修改**:修改 `DBANK` 位后必须复位才能生效,且复位后 Flash 映射会改变,需重新调整链接脚本。 - **链接脚本**:App2 的链接脚本中,`FLASH` 起始地址应设为 0x08100000,但若启用了重映射,实际运行时地址可能变为 0x08000000,需在代码中处理。 - **调试技巧**:使用 ST-Link 的 `set var` 命令查看 `SCB->VTOR` 值,确认向量表是否正确。 ## 结语 STM32H7 双 Bank 模式为 OTA 提供了强大支持,但地址映射的复杂性要求开发者深入理解硬件机制。通过合理规划分区、正确重定位向量表、谨慎使用重映射,可有效规避陷阱,实现稳定可靠的在线升级。希望本文能帮助你在实际项目中少走弯路。