# STM32双Bank切换实现OTA零停机:基于F4/F7的Bank映射与跳转细节 ## 为什么需要双Bank? 传统OTA升级通常采用A/B分区方案:将固件写入备用分区,校验后通过Bootloader跳转。但跳转过程需要复位,导致设备短暂停机(通常数百毫秒至数秒)。对于要求高可用性的系统(如工业控制器、医疗设备),这种停机不可接受。STM32 F4/F7系列(如STM32F427、STM32F746)内置双Bank闪存,允许在运行Bank A时擦写Bank B,并通过硬件寄存器即时切换映射,实现零停机升级。 ## 双Bank架构与地址映射 ### 物理结构 - 闪存被分为两个相等的Bank:Bank 1(地址0x08000000)和Bank 2(地址0x08040000,以1MB闪存为例)。 - 每个Bank独立擦除/编程,但共享数据总线。 ### 映射机制 通过设置选项字节中的`DBANK`位(如F7系列)或`BKB`位(部分F4系列),可切换Bank映射: - **单Bank模式**:默认,闪存连续编址,Bank 2地址紧随Bank 1。 - **双Bank模式**:两个Bank独立编址,但可通过`FLASH_CR`寄存器的`SWAP_BANK`位动态交换映射。例如,当`SWAP_BANK=1`时,Bank 1映射到0x08040000,Bank 2映射到0x08000000。 ### 关键寄存器 - `FLASH_CR`:`SWAP_BANK`位(bit 31)控制映射交换。 - `FLASH_SR`:`BSY`位表示忙,`WBNE`位表示写缓冲非空。 - 选项字节:`DBANK`位(F7)或`BKB`位(F4)决定是否启用双Bank模式(需在系统复位后生效)。 ## 零停机升级流程 1. **准备阶段**:系统运行在Bank A(地址0x08000000),固件版本V1.0。 2. **下载新固件**:通过通信接口(如UART、以太网)接收新固件V2.0,写入Bank B(地址0x08040000)。写入期间,Bank A继续运行,无中断。 3. **校验**:对Bank B的固件进行CRC或哈希校验,确保完整性。 4. **切换映射**:设置`SWAP_BANK=1`,硬件立即交换Bank映射,CPU从0x08000000取指时实际访问Bank B。 5. **跳转**:软件复位(NVIC_SystemReset)后,CPU从0x08000000重新启动,执行新固件。由于映射已交换,新固件直接运行,无需Bootloader干预。 6. **回滚**(可选):若新固件异常,可再次切换映射恢复旧固件。 ## 配置步骤(以STM32F746为例) ### 1. 启用双Bank模式 在系统初始化时,通过修改选项字节启用双Bank(需在复位后生效): ```c void enable_dual_bank(void) { FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); ob.DBANK = FLASH_BANK_DUAL; // 双Bank模式 HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); // 触发选项字节重载,系统复位 } ``` 注意:此操作会立即复位,因此应在首次部署时执行,而非OTA过程中。 ### 2. 写入Bank B固件 使用标准HAL库函数,但需指定目标地址为Bank B基址: ```c void write_firmware_to_bank2(uint32_t *data, uint32_t len) { uint32_t addr = FLASH_BANK2_BASE; // 0x08040000 HAL_FLASH_Unlock(); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL); for (uint32_t i = 0; i < len; i += 4) { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, data[i/4]) != HAL_OK) { // 错误处理 } } HAL_FLASH_Lock(); } ``` ### 3. 切换Bank映射 写入完成后,设置`SWAP_BANK`位: ```c void swap_bank(void) { FLASH_CR->SWAP_BANK = 1; while (FLASH_SR->BSY); // 等待操作完成 } ``` 注意:切换后,当前执行的代码仍在原Bank中,但地址映射已变。此时若继续执行原Bank代码,会导致取指错误,因此必须立即复位。 ### 4. 复位并运行新固件 ```c void jump_to_new_firmware(void) { swap_bank(); NVIC_SystemReset(); // 复位后从0x08000000执行,实际访问Bank B } ``` ## 完整代码示例 以下是一个简化的OTA升级核心函数: ```c #include "stm32f7xx_hal.h" extern uint32_t _estack; // 栈顶地址(来自链接脚本) void ota_update(uint8_t *new_fw, uint32_t size) { // 1. 写入Bank B HAL_FLASH_Unlock(); uint32_t addr = FLASH_BANK2_BASE; for (uint32_t i = 0; i < size; i += 4) { uint32_t word = *(uint32_t *)(new_fw + i); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, word); } HAL_FLASH_Lock(); // 2. 校验(此处省略,建议使用CRC) // 3. 切换Bank并复位 FLASH_CR->SWAP_BANK = 1; while (FLASH_SR->BSY); NVIC_SystemReset(); } // 新固件入口(在Bank B中) void new_firmware_main(void) { // 实际应用中,复位后自动执行,无需手动调用 } ``` ## 跳转细节与注意事项 ### 1. 中断向量表重定位 复位后,CPU从0x08000000读取栈顶指针和复位向量。由于映射已交换,该地址实际指向Bank B。但若新固件的中断向量表未重定位,中断服务函数可能指向旧地址。解决方案: - 在新固件启动代码中,设置`SCB->VTOR = 0x08000000`(或实际Bank基址)。 - 确保链接脚本将向量表放在Bank起始地址。 ### 2. 固件大小限制 双Bank模式下,每个Bank大小等于总闪存的一半。例如1MB闪存,每个Bank 512KB。若固件超过此限制,需改用单Bank模式或压缩。 ### 3. 切换时的时序 `SWAP_BANK`操作需要等待`BSY`清零,但切换本身是瞬间完成的。复位后,新固件立即执行,但需确保电源稳定,避免在切换瞬间掉电。 ### 4. 回滚机制 若新固件运行失败,可通过外部信号或看门狗触发回滚:再次设置`SWAP_BANK=1`并复位,即可恢复旧固件。注意,回滚前需确保旧固件仍存在于原Bank(未被覆盖)。 ### 5. 调试注意事项 - 使用ST-Link调试时,双Bank模式可能影响调试器的闪存识别,需在IDE中配置对应Bank。 - 在切换Bank前,建议关闭所有中断,避免在切换过程中产生异常。 ## 总结 STM32双Bank切换技术通过硬件映射交换,实现了OTA升级的零停机。关键在于理解Bank映射机制、正确配置选项字节、以及处理中断向量表重定位。本文提供的代码和步骤可直接应用于F4/F7系列项目,但需根据具体型号调整寄存器定义。掌握此技术,可显著提升设备的可用性和升级体验。