Keil5生成BIN文件:fromelf转换与命令行自动化构建实操
发布时间:2026/10/20 10:59:20 锦皓数字建站

好几次都遇到同一个问题固件调试完了Keil也勾上了生成HEX文件的选项编译通过顺手把hex文件丢给烧录工具或者发到现场。结果对方很快回了一句“我们这边只要bin文件hex用不了。”我自己就被这个事折腾过。后来才彻底搞清楚Keil5里生成bin文件不像勾选HEX那样有现成菜单它本质是靠一条命令行设置去调用自带的转换工具。这篇文章就把整条链路讲明白HEX和BIN到底差在哪、转换工具怎么用、图形界面里怎么配、抽掉界面后怎么实现全自动构建最后聊聊我在实测中踩过的坑。先说明一下这篇内容的适用人群刚接触嵌入式开发、被产线或上位机同事要过bin文件的或者已经在用Keil5但还在用第三方转换工具的再或者想搭一套命令行编译、每天自动出固件的环境的人。读完你至少能做到两件事第一个在Keil5的User页签里加一条命令编译完顺手出bin第二个写一个bat脚本连编译带转换全部在命令行里完成CI系统也能直接用。1. 为什么非得绕一圈生成BINHEX与BIN的真实差异先说清楚这个概念因为很多人只是“知道要bin”但说不清为什么要bin这会导致后续配置时容易踩坑。HEX文件一般指Intel HEX格式本质是文本文件。每一行文本里包含了起始地址、数据长度、数据类型、数据内容、校验和。烧录器拿到HEX后会逐行解析把数据写到对应的Flash地址上。所以HEX文件自带“收货地址”烧录器不需要你额外告诉它地址。BIN文件就是纯二进制字节流文件里每一个字节都是要被写入到Flash里面的原始数据。它不携带任何地址信息也没有校验和更没有任何文本格式的概念。烧录器或者Bootloader在接收BIN文件时必须有一个预先约定的起始地址然后从那个地址开始按顺序把字节写进Flash。打个比方HEX文件像一个贴着快递单号的包裹快递员照着单号往指定货架上放BIN文件就是一堆裸货物你必须先告诉仓库管理员放在哪个货架他才能逐个码好。那为什么很多场景只要BIN我总结了几种最常见的IAP在线升级嵌入式设备内部有Bootloader上位机或者手机App通过串口、蓝牙、WiFi把固件从Bootloader传输到设备。Bootloader通常只会往Flash的某个APP区连续写入字节它不需要解析HEX直接把接收到的字节流写进去就行。这时候你给它HEX反而麻烦因为Bootloader要么得自己实现HEX解析器要么就需要你先把HEX转成BIN再发。产线脱机烧录很多商用脱机烧录器、烧录架支持BIN文件。产线工人操作时只需要在软件里填一个烧录起始地址然后一键烧写。BIN文件因为体积小、格式简单处理速度也更快产线效率高。OTA云端固件包物联网设备做远程升级时固件包通常直接用BIN做差分、切片、加CRC校验。HEX是文本格式还掺杂地址信息做这些操作非常别扭。U盘或SD卡升级部分消费类设备把BIN文件放到FAT32分区的U盘里插上设备后触发热升级。这种场景下几乎都是BIN格式。顺带一提Keil5默认的编译输出产物是axf文件和hex文件。AXF是ELF格式的调试镜像包含调试信息和符号表烧录时一般用不到HEX是给烧录器用的。Keil5自己没有“生成BIN文件”的勾选项这是很多人找半天找不到的根本原因——它确实没有这个选项。所以必须借助命令行让工具从AXF里把纯二进制数据抽出来转成BIN。理解了这一点接下来要登场的就是Keil5自带的转换工具fromelf。2. 转换核心工具fromelf命令行下的格式转换主力fromelf是ARM编译器自带的工具它的工作就是读取编译链接后生成的ELF也就是AXF文件然后转换成你需要的输出格式BIN、HEX、Motorola S-record、反汇编文本等。2.1 fromelf在哪里、能做什么如果你的Keil5安装默认路径是C:\Keil_v5那么fromelf一般出现在两个位置使用AC5ARM Compiler 5C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe使用AC6ARM Compiler 6C:\Keil_v5\ARM\ARMClang\bin\fromelf.exe不同的编译器版本fromelf的语法细节还不一样。这一点特别容易翻车后面专门说。fromelf的常用能力包括把AXF转成BINfromelf --bin -o output.bin input.axf把AXF转成HEXfromelf --i32 -o output.hex input.axf把AXF转成反汇编文本fromelf --text -c -o output.dis input.axf查看ELF信息fromelf --text -v input.axf可以说凡是需要把编译产物做格式转换的活儿它基本都能干。2.2 为什么推荐直接用fromelf而不是第三方工具网上有人习惯用hex2bin.exe或者srec_cat这类第三方工具来转换。我也用过但最终还是回到了fromelf。原因有三第一版本匹配。fromelf和你的编译器是同一个工具链对AXF文件里的ELF结构理解最准确不会出现第三方工具因为解析不了某些段或者对符号表处理不对而生成的BIN异常的情况。第二免安装。只要Keil装好了fromelf就一定存在。产线机器上不用额外装软件少一个依赖就少一个故障点。第三参数稳定。命令行脚本里只需要写死工具路径和参数不管软件升级几次兼容性都相当好。我曾经在一个项目里遇到过用第三方hex2bin工具转出来的BIN文件大小比Flash容量还大。排查后发现是分散加载文件sct里定义了多个不连续的加载区第三方工具在转换时把中间地址空洞全部填了数据导致BIN异常膨胀。换成fromelf后虽然也会存在类似问题但至少行为一致性有保障而且它和编译器的对齐方式完全一致。2.3 AC5与AC6的参数差异这个很多人不注意。如果你默认使用的是Keil5自带的AC5编译器fromelf的写法是fromelf.exe --bin -o output.bin input.axf其中-o是output的缩写。如果你切换到了AC6ARM Compiler 6fromelf的语法会有变化长选项要写成--outputfromelf.exe --bin --output output.bin input.axf这里有个很坑的细节AC6的fromelf对--bin和--output是分开的中间加不加等号都行但用-o不保证有效。我建议在写命令时先把编译器版本确认好再选语法。下表是两种版本的关键差异功能AC5写法AC6写法转成BINfromelf --bin -o out.bin in.axffromelf --bin --output out.bin in.axf转成HEXfromelf --i32 -o out.hex in.axffromelf --i32 --output out.hex in.axf反汇编fromelf --text -c -o out.txt in.axffromelf --text -c --output out.txt in.axf确认当前工程用的是哪个编译器可以通过Keil的“Options for Target”里的“Target”页签查看“ARM Compiler”下拉框。如果是Default就看你安装的Keil版本默认配套了哪个编译器。3. Keil5的User页签图形界面里的命令行挂接实操理解了工具现在进入最核心的操作在Keil5里老老实实配置一条命令行让编译完成后自动执行转换。3.1 一步一步配置整个操作只需要几步但每一步都有值得注意的细节打开工程点击工具栏上的“Options for Target”也就是那个魔法棒图标。在弹出的对话框里切到“User”页签。找到“After Build/Rebuild”这一组选项会看到“Run #1”“Run #2”这样的输入框每个输入框前面有一个复选框。在“Run #1”前面的复选框打勾然后在后面的文本框里输入要执行的命令。点击“OK”保存然后编译工程。如果你用的是AC5编译器在“Run #1”里输入这条命令C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o $LL.bin $LL.axf如果你用的是AC6编译器则输入C:\Keil_v5\ARM\ARMClang\bin\fromelf.exe --bin --output $LL.bin $LL.axf输入完成后编译一次工程。Build Output窗口里会出现fromelf命令的执行信息打开工程目录下Linker输出目录通常是Objects文件夹如果看到了同名.bin文件说明配置成功。每次编译链接成功后都会自动重新生成BIN文件。如果编译有错误User页签里的After Build命令不会执行这一点是Keil本身的行为不用额外操心。3.2 搞懂$L和L变量别写死路径上面命令里的$L和L是Keil内置的宏变量它们会自动替换成当前工程的实际路径和文件名。理解这两个变量你就不会再犯把路径写死导致换工程就失效的毛病。$LLinker输出目录也就是AXF文件所在的目录通常是工程目录下的Objects。LLinker输出文件名不包含扩展名默认和Target名称一致。举个例子我的工程文件是Demo.uvprojxTarget名称是DemoLinker输出目录是.\Objects\。那么$LL.axf会被替换成.\Objects\Demo.axf$LL.bin会被替换成.\Objects\Demo.bin。这里有一个非常关键的点Keil的宏变量替换发生在命令行执行之前。如果你的命令里写了绝对路径换一台电脑、换一个工程目录路径就要重写但用$LL方式命令可以在任何工程里通用这也是我强烈推荐在教程和脚本里用它的原因。如果你希望BIN文件生成到指定目录比如工程根目录下的Output文件夹可以写成C:\Keil_v5\ARM\ARMClang\bin\fromelf.exe --bin --output $P\Output\$LL.bin $LL.axf这里$P代表工程文件.uvprojx所在的目录。需要注意$P\Output这个目录必须提前存在否则fromelf会因为找不到输出目录而报错。之前有朋友卡在这一点上加了个mkdir才解决。3.3 几个容易被忽略的操作细节第一Run #1前面的复选框必须勾上。很多人只在文本框里输入了命令却没有勾选复选框编译完成后自然什么都没有。第二建议勾上后面的“Log #1”复选框。这样命令的执行结果会显示在Build Output窗口里方便确认fromelf到底有没有被调用。第三After Build和After Rebuild不要都放同一条命令。如果你经常执行Rebuild而命令只挂在“After Build”下Rebuild完成时不会执行转换挂在“Before Build”下则会转换上一次的旧AXF文件容易拿到旧数据。最稳妥的做法是把命令挂在“After Build/Rebuild”下的Run #1里这样无论Build还是Rebuild完成后都会执行。第四还可以利用Run #2做一个用起来很爽的小技巧。在Run #2里加上explorer $L这样每次编译完成后Windows资源管理器会自动弹出AXF和BIN所在的目录省得手动翻文件夹。这个操作不影响任何功能只是提升操作效率我自己的工程里一直留着这一项。4. 告别GUI用UV4.exe命令行构建并自动产出BIN很多时候我们不止需要在IDE里点一下编译还需要让构建自动化。比如每天晚上自动编译一次并生成固件包或者把构建集成到CI环境里又或者给产线搭一个一键烧录的脚本。这时候就不能依赖Keil的图形界面了得请出UV4.exe。4.1 UV4.exe是什么、有哪些参数UV4.exe是Keil MDK的工程构建工具安装在C:\Keil_v5\UV4\UV4.exe。它支持通过命令行控制工程的编译、重编译、清理等操作。常用的参数如下-b构建Build相当于IDE里的Build目标-r重建Rebuild相当于执行全部重新编译-c清理Clean清除中间文件-j0隐藏GUI界面无人值守模式-o file把构建日志输出到指定文件最基本的调用方式C:\Keil_v5\UV4\UV4.exe -b -j0 -o build.log D:\Projects\Demo\Demo.uvprojx注意工程文件必须给完整路径并且如果路径里有空格要记得加双引号。还有一个重要概念UV4.exe的返回值即错误级别errorlevel。调用完批处理之后可以通过%errorlevel%来判断构建是否成功这一点在做自动化脚本时非常有用。通常返回0构建成功且没有警告返回1构建成功但有警告返回2及更高构建失败在无人值守构建时千万别忽略这些返回值。如果不判断哪怕编译失败了批处理还是会继续执行后面的fromelf转换最终生成一个旧版本的BIN或者直接报错。产线上一旦拿到这种“假BIN”问题就大了。4.2 一段完整的批处理脚本编译转换BIN下面给出一段我实际用过、可直接修改的批处理脚本。它做的事情是重建工程、判断编译结果、调用fromelf生成BIN、打印结果。echo off setlocal enabledelayedexpansion set UV4C:\Keil_v5\UV4\UV4.exe set PROJECTD:\Projects\Demo\Demo.uvprojx set FROMELFC:\Keil_v5\ARM\ARMClang\bin\fromelf.exe set AXFD:\Projects\Demo\Objects\Demo.axf set OUT_BIND:\Projects\Demo\Objects\Demo.bin set BUILD_LOGD:\Projects\Demo\build.log echo echo Start Building Firmware ... echo %UV4% -r -j0 -o %BUILD_LOG% %PROJECT% if %errorlevel%0 ( echo [OK] Build succeeded, no warnings. ) else if %errorlevel%1 ( echo [WARN] Build succeeded with warnings, still continue. ) else ( echo [ERROR] Build failed, errorlevel%errorlevel% echo Please check %BUILD_LOG% exit /b %errorlevel% ) echo Converting AXF to BIN ... %FROMELF% --bin --output %OUT_BIN% %AXF% if %errorlevel%0 ( echo [OK] BIN generated: %OUT_BIN% ) else ( echo [ERROR] BIN generation failed! exit /b %errorlevel% ) echo echo Build and BIN generation done. echo 如果想做增量编译把-r换成-b如果工程很大、每次都全量重建太耗时优先用-b。如果希望清掉上一次的BIN文件再重新生成可以在编译之前加一句if exist %OUT_BIN% del %OUT_BIN%这样做的好处是一旦转换失败目录里不会残留一个时间戳更新但其实内容无效的文件。CI流程里如果后续还有拷贝固件、发邮件之类的步骤残留旧文件会造成很大困扰。4.3 把批处理集成到CI或者每日构建这段批处理完全可以放到Jenkins、GitLab CI或者Windows任务计划程序里运行。通常做法是在CI里配置一个Windows节点拉取最新代码后执行这个bat脚本。把生成的BIN文件收集为构建产物。给BIN文件命名时带上版本号比如firmware_v1.2.3.bin可以提前在批处理里加上日期或者从某个版本头文件里读取。我的习惯是在工程里放一个version.h里面定义一个宏比如#define FIRMWARE_VERSION 1.2.3然后在批处理里用findstr提取版本号拼在输出文件名上。这样每次构建出来的BIN文件名本身就包含版本信息后续归档、回溯都非常清晰。如果你在同一个.uvprojx里有多个Target比如Bootload和App两个TargetUV4.exe默认会重建所有Target。如果只想构建某一个Target可以用-t TargetName参数指定。我的习惯是Bootloader和APP分成两个工程各自独立的批处理脚本这样CI流程更清晰互不干扰。5. 实测中容易翻车的几个细节从路径空格到地址空洞这一章是给“已经配置成功但总觉得哪里不对”的人看的。我在不同项目里把这些坑都踩过一遍写出来的都是实测过的问题和解决办法。5.1 路径里有空格命令就罢工Keil默认安装在C:\Keil_v5一般没有空格。但你的工程目录、用户名目录就不好说了比如D:\My Workspace\Project或者Windows用户名是“Zhang San”导致临时目录带空格。一旦涉及的命令行没有加双引号fromelf就会把路径拆成多段然后报“Cannot open file”或者“Access is denied”之类的错误。解决办法在User页签的命令框里路径一律加双引号批处理脚本里也一样所有包含路径的变量赋值时都带上引号。我在脚本里写set PROJECTD:\Projects\Demo\Demo.uvprojx就是提前给变量吃上引号后面调用时就不用每个地方都加一遍。一个小细节如果路径末尾带了反斜杠\再后面跟双引号在Windows批处理里可能被转义掉导致命令出错。所以我习惯路径末尾不带反斜杠写成D:\Projects\Demo而不是D:\Projects\Demo\。5.2 Target名称和工程名不一致导致找不到AXF这是新手最容易困惑的点。UV4工程文件名和Target名称完全可以不一样。比如工程文件叫Demo.uvprojx但Target名称改成了ApplicationV2。此时AXF文件名是ApplicationV2.axfL变量返回的也是ApplicationV2。如果你在图省事在命令里手写了Demo.binfromelf自然找不到Demo.axf。解决方法是永远不要手写文件名直接用$LL组合。在批处理脚本里则建议先确认AXF文件名或者在工程配置里让Target名称和工程名保持一致省掉后续麻烦。5.3 AC6和AC5混装时用了错误的fromelf路径有时候一台机器上同一个Keil版本可能同时安装了多个编译器版本。你在工程里选的编译器是AC5结果批处理脚本里却指向了ARMClang\bin\fromelf.exe虽然也能转换但万一两个版本的语法有差异就很容易产生“命令跑通了但参数不认”的情况。所以我的习惯是在User页签和批处理脚本里都固定使用与当前工程编译器匹配的fromelf路径并且在脚本里加一个if exist判断if not exist %FROMELF% ( echo [ERROR] fromelf not found: %FROMELF% exit /b 3 )这样一旦编译器路径变了脚本能立刻给出明确提示而不是生成一个奇怪的半成品文件。5.4 分散加载文件里地址不连续BIN文件异常膨胀这个坑我前面提过一次值得再深入说说。工程里如果用sct分散加载文件定义了多个加载区且地址之间有空洞fromelf在生成BIN文件时会从第一个可加载地址开始一直连续排到最后一个地址结束。中间的空洞会被填充为0x00而不是跳过。这会导致BIN文件变得很大甚至超过Flash容量。举个例子。某个项目里Flash起始地址0x08000000代码段在0x08000000但你在sct里把数据段放在了0x08020000中间隔了128KB的空洞。生成的BIN文件大小就会是0x08020000到0x08000000的差即128KB以上而实际代码可能只有50KB。烧录这样的BIN到Flash里空洞区域会写入0x00而不是Flash擦除后的0xFF。遇到这种情况怎么办我的建议是分三步走先确认sct里加载区的地址规划是不是真的需要留这么大间隙。很多时候是早期调试时临时留的后期固件功能稳定后完全可以收紧地址布局。如果地址间隙确实必要就要评估BIN文件膨胀是否影响后续流程。如果只是烧录Flash容量足够就问题不大但如果要走OTABIN体积直接决定传输时间必须优化。检查BIN文件大小在Windows资源管理器里看属性或者用dir命令查看。见到BIN大小明显异常第一反应就去看sct文件。不用急着换工具。srec_cat这类第三方工具在裁剪地址空洞方面确实更强但引入新工具的同时也引入了新的依赖。优先调整地址规划才是从根本上解决问题。5.5 烧录BIN时起始地址填错导致程序跑不起来BIN文件没有地址信息烧录时必须手动指定起始地址。这个地址不是随便填的它必须与你的AXF链接时的加载地址一致。常见芯片的Flash起始地址比如某些Cortex-M3/M4系列的0x08000000。如果APP的链接地址偏移到了0x08010000烧录时也要用这个偏移地址。我在一个现场就遇到过同事把BIN烧到缺省地址结果上电之后程序完全不运行排查了半天发现是烧录起始地址填成了0x00000000。BIN本身没问题地址错了而已。所以产线的烧录工位SOP里一定要写清楚每一类固件的烧录起始地址最好在烧录软件里把地址锁定或者写入配置文件随固件一起下发。5.6 BIN文件被占用转换失败只要你在Windows资源管理器里预览过BIN文件或者烧录工具还开着这个文件fromelf重新生成时就会提示“Permission denied”或者“File cannot be opened”。这种问题看起来莫名其妙但对老手来说基本秒懂先把占用BIN文件的程序关掉再编译。如果你在开发机上经常碰到可以考虑把BIN输出目录放到一个不会随手打开预览的路径。5.7 杀毒软件把fromelf或UV4拦了在部分公司内网电脑上杀毒软件可能会把UV4.exe或者fromelf.exe当成可疑程序处理。结果就是命令行编译偶尔失败或者fromelf被隔离。遇到这种问题把Keil安装目录加入杀毒软件的白名单是效率最高的方式。尤其是产线电脑权限管理严格提前在部署文档里写清楚这一条能省掉很多现场排查时间。最后再分享一个我个人比较习惯的做法。我会在工程目录下放一个build.bat把第4章那段批处理脚本放进去同时再放一个output文件夹放BIN产物。平时自己在IDE里开发调试时靠的是User页签那条命令一旦需要出稳定版本或交接给其他人就跑一下build.bat。两个入口配合使用开发效率高交付也不容易出错。如果你被“只要bin文件”这个问题卡住过希望这篇内容能帮你一次搞定。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。