4. HEX文件格式详解:Intel HEX记录类型、数据对齐与校验
HEX文件,做ECU刷写的没人不认识它。说白了,它就是一段段文本,描述了要把什么数据写到芯片的哪个地址去。我最早接触这玩意儿的时候,觉得它挺简单,不就是几行ASCII码嘛。后来踩了几个坑才发现,里面门道不少。
今天咱们就把Intel HEX格式掰开揉碎了讲。你想想看,刷写失败、校验不过,很多时候就是HEX文件本身有问题。搞懂了它,你才算真正入了ECU刷写的门。
4.1 HEX文件的骨架:记录类型
一个标准的HEX文件,每一行都是一条“记录”。每条记录长这样:
:10010000214601360121470136007EFE09D2190140
我习惯把它拆成五段来看:
- 冒号(:):每行的起始标记,没它就不算数
- 长度(1字节):表示后面数据域有多少个字节。比如上面的
10,就是16个字节 - 地址(2字节):这条数据要写入的起始地址。注意,这只是偏移地址
- 记录类型(1字节):核心中的核心,决定了这行数据怎么解释
- 数据域(N字节):真正的刷写数据
- 校验和(1字节):整条记录的“身份证”,算不对就报错
嗯,这里要注意:地址字段只有16位,最大只能表示64KB的范围。那ECU的Flash动不动就几兆甚至几十兆,怎么办?别急,后面有专门解决这个问题的记录类型。
4.2 五种记录类型,一个都不能少
Intel HEX定义了6种记录类型,但咱们做ECU刷写,真正打交道的就5种。我列个表,你一看就明白:
| 类型码 | 名称 | 说明 | ECU刷写中的用途 |
|---|---|---|---|
| 00 | Data Record | 数据记录,最常用的类型 | 存放实际的刷写数据 |
| 01 | End of File Record | 文件结束记录 | 告诉刷写工具:文件读完了 |
| 02 | Extended Segment Address | 扩展段地址记录 | 用于8086模式的地址扩展,现在用得少 |
| 03 | Start Segment Address | 起始段地址记录 | 指定代码的起始执行地址 |
| 04 | Extended Linear Address | 扩展线性地址记录 | 最常用的地址扩展方式,32位地址全靠它 |
| 05 | Start Linear Address | 起始线性地址记录 | 指定32位模式的起始执行地址 |
我个人最常用的是00、01和04。02和03在老的MCU上还能见到,现在的新项目基本都转04和05了。
4.2.1 Data Record(类型00)—— 真正的“干货”
这就是装数据的那条记录。每条最多装255个字节,但实际工具生成时,一般每行只放16个或32个字节。为什么?因为太长了不好读,也容易出错。
举个例子:
:10010000214601360121470136007EFE09D2190140
这条记录的意思是:从地址0x0100开始,写入16个字节的数据。数据内容就是21 46 01 36 ... 19 01。最后的40是校验和。
我在项目中遇到过一件事:有同事手动修改HEX文件,把数据改对了,但忘了重新算校验和。结果刷写工具直接报错,死活刷不进去。折腾了半天才发现是校验和不对。所以啊,改HEX文件一定要用专业工具,别手欠。
4.2.2 End of File Record(类型01)—— 刷写的“句号”
这个最简单,就一行:
:00000001FF
长度是0,地址是0,类型是01,校验和是FF。它告诉刷写工具:文件结束了,后面没内容了。没有这一行,有些刷写工具会一直等,或者直接报“文件不完整”。
4.2.3 Extended Linear Address(类型04)—— 搞定大地址
这是咱们最常用的地址扩展方式。它的原理很简单:用两个字节来指定高16位地址。
看个例子:
:020000040800F2
这条记录的意思是:从这条记录开始,后面的所有Data Record的地址,都要加上0x0800作为高16位。也就是说,如果后面有一条Data Record的地址是0x1000,那实际物理地址就是0x08001000。
为什么会这样?因为Data Record里的地址只有16位,最大只能到0xFFFF。而咱们ECU的Flash地址,比如STM32的从0x08000000开始,光靠16位地址根本不够用。所以需要用类型04来“抬高”基地址。
我建议你在解析HEX文件时,维护一个“当前基地址”变量。每遇到一条类型04记录,就更新这个变量。后面所有的Data Record,都用“基地址 + 偏移地址”来算实际地址。
4.2.4 Start Linear Address(类型05)—— 程序从哪开始跑
这个记录指定了程序的入口地址。对于ECU来说,就是复位向量。比如:
:0400000508000001F2
表示程序的起始地址是0x08000001。注意,有些编译器生成的HEX文件会带这个,有些不会。刷写工具一般会忽略它,因为ECU的启动地址是由芯片的硬件机制决定的,不是HEX文件说了算。
4.3 数据对齐:别让地址“错位”
做ECU刷写,数据对齐是个容易忽略但很重要的问题。我见过有人刷写失败,就是因为数据没有按4字节对齐。
什么叫对齐?简单说,就是数据的起始地址,最好是4的倍数。比如0x08000000、0x08000004、0x08000008这些。为什么?因为MCU的Flash控制器,一次读取就是4字节或8字节。地址不对齐,读写效率会下降,甚至直接报错。
我个人的习惯是:
- 每条Data Record的数据长度,尽量是4的倍数
- 每条Data Record的起始地址,尽量是4的倍数
- 如果遇到地址不对齐的情况,用0xFF填充补齐
举个例子,如果你的数据从0x08000001开始,只有3个字节。那我会建议你:
- 从0x08000000开始,写4个字节,第一个字节填0xFF,后面三个是真实数据
- 或者从0x08000001开始,写3个字节,但后面再补一个0xFF凑成4字节
具体怎么处理,取决于你的刷写工具和MCU的要求。但记住一条原则:不要留“尾巴”。地址不对齐的尾巴,最容易出问题。
4.4 校验和:HEX文件的“自检”机制
校验和的计算方法很简单:
校验和 = 0x100 - (长度 + 地址高字节 + 地址低字节 + 类型 + 所有数据字节) 的总和 & 0xFF
说白了,就是把整条记录(不包括冒号)的所有字节加起来,取低8位,然后用0x100减去这个值。如果结果是0x100,那就取0x00。
我写个伪代码你感受一下:
uint8_t calc_checksum(uint8_t* record, int len) {
uint8_t sum = 0;
for (int i = 0; i < len; i++) {
sum += record[i];
}
return (uint8_t)(0x100 - sum);
}
注意,这里的record是从长度字节开始算的,不包括冒号。校验和算出来之后,如果和记录里的校验和字节不一致,那这条记录就是坏的。
4.5 知识体系:一张图看懂HEX文件
下面这张图,是我自己总结的HEX文件解析流程。你照着这个思路走,基本不会出错:
这张图的核心逻辑就是:读一行,解析,判断类型,处理数据,校验,然后继续下一行。直到遇到类型01,结束。
4.6 实操中的几个小技巧
重要提醒:HEX文件里的地址是逻辑地址,不是物理地址。比如STM32的Flash物理地址从0x08000000开始,但HEX文件里可能写的是0x08000000,也可能写的是0x00000000加上类型04的0x0800。具体怎么映射,要看芯片的存储器映射和编译器的设置。
嗯,关于HEX文件格式,今天就讲这么多。你只要记住:记录类型决定了怎么解释数据,数据对齐决定了刷写是否顺利,校验和决定了文件是否完整。把这三点搞透了,HEX文件在你手里就是透明的。
可可精品资料库