第二章:硬件平台搭建:主控芯片选型、Flash分区规划与Bootloader设计原理

好,咱们正式开始动手搭建硬件平台。这一章我打算把三个最核心的决策点讲透:主控芯片怎么选、Flash怎么分区、Bootloader怎么设计。这三个东西决定了你整个远程升级系统的地基牢不牢。

我在做第一个电子标签项目时,就吃过芯片选型的亏。当时图便宜选了某款低端MCU,结果Flash不够用,Bootloader和应用程序挤在一起,升级到一半就崩了。嗯,从那以后我再也不敢轻视硬件规划了。

2.1 主控芯片选型:ESP32 vs STM32

选芯片这事儿,说白了就是看你的产品要什么。我一般会从三个维度去卡:通信能力、存储资源、成本功耗

对比项 ESP32 STM32(以F4/H7系列为例)
WiFi/BLE 原生集成,无需外挂 需外接WiFi模块(如ESP8266)
Flash大小 通常4MB~16MB 256KB~2MB(内部),可外扩
OTA支持 官方ESP-IDF自带OTA机制 需自己实现或移植
功耗 活跃模式较高,深度睡眠不错 低功耗模式更成熟
开发难度 上手快,生态好 资料多,但底层配置复杂
成本 约15~30元(含Flash) 10~50元(视型号)

我的建议:

  • 如果你的电子标签需要无线联网(WiFi/BLE),直接选ESP32。省掉一个通信模块,BOM成本能降20%。
  • 如果产品对实时性、低功耗、工业级稳定性要求极高,比如工厂产线上的标签,我倾向STM32 + 外挂WiFi模块。
  • 我个人习惯:原型验证用ESP32,量产时根据功耗和成本再决定是否切到STM32。

你想想看,一个电子标签如果每次升级都要插线,那还叫「远程」吗?所以芯片必须支持OTA。ESP32在这方面简直是开箱即用,而STM32你得自己写Bootloader,或者用ST官方的IAP例程改。

2.2 Flash分区规划:别让空间成为瓶颈

Flash分区,说白了就是给不同的程序和数据划分地盘。我见过太多人把整个Flash当成一个区,结果升级时旧程序被覆盖,新程序又没写完,直接变砖。

为什么会这样?因为没有预留备份区

我一般会这样规划一个典型的4MB Flash(以ESP32为例):

分区名称 起始地址 大小 用途
bootloader 0x1000 48KB 一级Bootloader,不可变
app_primary 0x10000 1.5MB 主应用程序(运行区)
app_backup 0x190000 1.5MB 备份应用程序(升级备用)
config 0x320000 64KB 设备配置参数
log 0x330000 64KB 运行日志(循环写入)
ota_data 0x340000 16KB OTA状态标记

避坑指南:

我曾经在STM32上只分了两个区:Bootloader和App。结果有一次升级过程中断电,App区写了一半,重启后Bootloader发现App校验失败,直接死循环。后来我加了一个备份区,升级时先写备份区,校验通过后再交换指针。从此再没出过问题。

对于STM32,Flash通常较小(比如512KB),我建议这样分:

  • Bootloader:32KB(足够放IAP代码)
  • App区:384KB
  • 备份区:64KB(只存关键参数,不存完整App)
  • 配置区:32KB

嗯,这里要注意:备份区的大小取决于你的升级策略。如果是全量升级,备份区必须和App区一样大。如果是差分升级,备份区可以小很多。

2.3 Bootloader设计原理:升级的守门员

Bootloader,就是上电后第一个跑的程序。它的任务很简单:决定这次该跑哪个App

我设计Bootloader时,会遵循这几个原则:

  1. 最小化原则:Bootloader只做一件事——检查升级标记并跳转。不要塞入驱动、协议栈等复杂逻辑。
  2. 容错优先:如果App校验失败,Bootloader不能死机,要能回滚到上一个可用版本。
  3. 通信接口抽象:Bootloader要支持多种升级方式(串口、WiFi、蓝牙),但内部逻辑统一。

下面是一个简化的Bootloader流程图,我用伪代码表示:

void bootloader_main() {
    // 1. 检查OTA标记
    if (check_ota_flag() == OTA_PENDING) {
        // 2. 从备份区复制新固件到主区
        if (copy_firmware(APP_BACKUP, APP_PRIMARY) == SUCCESS) {
            // 3. 校验主区固件
            if (verify_firmware(APP_PRIMARY) == PASS) {
                clear_ota_flag();
                jump_to_app(APP_PRIMARY);
            } else {
                // 4. 校验失败,回滚
                rollback_to_previous();
            }
        }
    } else {
        // 5. 无升级标记,直接跳转
        if (verify_firmware(APP_PRIMARY) == PASS) {
            jump_to_app(APP_PRIMARY);
        } else {
            // 6. 主区损坏,尝试备份区
            if (verify_firmware(APP_BACKUP) == PASS) {
                jump_to_app(APP_BACKUP);
            } else {
                // 7. 都坏了,进入恢复模式
                enter_recovery_mode();
            }
        }
    }
}

重要提醒:

Bootloader的代码必须放在写保护区域。我见过有人把Bootloader和App放在同一个可擦写区域,结果升级时误擦了Bootloader,整个设备变砖。ESP32的Bootloader默认在0x1000,且硬件写保护,但STM32需要你自己配置选项字节来保护。

我个人习惯在Bootloader里加一个看门狗。为什么?因为升级过程中如果卡死,看门狗会复位,Bootloader重新运行,就能进入回滚流程。没有看门狗,设备可能永远卡在升级的半路上。

另外,升级标记的设计也很关键。我一般用两个标记:

  • 升级请求标记:由App写入,告诉Bootloader「我要升级」。
  • 升级完成标记:由Bootloader写入,告诉App「新固件已就绪」。

这两个标记放在OTA数据区,每次操作前先擦除再写入,防止意外断电导致标记状态混乱。

2.4 小结:硬件平台搭建的三大铁律

好了,这一章的内容就这些。我总结一下:

  • 芯片选型:ESP32适合快速原型和无线场景,STM32适合工业级和低功耗场景。别盲目跟风,看需求。
  • Flash分区:至少分三个区——Bootloader、App主区、App备份区。备份区是救命稻草。
  • Bootloader设计:保持简单、容错优先、做好写保护。记住,Bootloader是升级系统的最后一道防线。

下一章,我会带你搭建实际的开发环境,并写一个能跑起来的Bootloader示例。到时候咱们再细聊代码层面的坑。

公众号:蓝海资料掘金营,微信deep3321