第2章:固件基础与文件格式

认识三种固件格式:.hex, .bin, .elf

做嵌入式这些年,我接触最多的就是这三种文件格式。很多新手上来就问:「到底该用哪个?」其实它们各有各的用途。

.bin 文件,说白了就是纯粹的二进制数据。没有地址信息,没有校验,就是一堆0和1的原始数据。我最早做单片机项目时,用的就是.bin。它体积最小,烧录也最快。但有个问题——你拿到一个.bin文件,根本不知道它该烧到哪个地址。

.hex 文件就不一样了。它里面包含了地址信息。每一行都告诉你「这段数据要放到哪个地址」。我习惯用Intel HEX格式,它用ASCII码表示二进制数据,可读性更好。你打开一个.hex文件,能看到类似这样的内容:

:10000000FFC000E8FFC001E8FFC002E8FFC003E8
:10001000FFC004E8FFC005E8FFC006E8FFC007E8
:00000001FF

每一行以冒号开头,后面跟着长度、地址、记录类型、数据,最后是校验和。

.elf 文件就复杂多了。它不光包含代码和数据,还有调试信息、符号表、重定位信息。我在调试阶段一定用.elf,因为它能让调试器知道「这个变量叫什么名字」「这个函数在哪个位置」。但烧录时,我通常会把.elf转成.hex或.bin再烧。

我的经验:量产烧录用.bin,开发调试用.elf,需要地址精确控制时用.hex。别搞混了。

固件结构解析

一个固件文件,内部到底长什么样?我拆开给你看。

典型的固件包含这几个部分:

  • 中断向量表:放在最前面,告诉CPU中断来了该跳到哪里
  • 代码段(.text):你的程序指令
  • 数据段(.data):已初始化的全局变量
  • BSS段(.bss):未初始化的全局变量(不占固件空间)
  • 只读数据段(.rodata):常量、字符串

我记得有一次,客户说固件烧进去跑不起来。我打开.hex文件一看,发现中断向量表地址偏移了。原来链接脚本里把起始地址设错了。你想想看,CPU一上电就跑到错误的地方取指令,能不挂吗?

这里有个关键点:固件在Flash中的布局。以STM32为例,Flash起始地址0x08000000,前4个字节是栈指针,接着4个字节是复位中断向量。如果这两个值不对,芯片根本启动不了。

避坑指南:我曾经花了一整天,就为了查一个「烧录成功但无法运行」的问题。最后发现是.hex文件里多了一行不该有的数据。所以,烧录前一定要用工具检查固件内容。

校验和的概念

校验和,听起来高大上,其实原理很简单。就是把你的一段数据加起来,得到一个值,然后把这个值附在数据后面。接收方用同样的方法算一遍,对比一下就知道数据有没有传错。

常见的校验方式有几种:

校验方式 原理 特点
简单累加和 所有字节相加,取低8位 实现简单,但容易漏错
CRC校验 多项式除法 检错能力强,工业常用
MD5/SHA 哈希算法 安全性高,但计算慢

在.hex文件里,每一行末尾都有个校验和。比如上面例子里的FF,就是前面所有字节的校验结果。如果烧录工具发现校验和不匹配,就会报错。

我做过一个OTA升级的项目,固件包用CRC32校验。每次下载完,设备自己算一遍CRC,跟固件包里的对比。不一致就拒绝升级。这招帮我挡掉了不少「升级变砖」的投诉。

注意:校验和只能检测错误,不能纠正错误。如果你需要纠错能力,得用更复杂的算法,比如汉明码或RS码。不过嵌入式场景下,校验和+重传机制已经够用了。

嗯,说到这我想起一个案例。有次产线反馈烧录良率低,我过去一看,发现是烧录器跟电脑的USB线太长,信号衰减导致数据传错。后来在固件里加了双重校验——先校验.hex文件本身,再校验烧录后的Flash内容。良率直接回到99.9%。

所以我的建议是:永远不要相信传输过程是可靠的。加个校验,花不了多少代码空间,但能省下无数排查时间。