4、固件镜像格式:BIN文件、HEX文件、DFU包格式、自定义镜像头结构
好,咱们今天聊聊固件镜像格式。这玩意儿看着不起眼,但搞错了,轻则升级失败,重则设备变砖。我在蓝牙芯片项目里,至少见过三次因为镜像格式问题导致的线上事故。嗯,咱们一个一个说清楚。
4.1 BIN文件:最纯粹的二进制
BIN文件,说白了就是芯片内存的“照片”。它没有任何额外信息,从起始地址开始,一个字节挨着一个字节,原样烧录到Flash里。
特点:
- 没有地址信息,烧录时必须指定起始地址
- 文件大小就是固件实际大小
- 解析简单,传输效率高
我个人习惯在量产阶段用BIN格式。为什么?因为它干净利落,没有冗余数据。OTA传输时,少传一个字节都是省电。
重要提醒:BIN文件不包含校验信息。如果你只传BIN,接收端必须自己算CRC。我曾经有个项目,就是因为忘了加校验,结果传输过程中一个bit翻转,设备直接死机。
4.2 HEX文件:带地址的“地图”
HEX文件是Intel发明的格式,它把固件数据切分成一条条记录。每条记录都包含:长度、地址、类型、数据、校验和。
你想想看,为什么要有HEX?因为早期烧录器不知道固件该放哪儿。HEX文件自己告诉烧录器:“这段数据放0x08000000,那段放0x08001000”。
HEX记录类型:
| 类型码 | 含义 | 说明 |
|---|---|---|
| 00 | 数据记录 | 普通固件数据 |
| 01 | 文件结束 | 表示HEX文件到此结束 |
| 02 | 扩展段地址 | 用于16位地址空间扩展 |
| 04 | 扩展线性地址 | 用于32位地址空间扩展 |
举个例子,一条典型的HEX记录长这样:
:10000000FF0102030405060708090A0B0C0D0E0F0F0
解析一下:10是数据长度(16字节),0000是地址,00是数据记录类型,后面16字节是数据,F0是校验和。
我的经验:开发阶段用HEX很方便。因为你可以只修改某个地址段的数据,不用重新生成整个固件。但OTA时我建议转成BIN再传,HEX的文本格式会浪费30%左右的带宽。
4.3 DFU包格式:为升级而生
DFU(Device Firmware Upgrade)是Nordic、TI等芯片厂商定义的升级包格式。它不像BIN那么裸,也不像HEX那么啰嗦,而是专门为OTA场景设计的。
典型的DFU包结构:
+------------------+
| DFU Header | ← 固定长度,描述包信息
+------------------+
| Firmware Data | ← 实际固件内容
+------------------+
| Signature | ← 数字签名(可选)
+------------------+
| CRC32/ SHA256 | ← 完整性校验
+------------------+
DFU包的好处是:
- 自带校验,不用额外处理
- 支持分片传输,适合BLE小包通信
- 可以包含多个镜像(比如App + Bootloader + SoftDevice)
我记得有一次做Nordic nRF52832的OTA,官方SDK要求DFU包必须包含init packet。这个init packet里记录了固件版本、硬件兼容性、SD版本要求。如果版本不匹配,升级过程直接拒绝。这设计挺聪明的,避免了刷错固件导致变砖。
避坑指南:我曾经遇到一个坑——DFU包的签名验证。如果你开启了安全升级,但私钥丢了,那所有设备都没法再升级了。所以,私钥一定要备份,而且要放在安全的地方。
4.4 自定义镜像头结构
说实话,大部分商业项目最后都会走向自定义镜像头。为什么?因为芯片厂商给的格式不一定满足你的业务需求。
我自己设计的镜像头长这样:
typedef struct {
uint32_t magic; // 魔数,用于识别固件合法性
uint32_t version; // 固件版本号
uint32_t size; // 固件大小
uint32_t crc32; // 固件CRC校验
uint16_t hw_version; // 硬件版本兼容性
uint16_t reserved; // 保留字段
uint32_t timestamp; // 编译时间戳
uint8_t signature[64]; // ECDSA签名(可选)
} firmware_header_t;
每个字段的作用:
- magic:我习惯用0xDEADBEEF或0xA5A5A5A5。Bootloader启动时先检查魔数,不对就直接跳过。
- version:主版本.次版本.修订号,打包成一个32位整数。比如0x01020003表示v1.2.3。
- size:固件实际大小,用于Bootloader分配内存和校验完整性。
- crc32:对整个固件数据(不含头)做CRC。我建议用硬件CRC加速,软件算太慢。
- hw_version:防止把v2板的固件刷到v1板上。我吃过这个亏,那次差点报废一批货。
- timestamp:Unix时间戳。调试时能快速知道固件是什么时候编译的。
核心建议:自定义镜像头一定要预留扩展字段。我最初只留了4字节,后来想加个安全启动标志,发现没地方了。只能改结构,结果所有Bootloader都得跟着升级。嗯,这教训挺深刻的。
4.5 实际项目中的选择策略
说了这么多,到底该用哪种?我一般这么选:
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 开发调试 | HEX | 方便分段烧录,调试器支持好 |
| 量产烧录 | BIN | 速度快,工具链简单 |
| BLE OTA | DFU包 | 自带分片和校验,省心 |
| Wi-Fi OTA | 自定义镜像头+BIN | 带宽大,可以加更多业务逻辑 |
| 多芯片升级 | 自定义镜像头+多段数据 | 一个包搞定所有芯片固件 |
你想想看,如果你的产品只卖几百台,用官方DFU格式完全够用。但如果要卖几十万台,我建议还是花时间设计一套自己的镜像格式。为什么?因为后期要加的功能太多了——远程回滚、AB分区切换、增量升级……官方格式往往跟不上你的需求。
最后一个小技巧:无论用哪种格式,一定要在固件末尾加一个“结束标记”。我习惯用4字节的0xFFFFFFFF。这样Bootloader在扫描Flash时,能明确知道固件在哪里结束,避免读到垃圾数据。
好了,镜像格式这块就聊到这儿。下一节咱们讲讲升级流程设计,那才是真正考验架构能力的地方。