为什么MCU只认二进制,我们却一直在烧录HEX文件?
嵌入式开发最容易被忽略的底层真相
这是一个几乎每个嵌入式工程师都遇到过,但很少有人真正想透的问题:
我们每天用Keil、IAR编译生成HEX文件,用ST-Link、J-Link烧录到MCU里。但所有人都知道,MCU是数字电路,只能识别0和1组成的二进制机器码。
那为什么不直接烧录二进制文件?HEX文件到底是什么?烧录器在中间到底做了什么?
今天这篇文章,我会带你彻底搞懂这个嵌入式开发最核心的底层逻辑。读完之后,你不仅能理解HEX文件的本质,还能明白为什么它能统治嵌入式行业几十年,成为几乎所有MCU的标准烧录格式。
一、先破后立:纠正一个最常见的误解
结论先放在最前面:
🚨MCU绝对不能直接运行HEX文件!
你烧录到MCU Flash里的,永远是纯二进制机器码。HEX文件本身只是一个文本格式的"二进制数据包装器"。
这个误解非常普遍,甚至很多工作了几年的工程师都没搞清楚。HEX文件不是什么"高级格式",也不是MCU能识别的特殊代码,它只是为了解决纯二进制文件在传输、存储和烧录过程中的一系列痛点而设计的。
二、两个核心概念的本质区别
要理解这个问题,我们必须先搞清楚MCU和HEX文件各自的本质是什么。
2.1 MCU的本质:纯二进制处理器
MCU的核心是CPU、Flash存储器和RAM,这些都是由晶体管组成的数字电路。数字电路只有两种稳定状态:高电平和低电平,分别对应二进制的1和0。
这意味着:
- MCU只能理解由0和1组成的二进制字节流
- 所有指令、数据、地址都必须以二进制形式存在
- 程序必须存储在Flash的连续或指定地址上
- 复位后,MCU会从固定的起始地址(如STM32的0x08000000)开始取指执行
2.2 HEX文件的本质:ASCII文本文件
这是最反直觉的一点:Intel HEX格式是纯文本文件。
你可以用任何文本编辑器(记事本、VS Code、Vim)打开一个HEX文件,看到的都是这样的内容:
:100000000C9434000C9451000C9451000C9451007C :100010000C9451000C9451000C9451000C9451006C :100020000C9451000C9451000C9451000C9451005C :100030000C9451000C9451000C9451000C9451004C :00000001FF没有任何不可见字符,全都是:、0-9和A-F这些ASCII字符。每一行都遵循严格的格式规范,用来描述一个非常简单的指令:把哪些二进制数据,写到MCU的哪个内存地址。
三、为什么我们不用纯二进制文件烧录?
既然MCU只认二进制,那为什么不直接生成和烧录.bin文件呢?这是一个非常好的问题。
纯二进制文件确实是MCU最终执行的原始数据,但它有三个致命缺陷,导致几乎所有现代MCU烧录工具都优先支持HEX格式。
3.1 详细对比:HEX vs BIN
| 特性 | 纯二进制文件(.bin) | Intel HEX文件(.hex) |
|---|---|---|
| 文件类型 | 二进制文件 | ASCII文本文件 |
| 核心内容 | 只有原始机器码 | 机器码+地址信息+校验和+记录类型 |
| 地址信息 | ❌ 无,必须手动指定烧录起始地址 | ✅ 每一行自带目标内存地址 |
| 错误检测 | ❌ 无,传输损坏无法发现 | ✅ 每一行都有校验和,自动检测错误 |
| 不连续数据 | ❌ 必须用0填充空白区域,文件体积大 | ✅ 可以只包含有效数据段,跳过空白Flash |
| 可读性 | ❌ 无法用文本编辑器查看 | ✅ 可以直接打开查看和编辑 |
| 跨平台性 | ❌ 不同系统换行符、编码可能导致问题 | ✅ 纯ASCII文本,完美跨平台 |
3.2 一个例子看懂BIN文件的痛点
假设我们有一个程序,它的代码分布在两个不连续的Flash区域:
- 0x08000000 - 0x080000FF:中断向量表(256字节)
- 0x08001000 - 0x08001FFF:应用代码(4096字节)
如果生成BIN文件,中间的0x08000100 - 0x08000FFF这3840字节的空白区域必须用0填充,最终BIN文件大小是4352字节。
而生成HEX文件,只需要两行数据记录,分别描述这两个区域的内容,文件大小不到10KB,而且不需要任何填充。
更严重的是,如果你烧录BIN文件时不小心把起始地址设成了0x08000001,整个程序就会完全错乱,而且你根本不知道哪里出了问题。
四、一行HEX文件的完整解析
Intel HEX格式的设计非常精妙,每一行都包含了烧录所需的全部信息。我们以最常见的一行数据为例,彻底拆解它的结构。
4.1 标准HEX行格式
:100000000C9434000C9451000C9451000C9451007C这一行可以精确地拆分成6个部分,每个部分都有明确的含义:
| 部分 | 长度(字符) | 示例内容 | 含义解释 |
|---|---|---|---|
| 起始符 | 1 | : | 每一行的固定开头,ASCII码为0x3A |
| 数据长度 | 2 | 10 | 本行包含16个字节的二进制数据(0x10=16) |
| 起始地址 | 4 | 0000 | 这些数据要写入的Flash起始地址是0x0000 |
| 记录类型 | 2 | 00 | 00=数据记录,01=文件结束,04=扩展线性地址 |
| 数据区 | 可变 | 0C943400... | 二进制数据的十六进制文本表示 |
| 校验和 | 2 | 7C | 本行所有字节的校验和,用于验证数据完整性 |
4.2 最关键的转换过程
这是整个问题的核心:烧录器如何把文本字符转换成二进制字节?
答案非常简单:每两个ASCII字符对应一个二进制字节。
例如:
- 文本字符
0C→ 二进制字节00001100(十进制12,十六进制0x0C) - 文本字符
94→ 二进制字节10010100(十进制148,十六进制0x94) - 文本字符
34→ 二进制字节00110100(十进制52,十六进制0x34)
所以上面那行HEX数据,经过转换后会变成16个连续的二进制字节:
0x0C, 0x94, 0x34, 0x00, 0x0C, 0x94, 0x51, 0x00, 0x0C, 0x94, 0x51, 0x00, 0x0C, 0x94, 0x51, 0x004.3 校验和的计算方法
校验和是HEX文件可靠性的关键。它的计算规则是:
- 把本行除了起始符
:和校验和本身之外的所有字节相加 - 取结果的低8位
- 对结果取反加1(即求补码)
我们用上面的例子手动计算一下:
0x10 + 0x00 + 0x00 + 0x00 + 0x0C + 0x94 + 0x34 + 0x00 + 0x0C + 0x94 + 0x51 + 0x00 + 0x0C + 0x94 + 0x51 + 0x00 + 0x0C + 0x94 + 0x51 + 0x00 = 0x384- 低8位:0x84
- 取反加1:0x7C
和HEX行末尾的校验和7C完全一致!如果传输过程中任何一个字符出错,校验和都会不匹配,烧录器会立即报错。
4.4 常见的记录类型
Intel HEX格式定义了多种记录类型,最常用的有三种:
| 记录类型 | 代码 | 用途 |
|---|---|---|
| 数据记录 | 00 | 包含实际的程序数据和目标地址 |
| 文件结束记录 | 01 | 标记HEX文件的结束,通常是最后一行:00000001FF |
| 扩展线性地址记录 | 04 | 用于32位地址空间,指定高16位地址 |
对于STM32这类32位MCU,Flash起始地址是0x08000000,所以HEX文件开头通常会有这样一行:
:020000040800F2这行的意思是:设置高16位地址为0x0800,后面所有数据记录的地址都是0x0800xxxx。
五、完整的烧录流程:从代码到MCU执行
现在我们把整个过程串起来,看看一行C代码是如何最终变成MCU上运行的程序的。
5.1 四步烧录法
编译链接阶段
- 编译器(armcc、gcc)把C/C++代码转换成汇编代码
- 汇编器把汇编代码转换成目标文件(.o),包含二进制机器码
- 链接器把多个目标文件和库文件合并,生成ELF文件,包含完整的地址信息
格式转换阶段
- 工具链(arm-none-eabi-objcopy、fromelf)把ELF文件转换成HEX格式
- 提取ELF文件中的代码段、数据段和对应的地址信息
- 按照Intel HEX格式打包成文本行,并计算每一行的校验和
烧录解析阶段
- 烧录器(ST-Link、J-Link)读取HEX文件,逐行解析
- 验证每一行的校验和,如果错误则立即终止烧录
- 把文本格式的十六进制数据转换成二进制字节
- 根据行中的地址信息,通过SWD/JTAG接口,把二进制字节写入MCU Flash的对应地址
运行阶段
- 烧录完成后,烧录器发送复位命令给MCU
- MCU复位,从Flash的起始地址(0x08000000)读取第一条指令
- CPU开始逐条执行二进制机器码,程序正式运行
5.2 形象的比喻
整个过程就像"快递配送":
- 二进制数据是你买的商品
- HEX文件是包装好的快递盒,上面写着收件地址和快递单号
- 烧录器是快递员,负责把每个包裹送到正确的地址
- MCU Flash是收件人的家
快递员不会把整个快递盒直接塞进你家,他会拆开包装,把商品放到你指定的位置。同样,烧录器也不会把HEX文件直接写入Flash,它会解析HEX文件,把二进制数据放到正确的地址上。
六、常见误区澄清
误区1:“HEX文件比BIN文件高级”
错。两者只是不同的存储格式,包含的有效二进制数据完全相同。HEX只是多了地址、校验和等元信息。你可以用objcopy工具在HEX和BIN之间自由转换,不会丢失任何有效数据。
误区2:“烧录器直接把HEX文件写入Flash”
错。烧录器写入Flash的永远是纯二进制数据。HEX文件在烧录前已经被完全解析和转换。如果你用十六进制编辑器读取MCU的Flash,看到的会是二进制数据,而不是HEX文本。
误区3:“所有MCU都只能用HEX文件烧录”
错。大多数烧录器也支持BIN文件,但需要你手动指定烧录的起始地址。如果地址错误,程序将无法运行。对于简单的8位MCU(如51单片机),BIN文件也很常用。
误区4:“HEX文件的大小就是程序占用的Flash大小”
错。HEX文件是文本格式,每个二进制字节需要两个ASCII字符表示,再加上每行的额外开销,HEX文件的大小大约是实际Flash占用量的2.5倍。
七、实用技巧:HEX文件的高级用法
7.1 手动修改HEX文件
因为HEX是文本文件,你可以直接用文本编辑器修改它的内容。这在调试时非常有用,比如:
- 修改一个常量的值
- 跳过某段代码
- 修改中断向量表的地址
注意:修改数据后必须重新计算校验和,否则烧录器会报错。
7.2 合并多个HEX文件
你可以把多个HEX文件合并成一个,用于烧录包含Bootloader和应用程序的完整固件。只需要把各个HEX文件的内容按顺序拼接起来,然后删除多余的文件结束记录即可。
7.3 提取BIN文件
如果你需要BIN文件,可以用objcopy工具从HEX文件转换:
arm-none-eabi-objcopy-Iihex-Obinary firmware.hex firmware.bin八、总结
HEX文件是嵌入式开发历史上最成功的发明之一。它用一种极其简单优雅的方式,解决了纯二进制文件在传输、存储和烧录过程中的所有痛点。
它的核心思想是:用人类可读的文本形式,精确描述机器可读的二进制数据的布局。这种思想影响了整个计算机行业,我们今天使用的JSON、XML等格式,本质上都是同样的思路。
回到最初的问题:为什么MCU只认二进制,我们却一直在烧录HEX文件?
答案很简单:因为HEX文件不是给MCU看的,是给烧录器和人类看的。
MCU只需要知道"0和1",但我们需要知道"这些0和1应该放在哪里",以及"它们有没有在传输过程中损坏"。HEX文件完美地满足了这个需求。
互动环节
你在工作中遇到过哪些和HEX文件相关的坑?有没有手动修改过HEX文件来解决问题?欢迎在评论区分享你的经历。
如果这篇文章对你有帮助,欢迎点赞、收藏、转发给你的同事和朋友。
