STM32H7双Bank闪存并行编程:实现OTA期间无缝代码执行
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,传统单Bank方案常导致代码暂停执行,影响实时性。STM32H7系列凭借双Bank闪存架构,支持在运行一个Bank的同时擦写另一个Bank,实现无缝固件更新。本文将深入解析双Bank并行编程原理,手把手配置Flash控制器,提供完整代码示例,并分享关键注意事项,助你构建高可靠、零中断的OTA系统。
# STM32H7双Bank闪存并行编程:实现OTA期间无缝代码执行
## 一、为什么需要双Bank闪存?
传统单Bank闪存方案中,OTA升级流程通常为:接收固件→擦除旧代码→写入新代码→跳转。擦写期间,CPU必须从闪存读取指令,若闪存被占用,代码执行将停滞,导致系统响应中断。对于电机控制、工业通信等实时性要求高的场景,这种停顿不可接受。
STM32H7系列(如STM32H743、H750)内置双Bank闪存,每个Bank容量相等(如1MB分两个512KB Bank)。硬件支持在Bank1执行代码的同时,对Bank2进行擦除/编程操作,反之亦然。这为OTA提供了天然的无缝切换基础。
## 二、双Bank并行编程原理
### 2.1 闪存架构
- **Bank划分**:闪存被分为两个物理Bank,地址连续。例如,STM32H743的Flash起始地址0x08000000,Bank1覆盖0x08000000-0x0807FFFF,Bank2覆盖0x08080000-0x080FFFFF。
- **独立控制**:每个Bank有独立的控制寄存器(FLASH_CR1/CR2)和状态寄存器(FLASH_SR1/SR2),可独立执行擦写操作。
- **总线访问**:CPU取指和DMA访问可同时命中不同Bank,通过AHB总线交叉矩阵实现并行访问。
### 2.2 并行编程机制
当CPU从Bank1取指时,若对Bank2发起擦除或编程命令,Flash控制器会利用空闲的总线周期处理操作,不会阻塞Bank1的读取。关键点:
- **擦除操作**:按扇区擦除(典型扇区大小128KB),擦除期间Bank2不可读,但Bank1不受影响。
- **编程操作**:以双字(256位)为单位写入,编程时间短,冲突概率低。
- **硬件仲裁**:若CPU同时访问两个Bank,硬件自动插入等待周期,但不会导致错误。
## 三、配置步骤
### 3.1 使能双Bank模式
默认情况下,STM32H7可能处于单Bank模式(通过选项字节配置)。需在系统初始化时检查并设置:
```c
void Flash_EnableDualBank(void) {
if (READ_BIT(FLASH->OPTSR_CUR, FLASH_OPTSR_DUALBANK) == 0) {
// 解锁选项字节
HAL_FLASH_OB_Unlock();
// 设置双Bank模式
SET_BIT(FLASH->OPTSR_PRG, FLASH_OPTSR_DUALBANK);
// 触发选项字节加载
HAL_FLASH_OB_Launch();
// 重新上电或复位后生效
NVIC_SystemReset();
}
}
```
注意:修改选项字节后需系统复位才能生效,因此建议在Bootloader中执行此操作。
### 3.2 配置Flash控制器
使用HAL库时,需初始化Flash等待周期和预取缓冲:
```c
void Flash_Init(void) {
HAL_FLASH_Unlock();
// 设置等待周期,根据主频调整(例如主频480MHz时,等待周期为2)
__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2);
// 使能指令缓存和数据缓存
HAL_FLASH_EnableIT(FLASH_IT_ICACHE_EN);
HAL_FLASH_EnableIT(FLASH_IT_DCACHE_EN);
HAL_FLASH_Lock();
}
```
### 3.3 双Bank并行编程实现
以下代码演示在Bank1执行时,对Bank2进行扇区擦除和编程:
```c
// 假设当前代码在Bank1运行,目标Bank2地址0x08080000
void OTA_WriteFirmware(uint32_t destAddr, uint32_t *data, uint32_t len) {
// 确保目标地址在Bank2
if (destAddr < 0x08080000 || destAddr >= 0x08100000) return;
// 解锁Flash
HAL_FLASH_Unlock();
// 擦除目标扇区(例如Bank2的第一个扇区)
FLASH_EraseInitTypeDef erase = {0};
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Banks = FLASH_BANK_2; // 指定Bank2
erase.Sector = 0; // 扇区号
erase.NbSectors = 1;
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
uint32_t error = 0;
if (HAL_FLASHEx_Erase(&erase, &error) != HAL_OK) {
// 处理错误
}
// 编程数据(以双字为单位)
for (uint32_t i = 0; i < len; i += 8) {
uint64_t data64 = *(uint64_t*)(data + i/4);
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, destAddr + i, data64) != HAL_OK) {
// 处理错误
}
}
HAL_FLASH_Lock();
}
```
在调用此函数时,主循环和中断照常运行,因为CPU取指在Bank1,而擦写操作在Bank2。
## 四、完整示例:OTA无缝升级流程
假设Bootloader位于Bank1起始,App1在Bank1后半部分,App2在Bank2。升级时,Bootloader将新固件写入Bank2,完成后设置跳转标志,复位后Bootloader检测标志并跳转至Bank2。
```c
// Bootloader主逻辑
void Bootloader_Main(void) {
// 检查跳转标志
if (CheckJumpFlag() == 0xAA55) {
// 跳转至Bank2的App2
JumpToApp(0x08080000);
}
// 若收到升级命令
if (OTA_CommandReceived()) {
// 擦写Bank2(此时Bootloader在Bank1运行)
OTA_WriteFirmware(0x08080000, newFwData, newFwLen);
// 设置跳转标志
SetJumpFlag(0xAA55);
NVIC_SystemReset();
}
}
void JumpToApp(uint32_t appAddr) {
// 设置MSP和跳转
uint32_t msp = *(volatile uint32_t*)appAddr;
__set_MSP(msp);
void (*appEntry)(void) = (void (*)(void))(*(volatile uint32_t*)(appAddr + 4));
appEntry();
}
```
## 五、注意事项
- **选项字节修改**:双Bank模式必须在首次启动时配置,且修改后需复位。若在App中修改,需确保App有复位能力。
- **中断向量表**:跳转到新Bank后,需重定位中断向量表(通过SCB->VTOR设置)。
- **擦除时间**:虽然并行编程不阻塞执行,但擦除大扇区仍需时间(约几百毫秒),期间Bank2不可访问,需避免从Bank2读取数据。
- **电源稳定性**:擦写期间电流波动较大,需保证电源稳定,建议使用独立LDO或电容。
- **错误处理**:编程前检查地址对齐(双字对齐),编程后校验数据(读回比较)。
- **缓存一致性**:若使能了D-Cache,编程后需Clean&Invalidate相关地址,避免读到旧数据。
## 六、总结
STM32H7的双Bank闪存并行编程为OTA提供了优雅的解决方案,实现了固件升级过程中的零代码停顿。通过合理配置选项字节、利用HAL库的Bank指定接口,开发者可以轻松构建无缝升级系统。本文提供的代码和步骤可直接应用于实际项目,但需根据具体型号和主频调整参数。掌握这一技术,将显著提升嵌入式产品的可靠性和用户体验。