STM32F4双Bank闪存并行读写:在线升级不掉电的Bank切换实战
一、为什么需要双Bank闪存?
传统单Bank闪存升级流程:
- 擦除整个应用区 → 2. 写入新固件 → 3. 跳转重启。
此过程存在致命缺陷:升级期间设备完全停机,且一旦写入失败(如断电),设备变砖。
双Bank架构将闪存划分为两个独立的存储区(Bank1和Bank2),支持并行编程:当CPU从Bank1执行代码时,可以同时对Bank2进行擦写操作。升级完成后,通过修改标志位并复位,Bootloader即可跳转到新Bank运行。整个过程无需断电,且旧固件始终保留,极大提升了可靠性。
二、STM32F4双Bank硬件原理
以STM32F407为例,其内置1MB闪存,默认配置为单Bank(Bank1=512KB,Bank2=512KB,但合并为连续地址0x08000000-0x080FFFFF)。启用双Bank模式后,地址映射发生改变:
- Bank1:0x08000000 - 0x0807FFFF(512KB)
- Bank2:0x08080000 - 0x080FFFFF(512KB)
关键寄存器:
-
FLASH_OPTCR:选项字节控制寄存器,其中DBANK位(bit22)用于切换单/双Bank模式。 -
FLASH_CR:闪存控制寄存器,PG(编程)、PER(页擦除)、MER(全擦除)等位。
注意:切换Bank模式需要修改选项字节,并触发系统复位才能生效。
三、双Bank并行读写的核心优势
- 无缝升级:运行在Bank1时,擦写Bank2,完成后复位跳转,中断时间仅为一个复位周期(毫秒级)。
- 回滚保护:新固件异常时,Bootloader可回退到旧Bank。
- 双固件冗余:适合A/B分区升级策略。
四、实战配置步骤
1. 启用双Bank模式
通过修改选项字节使能双Bank。可使用STM32CubeProgrammer或代码方式。以下为代码方式:
void Flash_Enable_DualBank(void) {
FLASH_OB_Unlock();
// 设置DBANK位为1(双Bank模式)
FLASH_OB_Program(OB_DBANK_MODE, 1);
FLASH_OB_Launch(); // 触发选项字节重载
FLASH_OB_Lock();
NVIC_SystemReset(); // 必须复位生效
}
2. 链接脚本调整
将固件分为两个区域,例如:
MEMORY
{
BANK1 (rx) : ORIGIN = 0x08000000, LENGTH = 512K
BANK2 (rx) : ORIGIN = 0x08080000, LENGTH = 512K
}
3. 并行读写实现
在Bank1运行时,对Bank2进行擦写。核心代码:
// 擦除Bank2的指定页
void Flash_Erase_Bank2_Page(uint32_t page_addr) {
FLASH_Unlock();
while (FLASH_GetStatus() == BUSY);
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
FLASH_ErasePage(page_addr); // 页地址需在Bank2范围内
FLASH_Lock();
}
// 写入数据到Bank2
void Flash_Write_Bank2(uint32_t addr, uint32_t *data, uint32_t len) {
FLASH_Unlock();
for (uint32_t i = 0; i < len; i++) {
while (FLASH_GetStatus() == BUSY);
FLASH_ProgramWord(addr + i*4, data[i]);
}
FLASH_Lock();
}
4. 升级流程与跳转
升级完成后,写入升级标志(如备份在备份寄存器),然后复位。Bootloader根据标志决定跳转地址:
void Jump_To_App(uint32_t app_addr) {
// 检查栈顶地址合法性
if (((*(volatile uint32_t*)app_addr) & 0x2FFE0000) == 0x20000000) {
uint32_t jump_addr = *(volatile uint32_t*)(app_addr + 4);
pFunction jump = (pFunction)jump_addr;
__set_MSP(*(volatile uint32_t*)app_addr);
jump();
}
}
五、完整示例:在线升级Demo
假设Bootloader位于Bank1,新固件下载到Bank2。以下为简化流程:
int main(void) {
// 初始化外设...
if (Check_Update_Flag()) {
// 1. 擦除Bank2
for (uint32_t page = 0x08080000; page < 0x080FFFFF; page += 2048) {
Flash_Erase_Bank2_Page(page);
}
// 2. 从外部存储(如SD卡)读取固件并写入Bank2
uint32_t buffer[128];
uint32_t write_addr = 0x08080000;
while (Read_Firmware_Block(buffer, 128)) {
Flash_Write_Bank2(write_addr, buffer, 128);
write_addr += 512;
}
// 3. 设置跳转标志并复位
Set_Boot_Flag(BANK2);
NVIC_SystemReset();
} else {
// 正常启动,根据标志跳转
if (Get_Boot_Flag() == BANK2) {
Jump_To_App(0x08080000);
} else {
Jump_To_App(0x08000000);
}
}
}
六、注意事项与避坑指南
- 选项字节修改不可逆:一旦启用双Bank,地址映射改变,旧固件无法直接运行,需重新烧录。建议在量产前配置好。
-
中断向量表重定位:Bank2固件需在启动时重定位向量表:
SCB->VTOR = 0x08080000; - 擦写期间禁止中断:虽然双Bank支持并行,但擦写操作会阻塞CPU,若需实时性,可考虑使用DMA或RTOS任务调度。
- 电源稳定性:升级期间掉电可能导致Bank2写入不完整,但Bank1仍可启动,因此务必在Bootloader中增加完整性校验(如CRC)。
- 使用官方库函数:STM32F4标准外设库或HAL库均支持双Bank操作,但需注意版本差异。
七、总结
双Bank闪存为STM32F4提供了强大的在线升级能力,通过合理的Bank切换和并行读写,可实现近乎零停机的固件更新。本文从原理到代码,完整演示了双Bank模式的配置与使用。在实际项目中,建议结合Bootloader设计、固件签名和回滚机制,构建高可靠的OTA系统。