# STM32双Bank切换实现OTA零停机:基于Flash双区映射的在线升级实战 ## 1. 为什么需要双Bank? 传统OTA升级流程:下载固件到临时区 → 擦除主区 → 写入新固件 → 重启。此过程擦写期间应用无法运行,造成数秒甚至更长的停机。对于工业控制、医疗设备或边缘网关,这种中断不可接受。 STM32(如F4、L4、H7系列)支持双Bank Flash架构,将Flash划分为两个独立Bank(如Bank1和Bank2),每个Bank可独立擦写。通过硬件映射,CPU可从任一Bank启动,实现“后台下载+切换启动”的零停机升级。 ## 2. 双Bank原理与映射机制 ### 2.1 硬件架构 - 以STM32F4为例,Flash容量1MB时,Bank1地址0x08000000~0x0807FFFF,Bank2地址0x08080000~0x080FFFFF。 - 每个Bank拥有独立擦写控制器,可并行操作(部分型号支持)。 - 启动映射由选项字节(Option Bytes)中的BOOT_SW位控制:置1时,CPU从Bank2启动,且Bank1映射到0x08000000,Bank2映射到0x08080000;置0时默认从Bank1启动。 ### 2.2 切换流程 1. 应用运行在Bank1,新固件下载到Bank2(通过Bootloader或应用自身)。 2. 校验新固件CRC/签名。 3. 设置BOOT_SW位,触发软复位。 4. 复位后CPU从Bank2启动,应用无缝切换,停机时间仅复位周期(微秒级)。 ## 3. 工程配置步骤 ### 3.1 链接脚本调整 将Flash划分为两个区域,分别对应Bank1和Bank2。以STM32F407VG(1MB Flash)为例,修改链接脚本(.ld): ```c MEMORY { FLASH_BANK1 (rx) : ORIGIN = 0x08000000, LENGTH = 512K FLASH_BANK2 (rx) : ORIGIN = 0x08080000, LENGTH = 512K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K } ``` 编译时,为Bank1和Bank2分别生成固件,或使用同一固件通过宏控制起始地址。 ### 3.2 选项字节设置 使用STM32CubeProgrammer或代码设置BOOT_SW位。代码方式: ```c // 解锁选项字节 FLASH_OB_Unlock(); // 设置BOOT_SW为1(从Bank2启动) FLASH_OB_UserConfig(OB_BOOT_SW_SET); // 保存选项字节 FLASH_OB_Launch(); ``` 注意:设置后需复位生效,且选项字节写入会触发一次Flash擦写,但时间极短(毫秒级)。 ## 4. 完整代码示例 以下示例实现:应用运行在Bank1,接收新固件写入Bank2,校验后切换。 ### 4.1 固件写入Bank2 ```c #define BANK2_BASE 0x08080000 #define PAGE_SIZE 2048 // 根据芯片调整 void Write_Firmware_To_Bank2(uint32_t *data, uint32_t size) { uint32_t addr = BANK2_BASE; FLASH_Unlock(); // 擦除Bank2(按页擦除) for (uint32_t offset = 0; offset < size; offset += PAGE_SIZE) { FLASH_ErasePage(addr + offset); } // 写入数据 for (uint32_t i = 0; i < size / 4; i++) { FLASH_ProgramWord(addr + i * 4, data[i]); } FLASH_Lock(); } ``` ### 4.2 校验固件完整性 ```c uint8_t Verify_Firmware_CRC(uint32_t expected_crc) { uint32_t crc = 0; // 使用硬件CRC或软件CRC计算Bank2区域 crc = HAL_CRC_Calculate(&hcrc, (uint32_t *)BANK2_BASE, FIRMWARE_SIZE / 4); return (crc == expected_crc) ? 1 : 0; } ``` ### 4.3 切换Bank ```c void Switch_To_Bank2(void) { // 设置BOOT_SW位 FLASH_OB_Unlock(); FLASH_OB_UserConfig(OB_BOOT_SW_SET); FLASH_OB_Launch(); // 软复位 NVIC_SystemReset(); } ``` ### 4.4 主流程 ```c int main(void) { // 初始化... // 检查是否有新固件待安装 if (New_Firmware_Available()) { // 接收固件到RAM缓冲区(或直接流式写入) Receive_Firmware(); // 写入Bank2 Write_Firmware_To_Bank2(fw_buffer, fw_size); // 校验 if (Verify_Firmware_CRC(expected_crc)) { Switch_To_Bank2(); } else { // 校验失败,继续运行旧固件 } } // 正常运行应用... while (1) { // 应用逻辑 } } ``` ## 5. 注意事项与优化建议 - **地址映射**:切换后,Bank2映射到0x08000000,但中断向量表仍指向原地址。需在启动文件或SystemInit中重设VTOR(向量表偏移寄存器)。 ```c SCB->VTOR = 0x08000000; // 切换后Bank2基址 ``` - **固件大小**:确保新固件不超过Bank容量(本例512KB)。 - **擦写时间**:虽然双Bank可并行,但写入Bank2时应用仍运行,需确保Flash控制器不冲突(部分型号支持同时读写不同Bank)。 - **回滚机制**:若新固件启动失败,可设置看门狗或标志位,在Bootloader中回滚到旧Bank。 - **选项字节保护**:写入选项字节前需解锁,且操作后重新锁定,防止误改。 - **性能考量**:双Bank切换仅复位,但若应用有状态需保存,需在切换前持久化关键数据。 ## 6. 总结 利用STM32双Bank架构,OTA升级从“停机数秒”优化为“零停机”,极大提升设备可用性。本文从原理到代码,展示了完整实现路径。实际项目中,还需结合安全启动、加密校验等机制,打造更健壮的升级系统。希望本文能为你提供实战参考,助你在嵌入式领域更上一层楼。