# STM32 双Bank切换实现OTA零停机:原理与边界条件分析 ## 一、为什么需要双Bank? 传统OTA升级流程通常包括:下载固件 → 擦除旧应用 → 写入新应用 → 跳转重启。其中擦除和写入阶段,系统必须停止运行,造成数秒甚至更长的停机。对于需要7x24小时运行的设备(如电力监控、医疗仪器),这种停机不可接受。 STM32的**双Bank Flash**(如F7、H7系列)将内部Flash划分为两个独立的Bank(Bank1和Bank2),每个Bank可独立擦写。升级时,系统在Bank1运行,同时擦写Bank2;完成后通过**切换启动地址**,重启后直接运行Bank2的新固件,整个过程无需停机等待Flash操作。 ## 二、双Bank切换的硬件原理 ### 2.1 Flash组织架构 以STM32H743为例,其Flash总容量2MB,分为两个Bank: - Bank1:地址0x08000000 ~ 0x080FFFFF(1MB) - Bank2:地址0x08100000 ~ 0x081FFFFF(1MB) 每个Bank内部又分为多个扇区(Sector),支持独立擦写。关键寄存器: - `FLASH_CR`:控制Flash操作,其中`BANK_SW`位用于切换启动Bank - `FLASH_OPTCR`:配置选项字节,决定系统从哪个Bank启动 ### 2.2 切换机制 系统复位后,CPU从`0x08000000`(Bank1)或`0x08100000`(Bank2)取指,具体取决于选项字节`nBOOT1`和`BANK_SW`位的组合。切换流程: 1. 在Bank1运行,将新固件写入Bank2 2. 设置`FLASH_CR`的`BANK_SW`位为1 3. 触发软件复位(`NVIC_SystemReset()`) 4. 复位后,CPU从Bank2启动,运行新固件 注意:切换是**原子性**的,复位后立即生效,无需外部干预。 ## 三、软件配置步骤 ### 3.1 链接脚本调整 需要为两个Bank分别创建链接脚本,确保固件链接到正确的地址。以GCC为例,Bank1的`FLASH`起始地址为`0x08000000`,Bank2为`0x08100000`。 ```c /* Bank1.ld */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512K } /* Bank2.ld */ MEMORY { FLASH (rx) : ORIGIN = 0x08100000, LENGTH = 1M RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512K } ``` ### 3.2 中断向量表重定位 每个Bank的固件必须将中断向量表重定位到自己的起始地址。在`SystemInit()`或`main()`开头执行: ```c void vector_relocate(void) { // 获取当前运行的Bank基地址 uint32_t app_addr = (FLASH_BASE & 0x1FFFFF) ? 0x08100000 : 0x08000000; // 设置VTOR寄存器 SCB->VTOR = app_addr; __DSB(); __ISB(); } ``` ### 3.3 双Bank写入流程 以下代码演示在Bank1运行时,将新固件写入Bank2: ```c void ota_write_bank2(uint8_t *data, uint32_t len, uint32_t offset) { // 解锁Flash HAL_FLASH_Unlock(); // 擦除Bank2的扇区(假设从扇区0开始) FLASH_EraseInitTypeDef erase; erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Banks = FLASH_BANK_2; erase.Sector = FLASH_SECTOR_0; erase.NbSectors = 8; erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; uint32_t error = 0; HAL_FLASHEx_Erase(&erase, &error); // 写入数据到Bank2地址 uint32_t addr = 0x08100000 + offset; for (uint32_t i = 0; i < len; i += 8) { uint64_t val = *(uint64_t*)(data + i); HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i, val); } HAL_FLASH_Lock(); } ``` ### 3.4 切换Bank并复位 ```c void switch_to_bank2(void) { // 设置BANK_SW位 FLASH->CR |= FLASH_CR_BANK_SW; // 等待就绪 while (FLASH->SR & FLASH_SR_BSY); // 软件复位 NVIC_SystemReset(); } ``` ## 四、边界条件与注意事项 ### 4.1 中断向量表必须重定位 如果忽略VTOR设置,中断将跳转到旧Bank的向量表,导致程序崩溃。务必在启动早期完成重定位,且确保两个固件的中断处理函数地址正确。 ### 4.2 Flash操作时序约束 - 擦写期间,CPU不能从同一Bank取指,否则会触发总线错误。因此,擦写Bank2时,代码必须运行在Bank1或RAM中。 - 建议将Flash操作函数放在RAM中执行(`__attribute__((section(".ramfunc")))`),避免取指冲突。 ### 4.3 固件版本校验 切换前必须验证新固件的完整性(如CRC32、SHA256)。否则,损坏的固件可能导致设备变砖。建议在写入完成后、切换前进行校验。 ```c bool verify_firmware(uint32_t bank_addr, uint32_t size, uint32_t crc_expected) { uint32_t crc = HAL_CRC_Calculate(&hcrc, (uint32_t*)bank_addr, size / 4); return (crc == crc_expected); } ``` ### 4.4 回滚机制 若新固件启动失败(如看门狗超时),应能回滚到旧Bank。实现方式: - 在固件头部存储状态标志(如`0xA5`表示有效) - 启动时检查标志,若无效则切换回另一Bank ```c void check_and_rollback(void) { uint32_t *app_header = (uint32_t*)APP_ADDR; if (app_header[0] != 0xA5A5A5A5) { // 切换回另一Bank FLASH->CR ^= FLASH_CR_BANK_SW; NVIC_SystemReset(); } } ``` ### 4.5 双Bank容量限制 双Bank模式将Flash容量减半,仅适用于固件大小不超过单个Bank的场合。对于大固件,需考虑压缩或差分升级。 ## 五、总结 STM32双Bank切换通过硬件级Bank隔离和原子切换,实现了OTA零停机升级。关键在于:正确配置链接脚本、重定位中断向量表、处理Flash时序约束,并设计可靠的校验与回滚机制。掌握这些边界条件,你就能构建工业级可靠的OTA系统。 实际项目中,建议结合RTOS和文件系统,实现固件下载、存储、校验和切换的流水线化,进一步提升升级效率和安全性。