STM32 双Bank切换实现OTA零停机:原理与边界条件分析
👁 1 阅读 · 2026-08-27 · 嵌入式
在工业物联网和汽车电子领域,OTA升级的停机时间直接影响系统可用性。STM32的双Bank架构允许在运行当前固件的同时擦写另一个Bank,通过原子切换实现零停机升级。本文深入剖析双Bank切换的硬件原理、软件配置步骤及关键边界条件(如中断向量重定位、Flash时序约束),并提供完整代码示例,帮助开发者规避常见陷阱,实现可靠的无缝升级。
# 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和文件系统,实现固件下载、存储、校验和切换的流水线化,进一步提升升级效率和安全性。