尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

C# 实现 Hex 转 Bin:Intel HEX 解析与固件转换工具实战

发布时间:2026/9/2 17:19:58

资讯中心
01
ARTICLE

C# 实现 Hex 转 Bin:Intel HEX 解析与固件转换工具实战

C# 实现 Hex 转 Bin:Intel HEX 解析与固件转换工具实战
简介面向单片机与嵌入式开发者的 Hex2Bin 小工具用于将 Hex 文件转换为通用 Bin 文件。Hex 格式包含地址等附加信息适合直接烧录 MCU但在与其他系统交互、或写入不支持 Hex 的设备与存储介质时Bin 格式往往更通用。工具使用 C# 编写在 Visual Studio 2022 下开发完成源码注释详细便于阅读、修改和二次扩展例如可以在生成后的 Bin 文件中按需加入校验码、加密信息或自定义数据段。压缩包共 97 个文件涵盖 C# 源程序、解决方案与工程文件、可直接运行的 exe、示例样例及配置文件整体仅 452KB目前已有 433 人学习/下载。借助这套源码开发者不但能直接完成格式转换还能深入理解 Hex 解析与数据写入流程并根据实际项目需要定制校验或加密逻辑提升单片机应用开发的灵活性与安全性。 做嵌入式开发的朋友一定遇到过这种尴尬MDK、IAR或者CubeMX默认生成的固件是.hex文件但烧录器、产线工装、OTA升级包甚至发给客户做远程升级时对方明确指定要.bin文件。命令行工具倒是能转可每次都要翻手册、敲参数Windows 下还不能保证每台电脑都有对应环境。于是我就用 C# 写了一个 WinForms 小工具把 Hex 转 Bin 这件事做成了双击即用的桌面程序源码也一并整理出来了。这篇文章会把整个实现思路、Intel HEX 格式的解析细节、C# 核心代码和实际测试方法都讲清楚希望对正在被这个转换问题困扰的人有点帮助。1. 为什么嵌入式工程师需要自己写Hex转Bin工具1.1 一条命令能解决的事为什么要专门写工具很多人第一反应是转换而已objcopy一条命令不就行了对在 Linux 或者安装了开发工具链的机器上确实可以。比如 Keil MDK 自带fromelf.exe用来生成 Bin 也就一行fromelf --bin --outputout.bin in.hex但现实情况往往没那么简单。我在实际项目里遇到过几种麻烦产线烧录工装的软件只认 Bin而且运行在没有任何开发环境的 Windows 工控机上现场技术支持同事电脑上只有烧录软件没有 Keil、没有 GCC 工具链需要把 Hex 转成 Bin 之后再做 CRC、加版本号、拼接 Bootloader这些操作没法靠单一命令完成有些转换命令对 Hex 的扩展地址段处理得不够直观遇到非连续地址时容易踩坑。命令行方案解决不了这些问题一个带界面、能设置偏移、能输出日志、还能看出转换过程的小工具在实际工作中会顺手得多。1.2 我在实际项目中碰到的几个典型场景先说说我自己的经历。最早做 STM32 固件升级时上位机接收的文件需要是纯二进制格式Bootloader 根据固定偏移把数据写入 Flash。当时我直接把 Keil 输出的 Hex 文件发给上位机上位机解析出来数据全是乱的排查半天才发现是格式理解错了。后来做产线烧录工装用的烧录器直接读取 Bin 文件写入 Flash但 Hex 文件包含的是分段地址信息烧录器固件根本不管这些只按连续内存块来写。如果不做转换烧录进去的程序一开始能跑后面跳转到某个地址直接 HardFault。还有一个场景是关于 OTA 差分包。云平台要求固件上传为 Bin 格式而且所有空闲区要填充 0xFF方便以后做增量升级。手工转换根本处理不了这种填充逻辑必须用工具做。这些需求加起来足够说明一个问题Hex 转 Bin 不是简单的文件后缀改名而是把带地址信息的文本文件转换成连续二进制镜像的过程中间涉及地址映射、空间拉伸、空洞填充等一系列问题。1.3 为什么选 C# 而不是 Python我在做这个工具时团队里有人建议用 Python说写起来更快。但考虑到几个现实因素我最后还是选了 C#目标运行环境是 Windows 工控机C# WinForms 可以直接打包成绿色 exe双击即用不需要解释器团队里懂 C# 的人多后续要增加功能、修 bug 更方便.NET 的文件读写、控件布局、异常处理在 Windows 下最顺手对底层二进制操作来说C# 的byte[]、BitConverter、FileStream完全够用性能也能满足要求。当然Python 也不是不行只是在我这个项目背景下 C# 明显更合适。选型这种事永远要看具体场景没有绝对的最好。2. Intel HEX文件格式转换前必须搞懂的底层协议2.1 HEX文件记录结构和类型要写出可靠的转换工具第一件事就是吃透 Intel HEX 的格式。它本质上是一种纯文本文件每一行记录都描述一小段数据以及这段数据应该写入的起始地址。标准记录行格式如下: LL AAAA TT [DD...] CC从左到右依次是:起始冒号固定不变LL数据区长度1 字节十六进制表示后面 Data 字段的字节数AAAA16 位地址2 字节十六进制表示数据写入的内存起始地址TT记录类型1 字节十六进制;DD...实际数据区长度由 LL 决定CC校验和1 字节十六进制保证整条记录没有传输错误。记录类型是解析的关键我整理了一张表格类型值类型名称含义00数据记录真正的固件数据写入 AAAA 对应的地址01文件结束记录整个文件最后一行没有数据区02扩展段地址记录将地址扩展到 20 位地址空间旧式 8086 模式03起始段地址记录指定 CS:IP 起始地址很少用到04扩展线性地址记录指定高 16 位基地址配合后边的数据记录形成 32 位地址05起始线性地址记录指定程序入口地址常见于 ARM 芯片的 Hex现代 ARM Cortex-M 系列芯片生成的 Hex用得最多的是00、01、04和05这几种。02和03我已经很少在 STM32 的工程里看到了但为了兼容老芯片工具里最好还是处理一下否则遇到老项目会直接报错。举个例子Keil 生成的 Hex 文件开头通常是:020000040800F2 :1000000000050020A9020000B5020000DD020000B6 :04000005080001DB :00000001FF第一行020000040800F2长度 2地址 0x0000类型04数据0800说明接下来的数据记录基地址是0x0800xxxx第二行1000000000050020...长度 0x10地址 0x0000类型00这 16 字节要写入基地址 0x0000 0x08000000第三行是起始线性地址入口是0x080001DB最后一行00000001FF类型01文件结束。这就是一个非常典型的 STM32 启动文件区域。2.2 校验和算法其实很简单Intel HEX 的校验和算法是对长度、地址、类型、数据所有字节求和取低 8 位然后取反加 1使得整条记录的校验和为 0。说白了就是校验和 (0x100 - (所有字节累加和 0xFF)) 0xFF比如记录0300300002337A1E字节序列长度0x03地址高0x00地址低0x30类型0x00数据0x02 0x33 0x7A累加和0x03 0x00 0x30 0x00 0x02 0x33 0x7A 0xE2校验和 (0x100 - 0xE2) 0xFF 0x1E和记录最后的1E完全一致。校验和判断逻辑很简单但是很重要。我见过一些在线转换工具遇到校验和不对的文件也不会报错而是默默解析最后生成的 Bin 文件烧进去才发现是坏的。写自己的工具时校验是必须做的一步宁可报错也不要输出错误数据。2.3 必须处理的边界情况光会解析标准记录还远远不够实际项目里的 Hex 文件经常出现一些让人头疼的情况地址不连续。Hex 文件可能只包含了 Flash 中几个区段的数据中间有空洞。比如 Bootloader 占用了0x08000000~0x08003FFF应用区从0x08008000开始那文件中间就会缺一大块。转换成 Bin 时这个空洞怎么办是跳过、填充 0x00 还是填充 0xFF这取决于目标芯片的 Flash 特性和引导程序要求。我的工具默认填充 0xFF这是 NOR Flash 擦除后的自然状态也是大多数烧录器默认的处理方式。扩展地址分段。有些文件会多次出现04类型记录基地址来回跳。解析时要注意基地址是逐条记录变化的不是固定的。有的工具实现时把基线地址写成只读一次遇到大文件就解析错位。首地址偏移。这是很多人忽略的问题。Hex 文件的地址往往不是从 0 开始的比如 STM32 的起始地址是0x08000000。转换出来的 Bin 文件是按物理存储从地址 0 开始排列还是保留原始偏移大多数烧录器希望得到一个从 0 开始的连续镜像所以我的工具用最小数据地址作为偏移基准把数据映射到从 0 开始的 Bin 文件。如果你的场景需要保留偏移比如烧录器会自动加上地址那我建议在工具里增加一个“输出起始地址”选项默认选择按最小地址对齐。3. C#实现转换核心逻辑从读文件到落盘的完整流程3.1 核心数据结构用字典还是大数组转换逻辑的本质是把 Hex 文件里分散在不同地址的数据重新排列到一个连续的字节数组中。有两种方案使用Dictionaryuint, byte按地址存数据最后再排序输出先解析一遍找出最小和最大地址确定 Bin 文件需要的总长度再直接分配byte[]填充。我一开始用了字典方案觉得灵活但实际测试发现当 Hex 文件达到几百 KB、数据分段又多时字典开销明显偏高而且排序输出也要额外处理。后来改成两遍扫描法第一遍遍历所有记录记录最小地址和最大地址根据地址范围计算 Bin 文件长度分配大数组默认填充 0xFF第二遍遍历把数据按地址写入数组对应偏移。这个方案简单、直观、占用内存小对于几十 KB 到几 MB 的固件完全够用。3.2 逐行解析HEX记录核心代码以下是整个解析过程的核心类我用的是 C#目标框架.NET Framework 4.6或.NET 6都可以跑public class HexToBinConverter { private Dictionaryuint, byte _dataMap new Dictionaryuint, byte(); public byte[] Convert(string hexFilePath) { try { string[] lines File.ReadAllLines(hexFilePath); uint baseAddress 0; // 当前扩展线性地址高位 uint segmentBase 0; // 段基地址兼容老格式 uint minAddress uint.MaxValue; uint maxAddress 0; // 第一遍先找出有效数据的地址范围 foreach (string line in lines) { if (string.IsNullOrWhiteSpace(line) || line[0] ! :) throw new FormatException(无效的HEX行格式缺少起始冒号: line); byte length Convert.ToByte(line.Substring(1, 2), 16); ushort offset Convert.ToUInt16(line.Substring(3, 4), 16); byte type Convert.ToByte(line.Substring(7, 2), 16); // 记录类型判断 switch (type) { case 0x00: // 数据记录 { uint addr (type 0x00) ? (baseAddress segmentBase offset) : 0; // 注意这里baseAddress高16位左移16位offset在低16位 uint fullAddr (baseAddress 16) | (uint)offset; if (fullAddr minAddress) minAddress fullAddr; uint endAddr fullAddr (uint)length; if (endAddr maxAddress) maxAddress endAddr; break; } } } if (minAddress maxAddress) throw new FormatException(文件中没有找到任何有效数据记录); int binLength (int)(maxAddress - minAddress); byte[] binData new byte[binLength]; for (int i 0; i binLength; i) binData[i] 0xFF; // 默认填充0xFF // 第二遍解析数据并写入字节数组 baseAddress 0; foreach (string line in lines) { if (string.IsNullOrWhiteSpace(line) || line[0] ! :) continue; byte length Convert.ToByte(line.Substring(1, 2), 16); ushort offset Convert.ToUInt16(line.Substring(3, 4), 16); byte type Convert.ToByte(line.Substring(7, 2), 16); switch (type) { case 0x00: { uint fullAddr (baseAddress 16) | (uint)offset; string dataStr line.Substring(9, length * 2); for (int i 0; i length; i) { byte b Convert.ToByte(dataStr.Substring(i * 2, 2), 16); int idx (int)(fullAddr - minAddress); binData[idx] b; } break; } case 0x04: { // 扩展线性地址记录 string dataStr line.Substring(9, 4); baseAddress (uint)Convert.ToInt16(dataStr, 16); break; } case 0x02: { // 扩展段地址记录老格式兼容 string dataStr line.Substring(9, 4); segmentBase (uint)Convert.ToInt16(dataStr, 16) 4; break; } case 0x01: case 0x03: case 0x05: // 文件结束、起始地址等无需处理 break; default: throw new FormatException(未知的HEX记录类型: type.ToString(X2)); } } return binData; } catch (Exception ex) { throw new InvalidDataException(HEX文件解析失败: ex.Message, ex); } } }这段代码有几个细节地址用uint而不是int不用处理符号位麻烦事baseAddress和segmentBase分开处理保证老式分段地址文件也能正确解析填充0xFF是在初始化数组时做的避免后再补一轮循环两遍解析都能共用同一套行读取逻辑逻辑相对清晰。3.3 缓冲区大小计算与Bin文件落盘有了上面解析出的byte[]落盘就简单了public static void WriteBinFile(byte[] data, string outputPath) { File.WriteAllBytes(outputPath, data); }但这里我额外加了两个选项起始地址偏移有些烧录器要求 Bin 文件保留原始地址比如 STM32 程序从0x08000000开始生成的 Bin 前 0x08000000 个字节都是 0xFF文件会非常大。默认情况下应该按“最小地址裁剪”让 Bin 从实际数据的起始地址开始排。如果勾选了“保留偏移”就预留空洞填充。区域扩展有时候需要把 Bin 扩展到固定大小比如 512KB方便后续 Bootloader 直接按长度读取。我的工具里加了一个“扩展长度”输入框填了以后会在数据末尾填充 0xFF 到指定大小。写文件时用File.WriteAllBytes最省事但输出路径要提前检查目录是否存在string dir Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(dir) !Directory.Exists(dir)) { Directory.CreateDirectory(dir); }这一点看起来简单但用户在 UI 上手动输入一个不存在的路径时很常见不处理就会莫名其妙地抛异常。4. 工具界面设计与异常处理经验4.1 界面布局越简单越好用给产线同事用的小工具界面一定不能复杂。我当时设计的主界面有这几个控件文件选择区一个文本框 一个“浏览”按钮支持拖放文件到文本框输出设置区输出路径文本框、输出目录选择按钮选项区保留偏移的 CheckBox、扩展长度的 TextBox操作区一个“开始转换”按钮一个日志输出框状态栏显示文件大小、记录数量、转换耗时。布局上我特意把“开始转换”按钮放在明显的位置字号也调大了。产线工人戴着手套操作按钮太小很容易点错。日志框用只读的RichTextBox显示每一条记录类型、地址范围和转换结果这样出问题时可以通过日志定位。核心 UI 事件就两个选择文件和开始转换。没有复杂的配置没有多步骤向导用户只需要选择文件、点一下按钮、拿到结果。工具应该解决问题而不是制造新的学习成本。4.2 我在异常处理上踩过的几个坑第一个坑没有处理文件编码问题。Keil 生成的 Hex 文件是 ASCII 编码但有些第三方 IDE 会把文件保存成带 BOM 的 UTF-8第一层就是EF BB BF。File.ReadAllLines如果是默认编码会把 BOM 带进第一行导致line[0] ! :判断失败。后来我统一用File.ReadAllLines(path, Encoding.ASCII)并显式去除每行的\r和\n问题算是解决了。第二个坑地址重叠没有告警。有一次用户反馈转换出来的 Bin 烧录后程序乱跑。我查了半天发现是 Hex 文件里同一地址出现了两组不同数据。这种情况在手动拼接 Hex 文件时很容易出现解析工具如果不检测直接后写入的数据覆盖前面的数据而前面可能是 Bootloader后面是 App覆盖之后程序自然崩溃。于是我在解析时加了一个检测记录每个地址的写入次数如果发现某个地址被重复写入就在日志里输出 WARNING。第三个坑日志输出太多导致界面卡死。处理 1MB 固件时如果每条数据记录都输出一行日志界面会卡得没法看。后来改成只输出记录类型 04、01、05 这些关键信息数据记录只在出错的时候才输出。第四个坑文件路径包含中文或空格。Windows 下路径一般没问题但偶尔会遇到带特殊字符的路径比如、#。我一开始直接用File.ReadAllLines结果在几台机器上报异常。后来发现是杀毒软件拦截了文件流读取在工控机上尤其严重。解决方法是把文件先复制到一个临时缓存目录再解析转换完成后自动删除临时文件。这个方法不一定对所有场景都适用但确实解决了我这边遇到的怪问题。5. 实测验证与回顾5.1 怎么确认转换结果是对的转换功能写完最重要的一件事不是看代码而是验证输出。我的验证方法是这样的用 Keil 的fromelf生成一份 Bin 作为基准用我的工具转换同一个 Hex用二进制比较工具比如 Beyond Compare 或者写个小脚本逐字节对比两份 Bin。对比脚本核心逻辑如下public static bool CompareFiles(string fileA, string fileB) { byte[] a File.ReadAllBytes(fileA); byte[] b File.ReadAllBytes(fileB); if (a.Length ! b.Length) return false; for (int i 0; i a.Length; i) { if (a[i] ! b[i]) return false; } return true; }我用 STM32F103、STM32F407、LPC1768 三个工程分别做了测试覆盖了04扩展线性地址、非连续地址、文件末尾入口地址等多种情况生成的 Bin 与fromelf的结果完全一致。还测过一个特殊情况某个工程的 Hex 文件最后一段数据填充到了0x0801FFFF导致 Bin 文件长度多出几百 KB里面全是0xFF。用我的工具转换后去掉尾部填充对比结果完全一致说明最小地址裁剪逻辑是对的。5.2 这个工具后续还能怎么增强基础功能稳定之后我根据实际使用反馈做了几个增强可能对你有参考价值批量转换。产线上一烧就是十几个固件一个个手动转换太慢。我加了一个多选文件列表支持一次拖入多个 Hex自动生成对应的 Bin 文件文件名默认改为xxx.bin并在前面加上版本号。CRC 校验值计算。有些 Bootloader 升级协议要求在 Bin 末尾追加 4 字节 CRC32。我在工具设置里加了一个选项可以自动追加 CRC32 小端序值省去单独写脚本的麻烦。一键烧录集成。工具支持配置烧录器命令行转换完成后自动调用烧录命令。比如 ST-Link CLI 可以这样配置ST-LINK_CLI.exe -P out.bin 0x08000000 -V -Rst这样从 Build 到 Flash 一步到位效率提升很明显。这些功能看着不大但实际用起来体验差别很大。特别是批量转换和 CRC 追加几乎每个做量产的人都会用到。最后再分享一个小技巧。做这类格式转换工具别急着写界面先把核心转换逻辑做成一个独立的类库通过命令行参数或者简单测试代码验证结果正确后再套 UI。我一开始直接写 WinForms界面和逻辑耦合在一起调试时每次都要打开窗口、选文件效率太低。重构后把转换逻辑抽成HexToBinConverter类我再也没为改一个解析 bug 去反复点界面了。工具写到现在也有几年了期间遇到最多的问题不是 Hex 解析本身而是用户给的输入文件五花八门有的文件末尾多空行有的记录大小写混用有的地址重复。把这些问题一个个处理掉工具才算真正稳定下来。如果你也在做类似的东西或者遇到了 Hex 转换的奇怪问题欢迎讨论这些坑我基本都踩过一遍了。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。