2. 固件基础概念:固件与软件的区别、固件文件格式、固件版本管理

大家好,我是你们这节课的讲师。咱们直接切入正题。做烧录工具开发,如果连固件本身都搞不清楚,那后面调试起来会非常痛苦。我个人习惯把固件理解成“半硬半软”的东西——它跑在芯片里,但又不像PC软件那样可以随便装。

2.1 固件与软件:到底差在哪?

很多人觉得固件就是软件,其实不完全对。我刚开始做嵌入式那会儿,也犯过这个迷糊。有一次把PC上的一个动态库直接扔到MCU里跑,结果可想而知——根本跑不起来。

咱们用一张表来对比,这样更清楚:

对比维度 固件 (Firmware) 软件 (Software)
存储介质 ROM、Flash、EEPROM 硬盘、SSD、光盘
更新频率 低,通常出厂后很少改 高,随时可以打补丁
运行环境 裸机或RTOS,直接操作寄存器 操作系统之上,依赖API
调试手段 JTAG/SWD、串口打印、逻辑分析仪 IDE断点、日志、性能分析器
出错后果 设备变砖,通常需要重新烧录 程序崩溃,重启即可恢复

核心区别一句话:固件是硬件和软件之间的“翻译官”,它直接控制硬件行为。软件出了问题可以重装,固件出了问题——嗯,你可能得拿编程器来救了。

为什么会这样?因为固件在编译时就已经确定了硬件资源的分配。你想想看,一个8KB的Flash空间,代码放进去就不能再改了。而PC软件有虚拟内存,有动态链接库,灵活得多。

2.2 固件文件格式:Hex、Bin、Elf

做烧录工具,天天跟这三种格式打交道。我见过不少新手把Hex当Bin直接烧,结果芯片不工作,还以为是硬件坏了。咱们一个一个说清楚。

2.2.1 Hex文件(Intel HEX格式)

Hex文件是文本格式,用ASCII字符表示二进制数据。每一行以冒号开头,包含长度、地址、记录类型、数据和校验和。

举个例子:

:020000040800F2
:10000000C0000000C1000000C2000000C3000000C4
:04000000C4000000C8
:00000001FF

这里要注意:Hex文件里包含了地址信息。烧录工具会解析这些地址,把数据放到正确的位置。我个人习惯用Hex做量产烧录,因为它自带校验,不容易出错。

小技巧:如果你用Hex文件烧录后芯片不工作,先检查一下起始地址对不对。我曾经遇到过客户把0x08000000的Hex烧到0x00000000,结果芯片直接“装死”。

2.2.2 Bin文件(二进制文件)

Bin文件就是纯粹的二进制数据,没有地址信息,没有校验。它是什么?就是芯片Flash里最终存储的内容,一个字节一个字节地排列。

比如上面那个Hex文件,转成Bin就是:

C0 00 00 00 C1 00 00 00 C2 00 00 00 C3 00 00 00 C4 00 00 00

Bin文件的好处是体积小,传输快。但坏处也很明显——你必须知道它应该烧到哪个地址。我建议在烧录工具里固定好起始地址,或者从配置文件中读取。

警告:Bin文件没有校验和,烧录过程中如果出现位错误,你根本不知道。所以用Bin文件时,一定要在烧录完成后做回读校验。

2.2.3 Elf文件(可执行与链接格式)

Elf文件是编译器的“原生”输出格式。它包含的信息最全:代码段、数据段、调试信息、符号表、重定位信息等等。

Elf文件的结构大致如下:

段名称 内容 烧录时是否需要
.text 代码指令 是
.data 已初始化的全局变量 是
.bss 未初始化的全局变量 否(运行时清零)
.debug 调试符号信息 否
.symtab 符号表 否

Elf文件在开发阶段非常有用,因为你可以用GDB加载它进行源码级调试。但量产时,我一般不用Elf——它太大了,而且包含很多无关信息。通常的做法是从Elf里提取出Hex或Bin。

2.3 固件版本管理

这一点我吃过不少亏。早期做项目时,版本号随便写,结果客户反馈问题,我根本不知道他手里烧的是哪个版本。后来我养成了几个习惯:

  1. 版本号嵌入固件:在代码里定义一个全局字符串,比如 const char* firmware_version = "v2.1.3",编译时自动更新。
  2. 烧录时记录版本:烧录工具在烧录完成后,把版本号和芯片ID写入日志文件。
  3. 支持版本回退:如果新固件有问题,能快速烧回旧版本。我建议保留最近3个版本的固件文件。

版本号的格式,我推荐语义化版本:

版本号 含义 举例
主版本号 不兼容的API修改 v2.0.0 → v3.0.0
次版本号 向下兼容的功能新增 v2.1.0 → v2.2.0
修订号 向下兼容的问题修正 v2.1.0 → v2.1.1

避坑指南:我曾经遇到过客户拿着一个v1.0.0的固件说有问题,结果我查了半天,发现他烧的是v0.9.0——版本号写错了。从那以后,我强制要求所有固件在启动时通过串口打印版本号,并且烧录工具必须校验版本号是否匹配。

2.4 三种格式的转换关系

为了让你更直观地理解这三种格式的关系,我画了一张图:

固件文件格式转换关系图 源代码 (.c/.h) 编译器 ELF 文件 (.elf) objcopy objcopy objcopy HEX 文件 (.hex) BIN 文件 (.bin) SREC 文件 (.srec) 说明:ELF是“母版”,Hex/Bin/SREC都是从ELF转换而来的“烧录格式” Hex和SREC包含地址信息,Bin是纯数据,需要指定烧录地址

从这张图你可以看到:源代码经过编译器生成ELF文件,然后通过objcopy等工具转换成Hex、Bin或SREC格式。ELF是“母版”,包含了所有信息;Hex和Bin是“烧录版”,只包含必要的数据。

我个人在开发阶段用ELF调试,在测试阶段用Hex验证,在量产阶段用Bin(配合地址配置文件)。这样既保证了灵活性,又提高了效率。

经验之谈:如果你在烧录时遇到“校验失败”的错误,先别急着怀疑硬件。用Hex文件烧录一次,再用Bin文件烧录一次,对比一下两次的结果。我遇到过好几次,都是因为Bin文件的起始地址设置错了。

好了,这一节的内容就到这里。固件基础概念是烧录工具开发的基石,搞清楚了这些,后面讲烧录协议和调试技巧时,你会觉得轻松很多。