3、刷写文件格式:S19文件格式解析、HEX文件格式解析、Bin文件格式解析
做嵌入式固件开发这么多年,我接触过不下十几种刷写文件格式。但真正在工业界广泛使用的,说白了就三种:S19、HEX和Bin。这三种格式各有各的脾气,选错了可是要出大问题的。
我记得刚入行那会儿,有一次把HEX文件当Bin文件直接烧录,结果芯片死活跑不起来。查了半天才发现,原来是文件格式搞混了。嗯,今天我就把这三种格式的底裤给你扒干净。
3.1 S19文件格式解析
S19格式,也叫Motorola S-record,是老牌半导体厂商摩托罗拉搞出来的。我最早接触它是在飞思卡尔(现NXP)的MCU上,后来发现很多车规级芯片都在用。
文件结构
S19文件是纯文本格式,每行都以大写字母'S'开头。我习惯把它拆成五个部分来看:
S00F000047656E6572696320535265636F7264F4
S1130000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
S1130010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
S5030003F9
S9030000FC
每行记录的结构是这样的:
| 字段 | 长度 | 说明 |
|---|---|---|
| 类型 | 2字节 | S0~S9,表示记录类型 |
| 长度 | 2字节 | 从地址开始到校验和之前的字节数 |
| 地址 | 2/3/4字节 | 数据存放的起始地址 |
| 数据 | N字节 | 实际的固件数据 |
| 校验和 | 1字节 | 取反加一校验 |
记录类型详解
我个人最常用的就这几种:
- S0:文件头记录,包含文件名或注释信息。我一般用它来标记固件版本号。
- S1:16位地址的数据记录,适合小容量芯片(64KB以内)。
- S2:24位地址的数据记录,覆盖16MB空间,我用的最多。
- S3:32位地址的数据记录,适合大容量或地址空间很大的芯片。
- S5:数据记录计数,告诉你前面有多少条S1/S2/S3记录。
- S9/S8/S7:结束记录,分别对应16/24/32位地址的终止符。
校验和计算
校验和算法其实很简单:从长度字段开始,到数据字段结束,所有字节求和后取反,再取低8位。我写过一个快速验证函数:
uint8_t calc_s19_checksum(const uint8_t *data, uint8_t len)
{
uint8_t sum = 0;
for (uint8_t i = 0; i < len; i++) {
sum += data[i];
}
return (~sum) & 0xFF;
}
3.2 HEX文件格式解析
Intel HEX格式,简称HEX文件,是英特尔搞出来的标准。说实话,这是我在项目中最常见的格式,几乎所有的MCU开发工具都支持它。
文件结构
HEX文件也是文本格式,每行以冒号':'开头。看个例子:
:10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:0400000500000000F7
:00000001FF
每行记录的结构:
| 字段 | 长度 | 说明 |
|---|---|---|
| 起始标记 | 1字节 | 固定为':' |
| 数据长度 | 1字节 | 本行数据的字节数 |
| 地址 | 2字节 | 16位起始地址 |
| 记录类型 | 1字节 | 00~05 |
| 数据 | N字节 | 实际固件数据 |
| 校验和 | 1字节 | 取反加一校验 |
记录类型详解
这里有六种记录类型,我挑重点说:
- 00 (数据记录):最常见的类型,存放固件数据。每行最多255字节,但工具通常限制在16或32字节。
- 01 (文件结束记录):文件末尾的标志,数据长度为0。
- 02 (扩展段地址):用于8086模式的段地址扩展,现在很少用了。
- 03 (起始段地址):指定代码的起始执行地址,也是古董级功能。
- 04 (扩展线性地址):这个很重要!32位地址的高16位。我经常用它来访问大容量Flash的高地址区域。
- 05 (起始线性地址):指定32位模式的起始执行地址,ARM芯片常用。
地址计算方式
HEX文件的地址计算有点绕,我画个图帮你理解:
// 扩展线性地址模式(推荐)
实际地址 = (扩展地址 << 16) | 记录中的16位地址
// 扩展段地址模式(不推荐)
实际地址 = (段地址 << 4) + 记录中的16位地址
我个人强烈建议只用扩展线性地址模式。段地址模式有地址重叠的问题,容易把人搞晕。
3.3 Bin文件格式解析
Bin文件,说白了就是固件的原始二进制数据。没有格式,没有校验,没有地址信息,就是纯粹的机器码。简单粗暴,但也最容易出问题。
文件特点
- 无头无尾:没有文件头,没有结束标记,就是纯数据。
- 无地址信息:文件本身不包含地址,烧录时必须指定起始地址。
- 无校验:没有内建的校验机制,传输错误只能靠外部手段检测。
- 体积最小:相比S19和HEX,Bin文件没有额外的格式开销,体积最小。
使用场景
我一般在以下场景用Bin文件:
- OTA升级:传输效率优先,Bin文件体积小,传输快。
- 量产烧录:产线工具通常直接烧录Bin文件,速度快。
- 加密固件:加密后的固件本身就是二进制数据,用Bin格式最合适。
3.4 三种格式对比
我整理了一张对比表,方便你快速选择:
| 特性 | S19 | HEX | Bin |
|---|---|---|---|
| 文件格式 | 文本 | 文本 | 二进制 |
| 地址信息 | 有 | 有 | 无 |
| 校验机制 | 有 | 有 | 无 |
| 文件体积 | 较大 | 中等 | 最小 |
| 可读性 | 中等 | 较好 | 差 |
| 适用场景 | 车规、工业 | 通用MCU | OTA、量产 |
3.5 格式转换实战
在实际项目中,我经常需要在三种格式之间转换。这里分享几个常用的方法:
使用SRecord工具
SRecord是开源工具,功能强大。我常用的命令:
# S19转HEX
srec_cat firmware.s19 -o firmware.hex -intel
# HEX转Bin
srec_cat firmware.hex -intel -o firmware.bin -binary
# Bin转S19(需要指定起始地址)
srec_cat firmware.bin -binary -offset 0x08000000 -o firmware.s19 -motorola
自己写转换函数
有时候工具不顺手,我就自己写个简单的转换函数。比如从HEX提取Bin数据:
int hex_to_bin(const char *hex_file, const char *bin_file)
{
FILE *fh = fopen(hex_file, "r");
FILE *fb = fopen(bin_file, "wb");
char line[256];
while (fgets(line, sizeof(line), fh)) {
if (line[0] != ':') continue;
int len = hex_to_byte(&line[1]);
int type = hex_to_byte(&line[3]);
if (type == 0x00) { // 数据记录
for (int i = 0; i < len; i++) {
uint8_t data = hex_to_byte(&line[9 + i*2]);
fwrite(&data, 1, 1, fb);
}
}
}
fclose(fh);
fclose(fb);
return 0;
}
好了,三种刷写文件格式就讲到这里。你想想看,理解了它们的底层结构,以后遇到刷写问题就能快速定位了。下一章我们聊聊密钥管理方案,那可是安全刷写的核心。