第三讲:固件映像格式——你的升级包长什么样?

好,咱们进入第三讲。前面聊了协议框架和通信流程,今天来聊聊升级包里到底装了什么——也就是固件映像格式。

说白了,固件映像就是你要烧进芯片的那堆二进制数据。但直接扔一堆二进制过去?不行。你得告诉接收端:这是什么?从哪开始?对不对?有没有损坏?所以,我们需要一个结构化的格式。

固件映像的结构:头、身、尾

一个标准的固件映像,我习惯把它分成三部分:

  • 头部(Header):描述信息,相当于身份证
  • 数据区(Data):真正的固件代码和数据
  • 尾部(Trailer):收尾信息,比如校验值、填充字节

你想想看,没有头部,Bootloader拿到数据都不知道该往哪写。没有尾部,它也不知道数据传完了没有。这三者缺一不可。

核心原则:头部要足够简单,但信息要足够完整。我见过有人把头部搞成几百字节,里面塞了一堆调试信息——嗯,这在量产时就是浪费Flash。

头部信息:魔数、版本号、长度、校验和

头部里放什么?我总结四个核心字段:

字段 大小(常见) 说明
魔数(Magic Number) 4字节 用于识别这是不是合法的固件映像
版本号 4字节 主版本+次版本,或者干脆用时间戳
长度 4字节 数据区的总字节数
校验和 2~4字节 对整个映像(或头部+数据)的校验

魔数这东西,说白了就是一个固定值。比如我习惯用 0xDEADBEEF 或者 0xA5A5A5A5。Bootloader收到数据后先读前4字节,如果对不上,直接扔掉——省得后面解析一堆垃圾数据。

版本号怎么设计?我在项目中遇到过两种做法:

  • uint32_t 拆成主版本(高16位)和次版本(低16位)
  • 直接用编译时间戳(比如 0x20250314

我个人更推荐时间戳,因为版本号容易忘改,时间戳是自动生成的,不会重复。

长度字段要注意:它只算数据区,不包括头部和尾部。这样Bootloader拿到长度后,就知道要接收多少字节才算完。

校验和,嗯,这里要重点说。我曾经踩过一个坑:只校验了数据区,没校验头部。结果有一次头部被篡改了,魔数还是对的,但版本号被改成了旧版本,导致升级回退。后来我改成对整个映像(头部+数据)做CRC32校验,再也没出过问题。

避坑指南:我曾经在项目里只用简单的累加和做校验,结果发现两个不同映像的累加和居然一样。从那以后,我坚持用CRC32或者SHA256。别省这点计算量,安全第一。

常见映像格式:HEX、SREC、BIN

实际工作中,你会遇到三种主流格式。我按使用频率排个序:

1. BIN(二进制格式)

最纯粹的格式。没有头部,没有地址信息,就是纯二进制数据。Bootloader直接把它烧到指定地址就行。

  • 优点:体积最小,解析最快
  • 缺点:没有地址信息,必须事先知道烧录地址
  • 适用场景:量产烧录、OTA升级(配合头部信息)

我个人在OTA升级中几乎只用BIN格式。因为Bootloader的头部已经包含了地址和长度信息,不需要再额外解析地址了。

2. HEX(Intel HEX格式)

文本格式,每行以冒号开头,包含地址、数据类型、数据、校验和。比如:

:100000000C944A000C9451000C9458000C945F0071
:100010000C9466000C946D000C9474000C947B00A5
:100020000C9482000C9489000C9490000C949700D9
:00000001FF

每行解释:

  • : 起始标记
  • 10 本行数据长度(16字节)
  • 0000 起始地址
  • 00 数据类型(00=数据,01=结束)
  • 后面是16字节数据
  • 最后两位是校验和

优点:带地址信息,可以烧录到任意位置。支持分段(比如代码在0x8000,数据在0x9000)。

缺点:体积大(文本格式比二进制大2~3倍),解析复杂。

我记得第一次用HEX格式做升级时,解析代码写了整整两天。后来发现一个坑:有些编译器生成的HEX文件地址不连续,中间有空洞。Bootloader如果按顺序烧录,会把空洞也写进去——嗯,那Flash就废了。

小技巧:如果你必须用HEX做升级,建议先把它转成BIN再传输。PC端做转换,嵌入式端只解析BIN,省心很多。

3. SREC(Motorola S-record格式)

和HEX类似,也是文本格式。但它的地址范围更大(支持32位地址),而且每行以 S 开头。

S00A00006C65642E7372654338
S11300000C944A000C9451000C9458000C945F00B4
S11300100C9466000C946D000C9474000C947B0020
S5030003F9
S9030000FC

SREC的地址可以到4字节(32位),而HEX最大只支持16位地址(扩展线性地址记录可以到32位,但比较麻烦)。所以如果你的芯片Flash很大(比如几MB),SREC会更方便。

不过说实话,我在实际项目中很少直接用SREC做升级。它和HEX一样,解析起来比较费劲。除非是跟某些老旧的烧录器配合,否则我建议还是用BIN。

三种格式对比

特性 BIN HEX SREC
格式 二进制 文本(ASCII) 文本(ASCII)
体积 最小 较大(约2~3倍) 较大(约2~3倍)
地址信息 有(16位+扩展) 有(32位)
解析复杂度 极低 中等 中等
适用场景 OTA升级、量产 调试、烧录器 大地址空间、烧录器

我的建议

如果你在做Bootloader升级协议,我强烈推荐:

  • 传输用BIN:配合自定义头部,简单高效
  • 调试用HEX:方便看地址,也方便分段烧录
  • SREC:除非你的芯片地址空间超过16位,否则没必要

嗯,这一讲就到这里。下一讲我们聊聊升级流程中的状态机设计——那才是真正考验功底的地方。