# STM32F4 双 Bank Flash 在线升级(IAP)的地址映射陷阱与规避 ## 一、双 Bank 模式与地址映射基础 STM32F4 系列(如 STM32F427/437)的 Flash 容量通常为 1MB 或 2MB,支持双 Bank 架构。每个 Bank 独立编址,但可通过选项字节配置为单一 Bank 模式(此时地址连续)或双 Bank 模式(地址交错)。在双 Bank 模式下,Flash 被划分为两个逻辑 Bank: - **Bank 1**:地址范围 `0x08000000` ~ `0x0807FFFF`(1MB 设备) - **Bank 2**:地址范围 `0x08080000` ~ `0x080FFFFF` **关键点**:双 Bank 模式下,CPU 的取指和数据的地址映射是**交错**的,即物理扇区 0 映射到 Bank1 的地址 0,物理扇区 1 映射到 Bank2 的地址 0,以此类推。这意味着,当从 Bank1 跳转到 Bank2 执行代码时,向量表地址必须重新映射,否则中断将无法正确响应。 ## 二、常见陷阱分析 ### 陷阱 1:Bank 切换后向量表错位 在 IAP 升级中,Bootloader 通常位于 Bank1 起始地址,App 位于 Bank2。当 Bootloader 跳转到 App 时,若未正确设置向量表偏移(VTOR),CPU 仍会从 `0x08000000` 读取中断向量,导致执行 `Reset_Handler` 失败或进入 HardFault。 **规避**:在 App 启动代码中,必须设置 `SCB->VTOR = APP_BASE_ADDRESS`,其中 `APP_BASE_ADDRESS` 为 App 在 Bank2 的起始地址(如 `0x08080000`)。 ### 陷阱 2:Flash 擦写操作跨越 Bank 边界 双 Bank 模式下,Flash 擦除操作以扇区为单位,但扇区编号是全局的。若开发者误以为 Bank1 的扇区编号从 0 开始,Bank2 从 1 开始,则可能擦除错误区域。实际上,STM32F4 的扇区编号是连续的(0~11),但物理映射交错。 **规避**:使用 HAL 库的 `HAL_FLASHEx_Erase` 时,必须指定正确的扇区编号和 Bank 选择(`FLASH_BANK_1` 或 `FLASH_BANK_2`)。 ### 陷阱 3:跳转函数未清理中断状态 在跳转前,若未关闭全局中断或未复位外设,可能导致中断在跳转瞬间触发,而向量表尚未切换,造成不可预知行为。 **规避**:跳转前执行 `__disable_irq()`,并调用 `HAL_RCC_DeInit()` 复位时钟,最后设置 MSP 和跳转。 ## 三、双 Bank IAP 实现步骤 ### 1. 配置选项字节启用双 Bank 模式 使用 STM32CubeProgrammer 或代码设置选项字节: ```c // 启用双 Bank 模式(以 STM32F427 为例) HAL_FLASH_Unlock(); __HAL_FLASH_OPTIMOD_ENABLE(); FLASH_OBProgramInitTypeDef ob; ob.OptionType = OPTIONBYTE_BYTE; ob.BYTE = 0x01; // 设置 nDBANK=0,启用双 Bank HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); HAL_FLASH_Lock(); ``` ### 2. Bootloader 跳转函数 ```c #define APP_ADDRESS 0x08080000 // Bank2 起始地址 typedef void (*pFunction)(void); void jump_to_app(void) { uint32_t app_stack = *(volatile uint32_t*)APP_ADDRESS; pFunction app_entry = (pFunction)(*(volatile uint32_t*)(APP_ADDRESS + 4)); // 检查栈顶地址是否合法(RAM 范围) if ((app_stack & 0x2FFE0000) != 0x20000000) { return; // 非法地址 } __disable_irq(); HAL_RCC_DeInit(); SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; // 设置主栈指针并跳转 __set_MSP(app_stack); app_entry(); } ``` ### 3. App 端向量表重映射 在 App 工程的 `system_stm32f4xx.c` 或 `main.c` 中,于 `main` 函数最开头设置: ```c #define APP_BASE 0x08080000 void main(void) { SCB->VTOR = APP_BASE; // 重映射向量表 // 其他初始化... } ``` ### 4. 升级流程(接收固件并写入 Bank2) ```c void write_firmware_to_bank2(uint8_t *data, uint32_t len, uint32_t offset) { FLASH_EraseInitTypeDef erase; uint32_t sector_error = 0; HAL_FLASH_Unlock(); // 计算扇区(Bank2 起始扇区为 8,每个扇区 128KB,但需根据具体型号调整) erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Banks = FLASH_BANK_2; erase.Sector = FLASH_SECTOR_8; // 根据实际偏移计算 erase.NbSectors = 1; erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; if (HAL_FLASHEx_Erase(&erase, §or_error) != HAL_OK) { // 错误处理 } // 写入数据(注意地址对齐) for (uint32_t i = 0; i < len; i += 8) { uint64_t data64 = 0; for (int j = 0; j < 8; j++) { data64 |= ((uint64_t)data[i+j]) << (8*j); } if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, APP_ADDRESS + offset + i, data64) != HAL_OK) { // 错误处理 } } HAL_FLASH_Lock(); } ``` ## 四、完整示例:Bootloader 与 App 协作 ### Bootloader 端(简化) ```c int main(void) { HAL_Init(); // 检查升级标志(如外部 EEPROM 或备份寄存器) if (upgrade_requested()) { receive_firmware(); // 接收并写入 Bank2 clear_upgrade_flag(); } jump_to_app(); } ``` ### App 端(简化) ```c int main(void) { SCB->VTOR = 0x08080000; // 必须设置 HAL_Init(); // 应用逻辑... } ``` ## 五、注意事项 - **地址对齐**:Flash 编程必须 8 字节对齐(双字),否则 HAL 库会报错。 - **扇区大小**:不同型号的 STM32F4 扇区大小不同(如 F427 的扇区 0~3 为 16KB,4 为 64KB,5~11 为 128KB),务必查阅参考手册。 - **中断安全**:跳转前关闭所有中断,并在 App 中重新初始化外设。 - **校验机制**:升级前建议对固件进行 CRC 校验,防止写入损坏数据。 - **调试技巧**:使用 ST-Link 的 `set var` 命令检查 `SCB->VTOR` 值,确认映射正确。 ## 六、总结 双 Bank 模式为 IAP 提供了强大的灵活性,但地址映射的复杂性要求开发者深入理解硬件架构。通过正确设置 VTOR、谨慎处理 Flash 擦写、并遵循安全的跳转流程,可以避免绝大多数陷阱。希望本文能帮助你在 STM32F4 上实现稳定可靠的在线升级功能。