# STM32双Bank切换OTA:破解Flash擦写期间中断向量表失效的终极方案 ## 引言 在嵌入式OTA(Over-The-Air)升级中,STM32的双Bank架构(如F4、F7、H7系列)允许在运行一个Bank时擦写另一个Bank,实现无缝升级。然而,当Flash擦写操作正在进行时,若中断向量表(VTOR)指向的Bank被擦除或写入,任何中断触发都将导致CPU跳转到无效地址,引发HardFault或系统死机。本文将深入探讨该问题的根源,并提供三种经过验证的解决方案,确保OTA升级期间系统中断响应不中断。 ## 1. 双Bank切换与中断向量表失效原理 ### 1.1 双Bank架构概述 STM32双Bank Flash将闪存划分为两个独立的Bank(如Bank1和Bank2),每个Bank可独立擦写。通过设置选项字节或使用`FLASH_CR`寄存器的`BANK_SW`位,可切换CPU的启动和执行区域。OTA升级时,通常将新固件写入非活动Bank,然后切换启动地址。 ### 1.2 中断向量表(VTOR)的作用 中断向量表存储所有中断服务函数(ISR)的入口地址。CPU通过VTOR基址+中断号偏移获取ISR地址。默认VTOR指向Flash起始地址(如0x08000000)。当执行Bank切换时,VTOR必须更新到新Bank的基址,否则中断将跳转到旧地址。 ### 1.3 失效场景分析 - **场景1**:在Bank1运行时,擦写Bank2。若VTOR仍指向Bank1,擦写Bank2不影响中断,但若擦写Bank1(如回滚操作),则中断向量表被破坏。 - **场景2**:切换Bank后,VTOR未及时更新,导致中断跳转到旧Bank的无效地址。 - **场景3**:擦写操作本身可能被中断打断,若中断处理函数位于被擦写的Bank,则执行擦写时中断触发即崩溃。 ## 2. 解决方案一:基于SRAM的中断向量重映射 ### 2.1 原理 将中断向量表复制到SRAM中,并将VTOR指向SRAM基址。由于SRAM不受Flash擦写影响,中断始终有效。需注意SRAM容量和向量表对齐(通常按128字节对齐)。 ### 2.2 配置步骤 1. 在链接脚本中预留SRAM区域(如`VECTOR_RAM`段)。 2. 启动代码中,将Flash中的向量表复制到SRAM。 3. 设置VTOR为SRAM基址。 ### 2.3 代码示例 ```c // 定义SRAM向量表区域(在链接脚本中分配) __attribute__((section(".vector_ram"))) uint32_t vector_table[128]; void vector_table_init(void) { // 复制Flash向量表到SRAM(假设原向量表在0x08000000) memcpy(vector_table, (uint32_t*)0x08000000, 128 * 4); // 设置VTOR为SRAM基址 SCB->VTOR = (uint32_t)vector_table; } // 在main函数早期调用 int main(void) { vector_table_init(); // 后续可安全擦写Flash } ``` ### 2.4 注意事项 - 确保SRAM空间足够(通常128字节对齐,需512字节)。 - 若使用RTOS,需确保向量表复制在调度器启动前完成。 - 中断服务函数地址必须位于非擦写区域(如SRAM或固定Bank)。 ## 3. 解决方案二:Bank状态标志位保护 ### 3.1 原理 在Flash的固定区域(如选项字节区或系统存储区)存储一个标志位,指示当前活动Bank。在擦写操作前检查标志位,确保不擦写当前活动Bank。中断处理函数中也可检查标志位,若中断向量表所在Bank正在被擦写,则延迟处理或使用备用ISR。 ### 3.2 配置步骤 1. 在Flash末尾或独立扇区定义标志位。 2. 每次切换Bank时更新标志位。 3. 擦写前读取标志位,禁止擦写活动Bank。 ### 3.3 代码示例 ```c #define BANK_FLAG_ADDR 0x080FF000 // 假设在最后一个扇区 #define BANK1_ACTIVE 0xA5A5A5A5 #define BANK2_ACTIVE 0x5A5A5A5A uint32_t get_active_bank_flag(void) { return *(volatile uint32_t*)BANK_FLAG_ADDR; } void set_active_bank_flag(uint32_t flag) { // 先擦除标志扇区,再写入 FLASH_EraseSector(FLASH_SECTOR_11, VOLTAGE_RANGE_3); FLASH_ProgramWord(BANK_FLAG_ADDR, flag); } int is_bank_erasable(uint32_t bank) { uint32_t flag = get_active_bank_flag(); if (bank == BANK1 && flag == BANK1_ACTIVE) return 0; if (bank == BANK2 && flag == BANK2_ACTIVE) return 0; return 1; } // 擦写Bank2前检查 if (is_bank_erasable(BANK2)) { // 执行擦写 } ``` ### 3.4 注意事项 - 标志位区域必须独立于两个Bank,避免被擦写。 - 标志位更新需原子操作,防止中断打断。 - 若标志位损坏,需有默认回退策略。 ## 4. 解决方案三:双Bank交替执行的原子切换策略 ### 4.1 原理 在切换Bank时,先将中断向量表切换到新Bank,再执行Bank切换。同时,确保切换过程在临界区(关中断)内完成,避免中断在切换中间触发。此外,利用双Bank的硬件特性,通过`FLASH_CR`的`BANK_SW`位实现原子切换。 ### 4.2 配置步骤 1. 关闭全局中断(`__disable_irq()`)。 2. 设置VTOR指向新Bank的向量表。 3. 设置`FLASH_CR`的`BANK_SW`位,切换Bank。 4. 重新使能中断(`__enable_irq()`)。 ### 4.3 代码示例 ```c void bank_switch_atomic(uint32_t new_bank_base) { __disable_irq(); // 更新VTOR到新Bank SCB->VTOR = new_bank_base; // 执行Bank切换(以F4为例) FLASH->CR |= FLASH_CR_BANK_SW; while (FLASH->CR & FLASH_CR_BANK_SW); // 等待切换完成 __enable_irq(); } ``` ### 4.4 注意事项 - 切换期间不能有任何中断,包括NMI和SysTick,因此需确保关中断时间极短。 - 若使用RTOS,需在调度器暂停状态下执行。 - 切换后需重新初始化外设时钟等,因为Bank切换可能影响系统时钟配置。 ## 5. 综合最佳实践 - **组合方案**:使用SRAM向量表重映射作为基础,配合Bank标志位保护,并在切换时采用原子操作。 - **升级流程**: 1. 启动时初始化SRAM向量表。 2. 下载新固件到非活动Bank。 3. 校验固件完整性。 4. 更新标志位,指示新Bank为活动。 5. 原子切换Bank并更新VTOR。 6. 复位系统。 - **测试要点**:在擦写期间触发各类中断(如定时器、UART),验证系统不崩溃。 ## 6. 总结 双Bank OTA升级中,中断向量表失效是必须克服的挑战。通过SRAM重映射、标志位保护、原子切换三种方案,可确保Flash擦写期间中断响应不中断。实际项目中,建议根据芯片资源(SRAM大小、Flash布局)选择合适方案,并充分测试。记住,嵌入式系统的稳定性源于对细节的极致把控。