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 或代码设置选项字节:
// 启用双 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 跳转函数
#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 函数最开头设置:
#define APP_BASE 0x08080000
void main(void) {
SCB->VTOR = APP_BASE; // 重映射向量表
// 其他初始化...
}
4. 升级流程(接收固件并写入 Bank2)
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 端(简化)
int main(void) {
HAL_Init();
// 检查升级标志(如外部 EEPROM 或备份寄存器)
if (upgrade_requested()) {
receive_firmware(); // 接收并写入 Bank2
clear_upgrade_flag();
}
jump_to_app();
}
App 端(简化)
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 上实现稳定可靠的在线升级功能。