# STM32 双 Bank 切换实现 OTA 零停机:基于 F4/F7 的 Flash 重映射细节 ## 1. 为什么需要双 Bank? 传统 OTA 方案通常采用 Bootloader + App 模式,升级时需擦写 App 区,期间系统必须停机。对于要求 7x24 小时连续运行的系统(如电力监控、医疗设备),这是不可接受的。STM32 F4/F7 系列(如 STM32F427、STM32F746)提供双 Bank Flash,每个 Bank 容量相等,可独立擦写。通过硬件重映射,CPU 可瞬间从 Bank1 跳转到 Bank2,无需复位,实现零停机升级。 ## 2. 双 Bank 与 Flash 重映射原理 ### 2.1 内存映射基础 以 STM32F746 为例,Flash 总容量 1MB,分为 Bank1(0x08000000-0x0807FFFF)和 Bank2(0x08080000-0x080FFFFF)。默认情况下,CPU 从 Bank1 启动。当启用双 Bank 模式并设置重映射位后,Bank2 的地址会被映射到 0x08000000,而 Bank1 则被映射到 0x08080000。 ### 2.2 关键寄存器 - **FLASH_CR (Flash Control Register)**:位 31 (DBANK) 用于选择单 Bank 或双 Bank 模式(仅部分型号支持,需在空片时设置)。 - **FLASH_CR 位 30 (SWAP_BANK)**:当置 1 时,交换 Bank1 和 Bank2 的映射地址。 - **FLASH_SR (Status Register)**:位 15 (BSY) 表示 Flash 忙,切换前必须等待。 ### 2.3 切换流程 1. 确保当前运行在 Bank1(或 Bank2),且目标 Bank 已写入新固件。 2. 设置 FLASH_CR 的 SWAP_BANK 位。 3. 等待 BSY 位清零。 4. 硬件自动重映射,PC 指针无需改动,但需注意中断向量表位置。 ## 3. 工程配置步骤 ### 3.1 链接脚本调整 需为两个 Bank 分别生成固件,或生成一个包含双 Bank 的镜像。以 STM32F746 为例,修改 `.ld` 文件: ```c /* Bank1 应用 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* Bank2 应用 */ /* FLASH (rx) : ORIGIN = 0x08080000, LENGTH = 512K */ ``` 编译两个版本,分别烧录到对应 Bank。 ### 3.2 中断向量表重定位 切换后,中断向量表仍指向旧地址,必须重定位。在系统初始化时,设置 SCB->VTOR: ```c #define APP_ADDRESS_BANK1 0x08000000 #define APP_ADDRESS_BANK2 0x08080000 void relocate_vector_table(uint32_t app_address) { SCB->VTOR = app_address; __DSB(); __ISB(); } ``` ## 4. 完整代码示例(基于 HAL 库) ### 4.1 双 Bank 切换函数 ```c #include "stm32f7xx_hal.h" #define FLASH_BANK1_BASE 0x08000000 #define FLASH_BANK2_BASE 0x08080000 /** * @brief 切换当前执行 Bank * @param target_bank: 1 或 2 * @retval HAL_StatusTypeDef */ HAL_StatusTypeDef Flash_SwitchBank(uint8_t target_bank) { uint32_t current_bank; uint32_t primask; // 判断当前运行 Bank if ((SCB->VTOR & 0x000FFFFF) < 0x00080000) { current_bank = 1; } else { current_bank = 2; } if (current_bank == target_bank) { return HAL_OK; // 无需切换 } // 关闭全局中断,防止切换期间中断触发 primask = __get_PRIMASK(); __disable_irq(); // 等待 Flash 空闲 while (FLASH->SR & FLASH_SR_BSY); // 设置 SWAP_BANK 位 FLASH->CR |= FLASH_CR_SWAP_BANK; // 等待切换完成 while (FLASH->SR & FLASH_SR_BSY); // 重定位中断向量表到新 Bank if (target_bank == 1) { SCB->VTOR = FLASH_BANK1_BASE; } else { SCB->VTOR = FLASH_BANK2_BASE; } __DSB(); __ISB(); // 恢复中断 __set_PRIMASK(primask); return HAL_OK; } ``` ### 4.2 固件写入函数(简化) ```c HAL_StatusTypeDef Flash_WriteFirmware(uint32_t dest_addr, uint8_t *data, uint32_t len) { uint32_t i = 0; uint32_t word; // 解锁 Flash HAL_FLASH_Unlock(); // 擦除目标 Bank(示例擦除整个 Bank2) FLASH_EraseInitTypeDef erase; erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Banks = FLASH_BANK_2; erase.Sector = 0; erase.NbSectors = 8; // 根据实际扇区数调整 erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; uint32_t error; HAL_FLASHEx_Erase(&erase, &error); // 写入数据(按 32 位字) for (i = 0; i < len; i += 4) { word = (data[i] | (data[i+1] << 8) | (data[i+2] << 16) | (data[i+3] << 24)); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, dest_addr + i, word); } HAL_FLASH_Lock(); return HAL_OK; } ``` ### 4.3 主程序示例 ```c int main(void) { HAL_Init(); SystemClock_Config(); // 假设当前运行在 Bank1,新固件已下载到 RAM 或外部存储 uint8_t new_firmware[1024]; // 示例数据 // 1. 将新固件写入 Bank2 Flash_WriteFirmware(FLASH_BANK2_BASE, new_firmware, sizeof(new_firmware)); // 2. 验证固件(CRC 等) // ... // 3. 切换 Bank Flash_SwitchBank(2); // 4. 切换后,代码继续执行,但实际已运行在 Bank2 的固件中 while (1) { // 应用逻辑 } } ``` ## 5. 注意事项与陷阱 - **双 Bank 模式启用**:部分型号(如 STM32F4 早期版本)默认单 Bank,需在空片时通过选项字节设置 DBANK=1,否则无法使用双 Bank。 - **中断安全**:切换 Bank 期间必须关闭全局中断,否则中断服务函数可能访问旧向量表导致 HardFault。 - **Flash 擦写时间**:擦写 Bank 时系统仍可运行(从另一 Bank 执行),但需确保代码不在被擦写的 Bank 中。 - **固件验证**:切换前务必验证新固件完整性(CRC32 或 SHA256),避免变砖。 - **看门狗**:若使用独立看门狗,需在擦写过程中喂狗,否则系统复位。 - **编译器优化**:链接脚本中需将只读数据(如字符串)放在当前 Bank,否则切换后可能访问错误地址。 ## 6. 总结 双 Bank 切换技术利用 STM32 硬件重映射,实现了 OTA 升级的零停机。关键在于理解 Flash 映射机制、正确配置寄存器,并妥善处理中断向量表和固件验证。本文提供的代码可直接应用于 F4/F7 系列,为你的产品赋予无缝升级能力。