4. 烧录文件格式:从Hex到Bin,你该知道的那些事

做嵌入式这些年,我见过太多新手在烧录文件格式上栽跟头。明明代码编译通过了,烧进去就是跑不起来——最后发现,文件格式选错了。今天咱们就把Hex、Bin、ELF这三兄弟彻底聊透。

一句话总结:Hex是“带地址的快递单”,Bin是“裸数据包裹”,ELF是“带说明书的高级货”。烧录时,绝大多数工具只认Hex和Bin。

烧录文件格式知识体系 Hex 文件 Bin 文件 ELF 文件 带地址信息 · ASCII文本 纯二进制 · 无地址 · 紧凑 调试信息 · 符号表 · 段信息 烧录首选 · 分段烧录 固件发布 · 整片烧录 调试器专用 · 不直接烧录 Hex ↔ Bin 可互相转换(地址信息会丢失或需指定) ELF → Hex/Bin 需通过 objcopy 等工具转换

4.1 Hex文件格式详解

Hex文件,全称Intel HEX文件。说白了,它就是用ASCII文本描述二进制数据的一种格式。每一行都是一条“记录”,告诉烧录工具:把什么数据写到哪个地址。

我刚开始做STM32项目时,第一次打开Hex文件,满屏的冒号和十六进制数看得我头皮发麻。后来摸清规律了,其实就那几种记录类型。

Hex文件的结构

每一行都以冒号开头,格式固定:

:10010000214601360121470136007EFE09D2190140
:10011000214601360121470136007EFE09D2190140
:04000005000000A5
:00000001FF

拆开来看,每一行由6个字段组成:

字段 长度 说明
起始码 1字节 固定为冒号 :
数据长度 1字节 本行有效数据的字节数
地址 2字节 数据在存储器中的起始地址
记录类型 1字节 00=数据, 01=结束, 02~05=扩展地址
数据 N字节 实际要烧录的二进制数据
校验和 1字节 前面所有字节的和取补码

我的小技巧:校验和的计算很简单——把冒号后面所有字节加起来,取低8位,再用0x100减掉它。如果结果不对,说明文件传输过程中损坏了。我曾经用这个方法抓出过一次串口线接触不良导致烧录失败的问题。

记录类型详解

Hex文件有6种记录类型,但常用的就3种:

  • 00 - 数据记录:最常见的类型,存放实际数据。地址是16位的,所以一条记录最多存64KB的数据。
  • 01 - 文件结束记录:Hex文件的最后一行,告诉工具“到这里就没了”。没有这一行,烧录工具会一直等下去。
  • 04 - 扩展线性地址记录:当地址超过64KB时,用这个记录来指定高16位地址。比如地址0x08000000,就需要先用04记录设置高地址。

注意:有些便宜的烧录器对Hex文件的地址范围有限制。我遇到过一款烧录器,地址超过0x100000就报错。后来发现是它的地址解析有bug,只处理了低20位。这种坑,踩过一次就记住了。

4.2 Bin文件格式详解

Bin文件,就是纯粹的二进制数据。没有地址信息,没有校验和,没有记录类型——就是一堆0和1的原始数据。

你想想看,如果把Hex比作带地址标签的快递包裹,那Bin就是一堆裸数据堆在那里。烧录工具必须知道“从哪个地址开始放”,才能正确烧录。

Bin文件的特点

  • 紧凑:没有额外开销,文件大小就是实际数据大小
  • 无地址:烧录时必须指定起始地址
  • 连续:数据必须是连续的,不能有空洞
  • 速度快:烧录时不需要解析格式,直接写入

实际经验:量产烧录时,我一般用Bin文件。因为Hex文件每行都要解析校验,速度慢不少。一条产线一天烧几千片,用Bin能省下半小时。但前提是——你得确保起始地址设置正确。

4.3 ELF文件格式简介

ELF(Executable and Linkable Format)是编译器直接生成的文件格式。它包含了调试信息、符号表、段信息等“高级内容”。

但说实话,烧录工具一般不直接处理ELF。为什么?因为ELF太“胖”了。一个几百KB的ELF,去掉调试信息后可能只有几十KB。烧录器只关心代码和数据,不关心你变量名叫什么。

我记得有一次,同事直接把ELF文件拖进烧录工具,结果工具报错“不支持的文件格式”。他一脸懵地问我为什么——嗯,因为那个工具只认Hex和Bin。

特性 ELF Hex Bin
调试信息 有 无 无
地址信息 有 有 无
文件大小 大 中等 小
烧录速度 慢 中等 快
适用场景 调试 开发/小批量 量产

4.4 Hex与Bin的区别与转换

两者的核心区别就一句话:Hex带地址,Bin不带地址。

这个区别在实际使用中影响很大:

  • 如果你的程序需要烧录到多个不同的地址区域(比如Bootloader在0x08000000,App在0x08020000),用Hex最方便——它自己知道该去哪。
  • 如果整个芯片只烧一个连续的固件,用Bin更省事。

如何转换?

最常用的工具是objcopy,它是GNU工具链的一部分:

# Hex 转 Bin
arm-none-eabi-objcopy -I ihex firmware.hex -O binary firmware.bin

# Bin 转 Hex(需要指定起始地址)
arm-none-eabi-objcopy -I binary -O ihex --change-addresses 0x08000000 firmware.bin firmware.hex

避坑指南:Bin转Hex时,--change-addresses参数必须给对。我曾经因为少写了一个0,把地址设成了0x0800000(少一位),结果烧进去后芯片直接变砖。还好是开发板,用ST-Link重新刷回来了。

除了objcopy,还有一些图形化工具也能做转换:

  • STM32CubeProgrammer:ST官方工具,支持Hex/Bin互转
  • J-Flash:SEGGER的工具,转换功能很强大
  • Hex2bin:一个开源小工具,命令行操作,轻量好用

4.5 生成烧录文件

在实际项目中,生成烧录文件通常有两种方式:

方式一:IDE自动生成

大多数IDE(Keil、IAR、STM32CubeIDE)在编译完成后,会自动生成Hex文件。你只需要在工程设置里勾选“生成Hex文件”选项。

以Keil为例:

  1. 打开工程选项(Project → Options for Target)
  2. 切换到Output标签页
  3. 勾选“Create HEX File”
  4. 重新编译,Hex文件就会出现在输出目录

方式二:手动生成

如果你用Makefile或者命令行编译,可以手动调用objcopy:

# 从ELF生成Hex
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex

# 从ELF生成Bin
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

我的习惯:开发阶段用Hex,因为烧录工具能校验地址是否正确。量产阶段用Bin,速度快。但不管用哪种,我都会在烧录前用hexdump或者xxd看一眼文件头,确认文件没损坏。这个习惯帮我避免过好几次“烧了一晚上,结果文件是空的”这种尴尬情况。