STM32F4 双 Bank Flash 在线升级(IAP)的地址映射陷阱与规避
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列中,双 Bank Flash 模式为在线升级(IAP)提供了原子性切换的便利,但地址映射的复杂性常导致开发者陷入跳转失败或程序跑飞的困境。本文深入剖析双 Bank 模式下的地址映射机制,揭示常见陷阱(如 Bank 切换后向量表错位、Flash 读写越界),并给出基于 HAL 库的完整配置与升级流程代码,帮助开发者安全实现 IAP 功能。
# STM32F4 双 Bank Flash 在线升级(IAP)的地址映射陷阱与规避
## 一、双 Bank 模式与地址映射基础
STM32F4 系列(如 STM32F427/437)的 Flash 容量通常为 1MB 或 2MB,支持双 Bank 架构。每个 Bank 独立编址,但可通过选项字节配置为单一 Bank 模式(此时地址连续)或双 Bank 模式(地址交错)。在双 Bank 模式下,Flash 被划分为两个逻辑 Bank:
- **Bank 1**:地址范围 `0x08000000` ~ `0x0807FFFF`(1MB 设备)
- **Bank 2**:地址范围 `0x08080000` ~ `0x080FFFFF`
**关键点**:双 Bank 模式下,CPU 的取指和数据的地址映射是**交错**的,即物理扇区 0 映射到 Bank1 的地址 0,物理扇区 1 映射到 Bank2 的地址 0,以此类推。这意味着,当从 Bank1 跳转到 Bank2 执行代码时,向量表地址必须重新映射,否则中断将无法正确响应。
## 二、常见陷阱分析
### 陷阱 1:Bank 切换后向量表错位
在 IAP 升级中,Bootloader 通常位于 Bank1 起始地址,App 位于 Bank2。当 Bootloader 跳转到 App 时,若未正确设置向量表偏移(VTOR),CPU 仍会从 `0x08000000` 读取中断向量,导致执行 `Reset_Handler` 失败或进入 HardFault。
**规避**:在 App 启动代码中,必须设置 `SCB->VTOR = APP_BASE_ADDRESS`,其中 `APP_BASE_ADDRESS` 为 App 在 Bank2 的起始地址(如 `0x08080000`)。
### 陷阱 2:Flash 擦写操作跨越 Bank 边界
双 Bank 模式下,Flash 擦除操作以扇区为单位,但扇区编号是全局的。若开发者误以为 Bank1 的扇区编号从 0 开始,Bank2 从 1 开始,则可能擦除错误区域。实际上,STM32F4 的扇区编号是连续的(0~11),但物理映射交错。
**规避**:使用 HAL 库的 `HAL_FLASHEx_Erase` 时,必须指定正确的扇区编号和 Bank 选择(`FLASH_BANK_1` 或 `FLASH_BANK_2`)。
### 陷阱 3:跳转函数未清理中断状态
在跳转前,若未关闭全局中断或未复位外设,可能导致中断在跳转瞬间触发,而向量表尚未切换,造成不可预知行为。
**规避**:跳转前执行 `__disable_irq()`,并调用 `HAL_RCC_DeInit()` 复位时钟,最后设置 MSP 和跳转。
## 三、双 Bank IAP 实现步骤
### 1. 配置选项字节启用双 Bank 模式
使用 STM32CubeProgrammer 或代码设置选项字节:
```c
// 启用双 Bank 模式(以 STM32F427 为例)
HAL_FLASH_Unlock();
__HAL_FLASH_OPTIMOD_ENABLE();
FLASH_OBProgramInitTypeDef ob;
ob.OptionType = OPTIONBYTE_BYTE;
ob.BYTE = 0x01; // 设置 nDBANK=0,启用双 Bank
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch();
HAL_FLASH_Lock();
```
### 2. Bootloader 跳转函数
```c
#define APP_ADDRESS 0x08080000 // Bank2 起始地址
typedef void (*pFunction)(void);
void jump_to_app(void) {
uint32_t app_stack = *(volatile uint32_t*)APP_ADDRESS;
pFunction app_entry = (pFunction)(*(volatile uint32_t*)(APP_ADDRESS + 4));
// 检查栈顶地址是否合法(RAM 范围)
if ((app_stack & 0x2FFE0000) != 0x20000000) {
return; // 非法地址
}
__disable_irq();
HAL_RCC_DeInit();
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// 设置主栈指针并跳转
__set_MSP(app_stack);
app_entry();
}
```
### 3. App 端向量表重映射
在 App 工程的 `system_stm32f4xx.c` 或 `main.c` 中,于 `main` 函数最开头设置:
```c
#define APP_BASE 0x08080000
void main(void) {
SCB->VTOR = APP_BASE; // 重映射向量表
// 其他初始化...
}
```
### 4. 升级流程(接收固件并写入 Bank2)
```c
void write_firmware_to_bank2(uint8_t *data, uint32_t len, uint32_t offset) {
FLASH_EraseInitTypeDef erase;
uint32_t sector_error = 0;
HAL_FLASH_Unlock();
// 计算扇区(Bank2 起始扇区为 8,每个扇区 128KB,但需根据具体型号调整)
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Banks = FLASH_BANK_2;
erase.Sector = FLASH_SECTOR_8; // 根据实际偏移计算
erase.NbSectors = 1;
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
if (HAL_FLASHEx_Erase(&erase, §or_error) != HAL_OK) {
// 错误处理
}
// 写入数据(注意地址对齐)
for (uint32_t i = 0; i < len; i += 8) {
uint64_t data64 = 0;
for (int j = 0; j < 8; j++) {
data64 |= ((uint64_t)data[i+j]) << (8*j);
}
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, APP_ADDRESS + offset + i, data64) != HAL_OK) {
// 错误处理
}
}
HAL_FLASH_Lock();
}
```
## 四、完整示例:Bootloader 与 App 协作
### Bootloader 端(简化)
```c
int main(void) {
HAL_Init();
// 检查升级标志(如外部 EEPROM 或备份寄存器)
if (upgrade_requested()) {
receive_firmware(); // 接收并写入 Bank2
clear_upgrade_flag();
}
jump_to_app();
}
```
### App 端(简化)
```c
int main(void) {
SCB->VTOR = 0x08080000; // 必须设置
HAL_Init();
// 应用逻辑...
}
```
## 五、注意事项
- **地址对齐**:Flash 编程必须 8 字节对齐(双字),否则 HAL 库会报错。
- **扇区大小**:不同型号的 STM32F4 扇区大小不同(如 F427 的扇区 0~3 为 16KB,4 为 64KB,5~11 为 128KB),务必查阅参考手册。
- **中断安全**:跳转前关闭所有中断,并在 App 中重新初始化外设。
- **校验机制**:升级前建议对固件进行 CRC 校验,防止写入损坏数据。
- **调试技巧**:使用 ST-Link 的 `set var` 命令检查 `SCB->VTOR` 值,确认映射正确。
## 六、总结
双 Bank 模式为 IAP 提供了强大的灵活性,但地址映射的复杂性要求开发者深入理解硬件架构。通过正确设置 VTOR、谨慎处理 Flash 擦写、并遵循安全的跳转流程,可以避免绝大多数陷阱。希望本文能帮助你在 STM32F4 上实现稳定可靠的在线升级功能。