资讯详情

资讯详情

Keil编译产物hex与bin格式区别、转换方法及烧录选型全解析

1. 从一句吐槽说起hex 和 bin 到底差在哪“Keil 编译的 hex 和 bin 都不会区分你学个啥”这句话我第一次听到的时候正坐在实验室里对着一个烧录失败的板子发愁。当时我以为只要 Keil 编译通过、生成了文件往单片机里一烧就完事了结果烧进去之后板子毫无反应串口一个字节都不吐。折腾了大半天才发现问题出在我把 hex 文件当成 bin 文件用了烧录器按纯二进制的方式去解析一个带地址信息的文本文件结果自然是一塌糊涂。这件事让我意识到很多人包括当年的我在学 Keil、学 STM32、学单片机的时候把大量精力花在了“怎么新建工程”“怎么装芯片包”“怎么点编译按钮”上却对编译产物本身一无所知。hex 和 bin 这两个文件几乎每个嵌入式开发者每天都会见到但真正能说清楚它们区别的人并不多。这篇文章就围绕这个看似基础、实则暗藏玄机的话题展开把 Keil 编译产物的来龙去脉、格式差异、转换方法、烧录选型、常见坑点全部讲透。不管你是刚接触 Keil MDK 的新手还是已经能独立建工程、跑通点灯程序但没深究过文件格式的进阶玩家这篇内容都能帮你把这块知识补上。我会从文件格式的底层结构讲起一直讲到实际项目里怎么选、怎么转、怎么排查问题尽量做到看完就能上手用。2. 先搞清楚 Keil 到底产出了些什么2.1 编译流程与产物清单Keil MDK 的编译流程大致是这样的源文件.c/.s经过编译器Arm Compiler编译成目标文件.o再经过链接器Linker把多个目标文件和库文件链接成可执行文件最后通过 fromelf 工具生成最终的烧录文件。整个链条里你会看到一堆扩展名不同的文件很多人只盯着最后的 hex 或 bin中间的产物一概不管出了问题就抓瞎。一个典型的 Keil 工程编译完成后输出目录里通常会有这些文件文件类型扩展名作用说明目标文件.o单个源文件编译后的机器码和符号信息链接脚本.sct分散加载文件决定代码和数据放在哪块内存可执行文件.axf带调试信息的完整可执行文件仿真和调试用映射文件.map记录各段地址、大小、符号分布排查内存问题必备十六进制文件.hex带地址信息的文本格式烧录文件二进制文件.bin纯二进制镜像无地址信息列表文件.lst汇编与源码对照调试底层问题时用这里面 .axf 是“母体”hex 和 bin 都是从它派生出来的。理解这一点很关键hex 和 bin 不是并列关系而是同一个可执行文件在不同用途下的两种导出形式。.axf 里包含了完整的段信息代码段、数据段、只读数据段等fromelf 工具根据这些段信息要么生成带地址记录的 hex要么生成去掉所有元信息的纯 bin。2.2 为什么 Keil 默认只给 hex很多人第一次发现 Keil 只生成 hex 不生成 bin 的时候会纳闷其实这是有历史原因的。hex 格式Intel HEX诞生于上世纪七十年代那时候的烧录器、编程器普遍支持这种带地址信息的文本格式因为它可以描述非连续的内存分布烧录器能根据地址记录把数据放到正确的位置。而 bin 是纯粹的地址连续镜像它假设你从某个固定起始地址开始烧中间不能有空洞。Keil 默认勾选“Create HEX File”是因为 hex 的兼容性更好几乎任何烧录工具都认。但 bin 在很多场景下更受欢迎比如 OTA 升级、量产烧录、给第三方工具处理等因为 bin 体积更小、解析更简单。所以实际项目里经常需要同时产出 hex 和 bin或者根据下游工具的要求二选一。2.3 一个容易被忽略的事实hex 不是二进制这是新手最容易踩的认知坑。hex 文件打开一看全是 ASCII 字符每行以冒号开头后面跟着一串十六进制数字。它本质上是文本文件描述的是“在地址 X 处写入数据 Y”这样的记录。而 bin 文件打开是乱码因为它就是纯粹的字节流没有任何地址和格式信息。这个区别直接决定了两者的使用方式hex 可以带着地址信息烧到任意位置烧录器会自己解析bin 必须由你告诉烧录器“从哪个地址开始烧”否则烧错位置就是必然的。我见过太多人拿着 bin 文件问“为什么烧进去不运行”十有八九是起始地址设错了。3. hex 文件格式深度拆解3.1 Intel HEX 的记录结构Intel HEX 文件的每一行都是一条记录格式固定为:LLAAAATT[DD...]CC逐段解释一下:是每行的起始标志固定不变LL是数据长度表示这一行携带多少个字节的数据用两位十六进制表示AAAA是数据要写入的起始地址十六位TT是记录类型决定这条记录是干什么的DD...是实际的数据字节CC是校验和用来验证这一行有没有传输出错校验和的算法是把 LL、AAAA、TT、DD 所有字节相加取低八位再用 0x100 减去这个值。这个细节在写 hex 解析工具的时候必须搞清楚否则解析出来的数据全是错的。3.2 六种记录类型分别是什么Intel HEX 定义了六种记录类型实际工程里最常见的是前四种类型码名称作用00数据记录携带实际的程序数据01文件结束记录标记文件结尾固定为:00000001FF02扩展段地址记录扩展地址的高十六位段寻址方式03起始段地址记录指定 CS:IP 起始执行地址04扩展线性地址记录扩展地址的高十六位线性寻址方式05起始线性地址记录指定 32 位起始执行地址对于 Cortex-M 系列单片机你会在 hex 文件里看到类型 04 和类型 05。类型 04 用来设置高十六位地址让后面的数据记录能覆盖 32 位地址空间类型 05 则记录了程序的入口地址也就是复位后 PC 应该跳转的地方。这个类型 05 记录非常关键它对应热搜词里的“hex start linear address record”很多烧录异常都和它有关。3.3 一个真实的 hex 片段分析拿一个 STM32 点灯程序的 hex 文件开头举例:020000040800F2 :1000000000F00020C1000008C5000008C9000008B8 :10001000CD000008D10000080000000000000000A8 ... :04000005080000C1F2 :00000001FF第一行:020000040800F2是类型 04 记录表示后面的数据地址高十六位是 0x0800也就是从 0x08000000 开始这正是 STM32 的 Flash 起始地址。第二行开始是数据记录把实际的机器码写入对应地址。倒数第二行:04000005080000C1F2是类型 05 记录表示程序入口地址是 0x080000C1注意最低位是 1说明是 Thumb 状态。最后一行是固定的结束记录。看懂这个结构之后你就能明白为什么 hex 文件比 bin 大——它多了地址、类型、校验这些元信息体积通常是 bin 的两倍多。但换来的是更强的自描述能力烧录器不需要额外配置就能知道数据该放哪。3.4 为什么 hex 能描述非连续内存这是 hex 相对 bin 的核心优势。有些芯片的 Flash 和配置区不连续比如程序放在 0x08000000但某些配置数据要放到 0x0801F800 的独立扇区。用 bin 的话中间那段空洞也得用 0xFF 填充文件体积白白增大而 hex 可以直接跳过空洞只记录有数据的地址段。在 Keil 里如果你的工程用了分散加载文件.sct把代码和数据分配到不连续的地址生成的 hex 就会体现这种跳跃。而如果你强行转成 binfromelf 默认会从最低地址一直填到最高地址中间的空洞全部补 0xFF文件可能膨胀好几倍。这一点在做 Bootloader App 双区方案时特别要注意。4. bin 文件简单粗暴但处处是坑4.1 bin 的本质就是一段字节流bin 文件没有任何格式它就是一段连续的字节。你把它读进内存第一个字节对应起始地址第二个字节对应起始地址加一以此类推。没有地址记录没有校验没有结束标志什么都没有。这种极简设计带来两个后果一是文件体积最小适合传输和存储二是所有地址信息都得靠外部约定一旦约定错了烧进去的数据就全错位。我见过有人把 bin 文件用十六进制编辑器打开发现开头几个字节是00 F0 00 20还以为是文件损坏了其实那是栈顶地址是正常的向量表内容。4.2 bin 的起始地址从哪来bin 文件本身不告诉你起始地址这个信息必须从别的地方获取。常见来源有三个第一个是 Keil 的分散加载文件。打开工程的 .sct 文件你会看到类似0x08000000 0x00040000 { ... }的配置这里的 0x08000000 就是代码段的起始地址转 bin 时默认从这里开始。第二个是 .map 文件。map 文件里会列出各个段的加载地址和执行地址找到 ROM 区域的起始地址即可。第三个是芯片手册。不同芯片的 Flash 起始地址不一样STM32 通常是 0x08000000GD32 也是但有些国产芯片可能是 0x00000000 或其他值必须查手册确认。提示转 bin 之前一定要确认起始地址转完之后最好用十六进制编辑器看一眼开头几个字节和 map 文件里的向量表对照一下确认无误再烧录。4.3 bin 体积为什么比 hex 小前面提过hex 每行都有冒号、长度、地址、类型、校验这些额外字符一个字节的数据往往要占两三个字节的文本空间。而 bin 是纯数据一个字节就是一个字节。粗略估算同样内容的 hex 文件体积大约是 bin 的 2.2 到 2.5 倍。这个差异在 OTA 升级场景下很关键。假设你的固件 bin 是 100KB那 hex 可能就是 230KB 左右传输时间和存储空间都差一倍多。所以做 OTA 的时候几乎都是用 bin 而不是 hex。但反过来说bin 没有校验信息传输过程中如果出错接收端很难发现通常需要额外加 CRC 校验。5. Keil 里怎么同时生成 hex 和 bin5.1 图形界面配置 hex 生成在 Keil 里生成 hex 是最简单的很多人第一次建工程就勾了这个选项。具体路径是Options for Target - Output 选项卡 - 勾选 “Create HEX File”。勾上之后重新编译输出目录里就会出现 .hex 文件。这里有个细节值得注意Output 选项卡里还有一个 “Create Batch File” 选项勾上之后会生成一个 .bat 批处理文件里面记录了编译和链接的完整命令。这个文件在排查编译问题时很有用你可以看到 Keil 到底调用了哪些工具、传了哪些参数。5.2 用 fromelf 生成 bin 的完整配置Keil 默认不生成 bin需要手动配置。方法是在 Options for Target - User 选项卡里找到 “After Build/Rebuild” 区域勾选 “Run #1”然后在命令框里填入 fromelf 的调用命令fromelf --bin --output./Objects/your_project.bin ./Objects/your_project.axf这里的路径要根据你的实际工程结构调整。--bin表示输出二进制格式--output指定输出文件路径最后是要处理的 .axf 文件。配置好之后重新编译bin 文件就会自动生成。如果你还想同时生成 hex可以把命令改成fromelf --bin --output./Objects/your_project.bin ./Objects/your_project.axf fromelf --i32 --output./Objects/your_project.hex ./Objects/your_project.axf--i32表示输出 Intel 32 位 HEX 格式也就是我们常说的 hex 文件。这样一条配置就能同时产出两种格式省得来回切换。5.3 fromelf 常用参数速查fromelf 是 Arm 工具链里的格式转换工具功能远不止转 bin 和 hex。整理几个常用的参数参数作用--bin输出纯二进制文件--i32输出 Intel 32 位 HEX 文件--vhx输出 Verilog HEX 格式FPGA 场景用--text输出反汇编文本--outputfile指定输出文件名--baseaddr指定 bin 的起始地址-c反汇编时同时显示源码-d只反汇编数据段其中--base参数在转 bin 时特别有用。如果你的代码起始地址不是默认值或者你想把 bin 的起始地址对齐到某个边界可以用这个参数指定。比如--base0x08000000就明确告诉 fromelf 从 0x08000000 开始生成 bin。5.4 多 Target 方案下的产物管理实际项目里一个工程经常有多个 Target比如 Debug 和 Release或者 Bootloader 和 App。每个 Target 的输出目录和产物名称最好区分开否则转 bin 的时候容易搞混。我的做法是在每个 Target 的 Output 选项卡里把输出目录设成不同的文件夹比如./Objects/Debug/和./Objects/Release/然后在 User 选项卡里用相对路径调用 fromelf。这样编译完 Debug 就生成 Debug 的 bin编译 Release 就生成 Release 的 bin互不干扰。另外如果工程里有多个 Target 对应不同的芯片型号或不同的功能配置建议在 fromelf 命令里用$L这样的 Keil 内置变量来引用当前 Target 的输出文件避免硬编码文件名导致换 Target 后命令失效。6. hex 和 bin 的互相转换与验证6.1 hex 转 bin 的几种方法有时候你手里只有 hex 文件但下游工具只认 bin这时候就需要转换。常见方法有三种第一种是用 fromelf但 fromelf 主要处理 .axf对 hex 的支持有限通常不推荐。第二种是用专门的转换工具比如 hex2bin、srec_cat 等。hex2bin 是个轻量级命令行工具用法很简单hex2bin input.hex它会自动解析 hex 里的地址信息生成对应的 bin 文件。如果 hex 里有非连续地址hex2bin 默认会用 0xFF 填充空洞也可以用参数控制。第三种是用 Python 脚本自己解析。Intel HEX 格式很规整写个百来行的解析脚本并不难而且可以完全按自己的需求处理地址空洞、校验和验证等细节。对于需要批量处理或集成到自动化流程里的场景自己写脚本反而更灵活。6.2 bin 转 hex 的注意事项bin 转 hex 相对少见但也不是没有。比如你从某个渠道拿到一个 bin 固件想用只支持 hex 的烧录器烧录就得转一下。转换的关键是必须指定起始地址因为 bin 本身不带地址信息。用 srec_cat 可以这样转srec_cat input.bin -binary -offset 0x08000000 -o output.hex -intel-offset 0x08000000就是指定起始地址-intel表示输出 Intel HEX 格式。转完之后一定要用十六进制编辑器对比一下确认地址和数据都对得上。6.3 转换后的验证方法转换完不验证等于白转。我一般用三个方法交叉验证第一个是比对文件大小。hex 转 bin 后bin 的大小应该等于 hex 里所有数据记录的长度之和如果差太多说明地址解析有问题。第二个是用十六进制编辑器看开头。bin 文件的开头通常是向量表第一个字是栈顶地址第二个字是复位向量。对照 map 文件或芯片手册确认这两个值是否合理。第三个是烧录后实测。这是最可靠的验证但成本也最高。如果条件允许烧进去跑一下看功能是否正常比任何静态检查都管用。7. 烧录选型什么时候用 hex什么时候用 bin7.1 按烧录工具的能力选不同的烧录工具对 hex 和 bin 的支持程度不一样。J-Link、ST-Link 这类调试器配合官方软件通常两种格式都支持你选哪个都行。但一些量产烧录器、离线编程器可能只认其中一种这时候就得按工具的要求来。我的经验是如果烧录工具能自动识别文件格式优先用 hex因为它自带地址信息不容易烧错位置。如果工具只认 bin那就必须手动确认起始地址烧之前多检查一遍。7.2 按应用场景选OTA 升级场景几乎都用 bin因为传输效率高而且升级程序通常知道固件该写到哪个地址。量产烧录场景看烧录器但很多工厂的烧录器是按 bin 配置的因为 bin 解析快、效率高。调试阶段用 hex 更方便因为改完代码重新编译hex 的地址信息会自动更新不用手动调整。还有一个场景是给第三方做固件交付。如果对方只给你一个烧录地址范围那给 bin 最省事如果对方希望固件自带地址信息、减少配置错误那给 hex 更稳妥。这个没有绝对答案看双方约定。7.3 双区方案下的产物处理做 Bootloader App 双区方案时产物处理要格外小心。Bootloader 通常固定在 Flash 起始地址App 则从某个偏移地址开始。如果 App 用 bin 格式烧录时必须指定正确的偏移地址否则会覆盖 Bootloader。更稳妥的做法是给 App 生成 hex因为 hex 里的地址记录已经包含了偏移信息烧录器会自动放到正确位置。如果下游工具只认 bin那就必须在烧录脚本里硬编码偏移地址并且做好版本管理和校验防止烧错。8. 常见问题与排查技巧实录8.1 烧录后不运行的排查思路烧录后板子没反应是最常见的问题。按下面的顺序排查基本能覆盖九成以上的情况现象可能原因排查方法完全无反应起始地址错误检查 bin 烧录地址是否与代码起始地址一致串口无输出时钟配置错误用调试器看时钟寄存器确认晶振起振运行后死机向量表偏移未设置检查 SCB-VTOR 是否指向正确地址部分功能异常数据段未初始化检查启动文件里的数据拷贝逻辑烧录报错文件格式不匹配确认烧录器支持 hex 还是 bin其中起始地址错误是最常见的。很多人转 bin 的时候没注意起始地址或者烧录器里配置的地址和实际代码地址不一致导致数据整体错位。用调试器读一下 Flash 开头的几个字和 map 文件里的向量表对比一眼就能看出来。8.2 hex 文件校验和错误的处理有时候 hex 文件在传输或编辑过程中会损坏导致校验和错误。烧录器通常会报“checksum error”之类的提示。这时候可以用工具重新计算校验和或者直接重新编译生成新的 hex。如果 hex 是你手动编辑过的那校验和错误几乎是必然的因为改数据的同时必须同步更新校验和。我的建议是尽量不要手动改 hex如果非要改用专门的 hex 编辑工具它们会自动处理校验和。8.3 bin 文件大小异常的排查bin 文件大小异常通常有两种情况一是比预期大很多二是比预期小很多。比预期大多半是因为地址空洞被填充了。比如代码在 0x08000000但有个数据段在 0x08010000fromelf 转 bin 时会把中间的空洞全部补 0xFF文件体积就膨胀了。解决办法是用--base参数限制起始地址或者改用 hex 格式避免填充。比预期小可能是链接脚本配置有问题部分代码段没有被正确包含进来。这时候要检查 .map 文件看各个段的地址和大小是否合理确认没有段被遗漏。8.4 Keil 编译报错的典型场景热搜词里出现了不少 Keil 报错相关的词这里整理几个高频问题“keil 无法打开工程”通常是工程文件损坏或路径包含中文/特殊字符。把工程移到纯英文路径下或者用文本编辑器打开 .uvprojx 文件检查是否有异常字符。“keil 退出仿真就会卡住”是 Keil 和某些调试器驱动不兼容导致的更新调试器驱动或换用其他版本的 Keil 通常能解决。“keil 里没有 proteus 仿真器”是因为 Proteus 的 Keil 驱动没有正确安装需要手动把 Proteus 的 VDM51.dll 或类似文件复制到 Keil 的 BIN 目录下。“keil 5 没有代码补全”是配置问题在 Edit - Configuration - Text Completion 里把相关选项打开即可。8.5 芯片包与器件支持问题“普冉芯片支持包 keil 怎么配”“瑞萨 RASC Keil 环境搭建”这类问题本质都是芯片厂商的器件支持包DFP安装问题。Keil 本身只带 Arm 官方的 CMSIS 包具体芯片的支持包需要从芯片厂商官网下载然后通过 Pack Installer 安装。安装完支持包后新建工程时才能在器件列表里找到对应的芯片型号。如果找不到检查 Pack Installer 里是否显示已安装以及 Keil 的 Pack 根目录路径是否正确。有时候网络问题会导致包下载不完整重新下载安装即可。9. 从文件格式延伸到工程习惯9.1 产物管理要形成规范我见过太多工程输出目录里堆满了各种版本的 hex 和 bin文件名全是默认的根本分不清哪个是哪个。这种习惯在单人开发时问题不大一旦多人协作或者需要回溯版本就是灾难。我的做法是给产物加上版本号和日期比如project_v1.2.3_20250101.hex并且在编译脚本里自动生成。同时把产物目录纳入版本管理或者至少保留最近几个版本这样出问题的时候能快速定位是哪个版本引入的。9.2 编译配置要写进文档每个工程的编译配置包括 hex 和 bin 的生成命令、起始地址、优化等级等都应该写进 README 或工程文档里。换电脑、换人接手的时候照着文档配置一遍就能复现不用靠记忆或翻聊天记录。特别是 fromelf 的命令行参数很容易配错。把正确的命令记下来下次直接复制粘贴比重新摸索快得多。9.3 自动化验证值得投入如果项目周期允许建议在编译流程里加入自动验证环节。比如编译完成后自动检查 bin 文件大小是否在合理范围、hex 文件是否能被解析、向量表的前两个字是否合法等。这些检查用脚本实现并不复杂但能在早期发现很多低级错误。我自己写过一个 Python 脚本编译后自动读取 bin 文件检查栈顶地址是否在 RAM 范围内、复位向量是否指向 Flash 区域、文件大小是否超过 Flash 容量。这个脚本帮我拦下过好几次配置错误省了不少调试时间。9.4 关于反编译和格式转换的边界热搜词里有“hex 文件反编译成 c 语言”这样的词这里简单说一句hex 和 bin 都是机器码反编译回 C 语言只能得到近似的结果变量名、注释、原始结构都会丢失实际参考价值有限。真正需要理解固件逻辑的时候反汇编加动态调试比反编译更实用。格式转换工具hex2bin、srec_cat 等是日常开发的好帮手但要注意工具的版本和参数差异。不同版本的 hex2bin 对地址空洞的处理策略可能不同用之前先拿小文件测试一下确认行为符合预期再批量处理。10. 我踩过的几个坑和一点心得第一个坑是转 bin 时忘了指定起始地址结果烧进去的固件整体偏移了 0x100 字节板子跑起来各种异常。后来养成习惯转完 bin 先用十六进制编辑器看开头和 map 文件对照确认无误再烧。第二个坑是 hex 文件手动改了一个字节忘了更新校验和烧录器直接拒绝。从那以后需要改固件内容的时候我都是改源码重新编译实在要改 hex 就用专门的工具。第三个坑是双区方案里 App 用了 bin烧录脚本里的偏移地址写错了把 Bootloader 覆盖了板子直接变砖。后来改成 App 也用 hex让烧录器自己解析地址省心很多。这些坑说起来都不复杂但没踩过的时候就是想不到。嵌入式开发就是这样很多问题不是技术难度高而是细节没注意到。hex 和 bin 的区别看似基础但真正理解之后能帮你避开一大堆低级错误也能让你在看 map 文件、配烧录脚本、做 OTA 升级的时候更有底气。最后分享一个小技巧如果你经常需要在 hex 和 bin 之间转换可以写一个简单的批处理或 shell 脚本把常用的转换命令封装起来用的时候传个文件名就行。我自己的脚本里还加了自动校验和检查、文件大小对比、向量表合法性验证这几个功能用起来比手动操作靠谱得多。工具不在多高级能稳定解决问题就是好工具。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →