避开坑点!STM32H750 IAP升级中QSPI只读问题的实战解决方案
避开坑点!STM32H750 IAP升级中QSPI只读问题的实战解决方案
在嵌入式系统开发中,固件升级(IAP)是一个至关重要的功能,它允许设备在不依赖物理烧录器的情况下更新程序。然而,当我们在STM32H750这样的高性能MCU上实现IAP时,会遇到一些独特的挑战。本文将深入探讨STM32H750 IAP升级过程中的一个关键限制——QSPI内存映射模式下的只读特性,并提供一套完整的解决方案。
1. STM32H750 IAP升级的核心挑战
STM32H750作为STMicroelectronics推出的高性能微控制器,其内部Flash仅有128KB,这在许多应用中显然不够。为了扩展存储空间,开发者通常会使用外部QSPI Flash。然而,正是这个看似常规的操作,却为IAP升级埋下了一个"陷阱"。
关键限制点:当STM32H750的QSPI接口配置为内存映射模式时,外部Flash只能读取,不能写入。这意味着我们无法像传统IAP那样直接在程序存储区写入新固件。同时,由于内部Flash只有一个扇区,擦除操作会清除整个扇区,包括正在运行的IAP引导程序。
注意:这个限制在STM32H750参考手册中有明确说明,但很容易被开发者忽略,直到实际开发中遇到问题才会发现。
2. 双Flash架构的解决方案设计
面对上述限制,我们提出了一种创新的双Flash架构方案:
2.1 硬件资源配置
| 组件 | 类型 | 容量 | 用途 |
|---|---|---|---|
| 内部Flash | 内置 | 128KB | 存储Bootloader1和APP内部部分 |
| QSPI Flash | 外部 | 1MB | 存储Bootloader2和APP外部部分 |
| SPI Flash | 外部 | 1MB | 作为固件更新的中转缓冲区 |
2.2 内存分配策略
内部Flash分配:
- 前20KB:Bootloader1
- 剩余108KB:APP1-1(主程序的内部部分)
QSPI Flash分配:
- 前20KB:Bootloader2(存储而非运行)
- 剩余部分:APP1-2(主程序的外部部分)
SPI Flash分配:
- 固件更新缓冲区
- Bootloader1备份区
- 系统状态标志区
3. 四阶段升级流程详解
3.1 初始烧录阶段
这一阶段需要使用ST-Link等烧录工具完成以下操作:
// 示例代码:将Bootloader2和初始APP写入QSPI Flash void program_qspi_flash(void) { QSPI_CommandTypeDef sCommand; // 配置QSPI为写入模式 sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; sCommand.AddressMode = QSPI_ADDRESS_1_LINE; sCommand.DataMode = QSPI_DATA_1_LINE; // 擦除QSPI Flash目标区域 sCommand.Instruction = SECTOR_ERASE_CMD; sCommand.Address = QSPI_BOOT2_ADDR; HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); // 写入Bootloader2 sCommand.Instruction = PAGE_PROG_CMD; write_data_to_qspi(QSPI_BOOT2_ADDR, bootloader2_bin, bootloader2_size); // 写入初始APP write_data_to_qspi(QSPI_APP_ADDR, app_bin, app_size); }3.2 Bootloader1执行阶段
Bootloader1运行在内部Flash中,主要完成以下关键操作:
- 检查更新标志位,判断是否需要执行升级
- 从SPI Flash读取新固件(app2-2)写入QSPI Flash的APP区域
- 将当前Bootloader1备份到SPI Flash
- 将QSPI Flash中的Bootloader2加载到SRAM并跳转执行
提示:在备份Bootloader1时,务必进行校验,确保备份数据的完整性。
3.3 Bootloader2执行阶段
由于直接从QSPI Flash运行Bootloader2可能存在困难,我们的方案将其加载到SRAM中运行:
// 将Bootloader2从QSPI Flash复制到SRAM void copy_boot2_to_sram(void) { memcpy((void*)SRAM_BOOT2_ADDR, (void*)QSPI_BOOT2_ADDR, BOOTLOADER2_SIZE); // 设置向量表偏移 SCB->VTOR = SRAM_BOOT2_ADDR & 0x1FFFFF80; // 跳转到SRAM中的Bootloader2 void (*boot2_entry)(void) = (void (*)(void))(SRAM_BOOT2_ADDR + 4); boot2_entry(); }Bootloader2的主要职责是:
- 从SPI Flash读取新固件的内部部分(app2-1)
- 将其写入内部Flash的APP区域
- 从备份恢复Bootloader1
- 跳转到更新后的主程序
3.4 主程序中的固件接收处理
在主程序运行期间,需要实现固件接收和验证逻辑:
// 固件接收处理示例 void handle_firmware_update(uint8_t *data, uint32_t length) { // 1. 验证固件签名和CRC if(!verify_firmware(data, length)) { return; } // 2. 将固件写入SPI Flash spi_flash_write(SPI_APP_BUFFER_ADDR, data, length); // 3. 设置更新标志 uint8_t update_flag = 0xAA; spi_flash_write(SPI_UPDATE_FLAG_ADDR, &update_flag, 1); // 4. 系统复位 NVIC_SystemReset(); }4. 关键实现细节与优化建议
4.1 安全考虑与数据校验
在固件升级过程中,数据完整性至关重要。我们建议实施多层校验机制:
- 长度校验:上位机首先发送固件长度,设备确认后再接收数据
- CRC校验:对接收到的完整固件进行CRC32校验
- 数字签名:使用ECDSA等算法验证固件来源可信
4.2 性能优化技巧
- 双缓冲技术:在SPI Flash中实现双缓冲,支持边接收边写入,减少总升级时间
- 差分升级:仅传输和更新变化的部分,大幅减少数据传输量
- 压缩传输:在上位机端压缩固件,设备端解压,节省传输时间
4.3 异常处理机制
完善的IAP系统需要处理各种异常情况:
| 异常类型 | 处理策略 |
|---|---|
| 断电恢复 | 通过状态标志识别中断的升级过程,继续或回滚 |
| 校验失败 | 丢弃损坏的固件,保持原系统运行 |
| Flash损坏 | 实现坏块管理,自动跳过损坏区域 |
| 版本回退 | 保留上一版本固件,支持快速回退 |
5. 实际应用中的经验分享
在多个项目实践中,我们发现以下几点特别值得注意:
SRAM容量限制:STM32H750的SRAM有限,当处理大固件时需要分块操作。在我们的案例中,512KB的SRAM可以很好地满足需求,但对于更大的固件,需要更精细的内存管理。
中断处理:在Bootloader阶段,许多系统功能尚未初始化。我们在早期版本中遇到过因为未禁用中断导致的随机崩溃,后来通过在跳转前统一禁用所有中断解决了这个问题。
时序控制:不同厂商的SPI Flash擦写时间差异很大。我们建立了一个Flash参数数据库,根据检测到的Flash ID自动调整等待延时,显著提高了兼容性。
调试技巧:在开发初期,我们在关键节点添加了LED指示灯和串口调试输出,这些看似简单的工具在实际调试中发挥了巨大作用。
