STM32F103VET6实战:用W25Q64外部Flash实现Bootloader+Application双备份(含完整代码解析)

STM32F103VET6实战:用W25Q64外部Flash实现Bootloader+Application双备份(含完整代码解析)

最近在做一个工业数据采集终端的项目,客户要求设备在野外部署后能够稳定运行数年,同时还要支持远程固件升级。最头疼的问题就是升级过程中万一断电怎么办?总不能让人跑到荒郊野外去重新烧录程序吧。这种场景下,传统的单备份升级方案风险太高,一旦升级失败设备就“变砖”了。

我调研了几种方案,最终决定采用Bootloader+双备份的架构,利用STM32F103VET6内部Flash运行程序,再通过SPI接口连接W25Q64外部Flash存储两个完整的应用程序副本。这样即使升级过程中出现任何意外,设备也能自动回滚到之前的稳定版本,确保系统始终可用。

这篇文章就是我在实际项目中摸索出来的完整实现方案,从硬件选型到代码细节都会详细拆解。如果你也在为STM32的远程升级安全性发愁,或者想了解如何利用外部Flash做双备份,相信这篇实战指南能给你不少启发。

1. 硬件架构设计与选型考量

选择STM32F103VET6搭配W25Q64这个组合,是我经过多次测试对比后的决定。STM32F103VET6属于Cortex-M3内核,512KB的内部Flash和64KB的RAM,性能足够应对大多数嵌入式应用。更重要的是它的性价比——在需要大量部署的工业场景中,成本控制是个现实问题。

W25Q64是Winbond的8MB SPI Flash,它的几个特性特别适合做程序备份:

  • SPI接口简单:只需要4根线(CS、CLK、MISO、MOSI)就能连接,不占用太多IO资源
  • 扇区结构规整:整个芯片分为128个扇区,每个扇区64KB,还有256个4KB的块,擦写管理很方便
  • 低功耗:深度睡眠模式下电流只有1μA,对于电池供电的设备很友好
  • 耐用性:每个扇区可擦写10万次,数据保存期限20年

注意:W25Q64的写操作需要先擦除,擦除的最小单位是4KB扇区。这意味着如果你的程序大小不是4KB的整数倍,会浪费一些存储空间。

硬件连接上,我推荐下面这种接法:

STM32F103VET6          W25Q64
PA4 (SPI1_NSS)   ---   CS
PA5 (SPI1_SCK)   ---   CLK
PA6 (SPI1_MISO)  ---   DO
PA7 (SPI1_MOSI)  ---   DI
3.3V             ---   VCC
GND              ---   GND

SPI的时钟频率我设置在18MHz左右,这个速度既能保证数据传输效率,又不会因为信号完整性问题导致读写错误。实际测试中,通过W25Q64备份一个200KB的应用程序大约需要2.3秒,这个时间对于大多数升级场景都是可以接受的。

2. Flash空间规划与分区策略

空间规划是整个方案的基础,分得好不好直接影响到后续的维护复杂度。W25Q64有8MB空间,看起来很大,但如果不合理规划,后期扩展会很麻烦。

我的分区方案是这样的:

分区名称 起始地址 大小 用途说明
Bootloader区 0x08000000 16KB 存放Bootloader程序
Application区 0x08004000 496KB 运行中的应用程序
外部Flash分区1 0x000000 1MB 存储待升级的新程序
外部Flash分区2 0x100000 1MB 存储当前程序的备份
标志位区 0x200000 4KB 存储升级状态标志和校验信息

为什么Application区从0x08004000开始?因为STM32的中断向量表默认在0x08000000,Bootloader需要占用前面的空间。16KB对于Bootloader来说绰绰有余,实际上我的Bootloader代码编译后只有8KB左右,留一些余量给未来功能扩展。

外部Flash的1MB分区是怎么来的?STM32F103VET6内部Flash最大512KB,去掉Bootloader的16KB,剩下496KB。取整到1MB是为了对齐和管理方便,虽然浪费了一些空间,但换来了清晰的地址映射。

标志位区只需要很小的空间,我分配了4KB(一个扇区)。这里存放的关键信息包括:

struct ProgramStatusFlag {
    uint8_t backup_flag;          // 0xA5表示已有备份
    uint32_t old_prog_add_checksum;  // 备份程序累加校验和
    uint8_t old_prog_xor_checksum;   // 备份程序异或校验和
    uint32_t new_prog_add_checksum;  // 新程序累加校验和
    uint8_t new_prog_xor_checksum;   // 新程序异或校验和
    uint8_t update_flag;          // 0x55表示需要更新
};

这种分区方案有几个好处:

  1. 地址对齐:每个分区起始地址都是1MB边界,计算和跳转都很方便
  2. 预留空间:即使未来应用程序增大,1MB的空间也足够容纳
  3. 隔离性好:Bootloader、Application、备份数据物理隔离,互不干扰

3. Bootloader设计与实现细节

Bootloader是整套系统的“大脑”,它需要在每次上电时做出关键决策:是运行现有程序,还是执行升级流程?我的Bootloader设计遵循“简单可靠”的原则,功能尽量精简,代码经过充分测试。

3.1 启动流程与状态判断

Bootloader的main函数逻辑是这样的:

int main(void) {
    // 初始化硬件
    hardware_init();
    
    // 读取外部Flash中的状态标志
    read_status_flags();
    
    // 判断是否需要备份当前程序
    if (status.backup_flag != 0xA5) {
        // 首次运行或Flash被擦除,执行备份
        backup_current_program();
    }
    
    // 判断是否需要更新程序
    if (status.update_flag == 0x55) {
        // 有新的程序需要更新
        update_new_program();
    }
    
    // 跳转到应用程序
    jump_to_application();
    
    // 正常情况下不会执行到这里
    while(1);
}

这里有个细节需要注意:backup_flag的判断。W25Q64擦除后的值是0xFF,所以如果读出来不是0xA5,就说明要么是第一次运行,要么是标志位区被意外擦除了。这种情况下Bootloader会主动备份当前程序,确保系统至少有一个可用的备份。

3.2 程序备份机制

备份过程的核心是数据校验。我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值