STM32双Bank切换OTA:破解Flash擦写期间中断向量表失效的终极方案
👁 1 阅读 · 2026-08-27 · 嵌入式
在STM32双Bank架构下进行OTA升级,Flash擦写期间中断向量表失效是导致系统崩溃的常见隐患。本文深入剖析双Bank切换原理,揭示中断向量表失效的根本原因,并提供三种实战级解决方案:基于SRAM的中断向量重映射、Bank状态标志位保护、以及双Bank交替执行的原子切换策略。通过完整代码示例与配置步骤,助你构建健壮的OTA升级流程,确保系统在固件更新中始终响应关键中断。
# 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布局)选择合适方案,并充分测试。记住,嵌入式系统的稳定性源于对细节的极致把控。