第二章:硬件平台搭建:主控芯片选型、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时,会遵循这几个原则:
- 最小化原则:Bootloader只做一件事——检查升级标记并跳转。不要塞入驱动、协议栈等复杂逻辑。
- 容错优先:如果App校验失败,Bootloader不能死机,要能回滚到上一个可用版本。
- 通信接口抽象: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