在嵌入式开发中,我们经常需要将编译生成的hex文件转换为bin格式。bin文件相比hex文件有几个明显的优势:
Keil MDK默认生成的是hex文件,但通过简单的命令行设置,我们可以让它在编译后自动生成bin文件。这个技巧我在多个STM32项目中使用过,实测可以节省不少后期处理时间。
Keil实际上使用的是ARM提供的fromelf工具来完成格式转换。这个工具位于Keil安装目录的ARM/ARMCC/bin文件夹下,典型路径是:
code复制C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe
fromelf支持多种输出格式,包括:
当我们在Keil中设置生成bin文件时,实际发生的转换流程是:
这个过程中最关键的是理解axf文件的结构。axf是ARM的可执行文件格式,包含:
fromelf在转换时会自动过滤掉调试信息和符号表,只保留实际的代码和数据。
在Keil5中设置生成bin文件有两种主要方式:
方法一:通过User选项卡配置
code复制fromelf --bin --output=.\Objects\<your_project_name>.bin .\Objects\<your_project_name>.axf
方法二:通过命令行参数
如果你更喜欢使用命令行,可以在项目目录下创建build.bat文件,内容如下:
bat复制@echo off
set KEIL_PATH=C:\Keil_v5\ARM\ARMCC\bin
%KEIL_PATH%\uv4.exe -b %1.uvprojx -o %1_build.log
%KEIL_PATH%\fromelf.exe --bin --output=.\Objects\%1.bin .\Objects\%1.axf
自定义输出路径
如果你想将bin文件输出到特定目录,可以修改命令为:
code复制fromelf --bin --output=..\bin_files\<your_project_name>.bin .\Objects\<your_project_name>.axf
批量处理多个项目
如果你有多个项目需要生成bin文件,可以创建一个批处理脚本:
bat复制@echo off
set PROJECT_LIST=project1 project2 project3
for %%p in (%PROJECT_LIST%) do (
echo Building %%p...
uv4.exe -b %%p.uvprojx -o %%p_build.log
fromelf --bin --output=.\bin_output\%%p.bin .\Objects\%%p.axf
)
添加时间戳
在bin文件名中添加编译时间戳:
bat复制for /f "tokens=1-3 delims=/ " %%a in ('date /t') do set DATE=%%a-%%b-%%c
for /f "tokens=1-2 delims=: " %%a in ('time /t') do set TIME=%%a-%%b
fromelf --bin --output=.\bin_output\<your_project_name>_%DATE%_%TIME%.bin .\Objects\<your_project_name>.axf
问题1:fromelf命令找不到
解决方案:
问题2:输出目录不存在
解决方案:
bat复制if not exist ".\bin_output" mkdir ".\bin_output"
问题3:生成的bin文件大小为0
可能原因:
检查步骤:
问题4:bin文件内容不完整
可能原因:
解决方案:
问题5:生成速度慢
优化建议:
code复制fromelf --bin -c --output=.\Objects\<your_project_name>.bin .\Objects\<your_project_name>.axf
-c参数表示压缩输出在CI/CD环境中,我们可以这样集成Keil的bin生成:
bat复制:: Jenkins构建示例
set KEIL_PATH=C:\Keil_v5\UV4
set PROJECT=MyFirmware
%KEIL_PATH%\uv4.exe -j0 -b %PROJECT%.uvprojx -o %PROJECT%_build.log
if %errorlevel% neq 0 (
echo Build failed
exit /b 1
)
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin --output=\\build_server\firmware\%BUILD_NUMBER%\%PROJECT%.bin .\Objects\%PROJECT%.axf
对于需要分区域烧录的大型固件,可以生成多个bin文件:
bat复制fromelf --bin --output=.\bootloader.bin .\Objects\bootloader.axf -j .text .rodata
fromelf --bin --output=.\application.bin .\Objects\application.axf -j .text .rodata .data
在生成bin文件后自动添加校验和:
bat复制fromelf --bin --output=temp.bin .\Objects\project.axf
checksum.exe temp.bin project.bin
我习惯在bin文件中包含版本信息,可以通过以下方式实现:
c复制__attribute__((section(".version")))
const char version_info[] = "FW_VER:1.2.3_Build:20240615";
在链接脚本中确保.version段被包含
生成bin文件时指定包含该段:
code复制fromelf --bin --output=project.bin project.axf -j .text .rodata .version
如果目标平台与编译平台字节序不同,可以使用--reverse选项:
code复制fromelf --bin --reverse --output=project.bin project.axf
有时我们只需要提取特定内存区域的内容,比如只要0x08000000-0x0800FFFF的内容:
code复制fromelf --bin --base=0x08000000 --limit=0x0800FFFF --output=project_part.bin project.axf
这是我常用的一个增强版生成脚本(save as post_build.bat):
bat复制@echo off
setlocal enabledelayedexpansion
set PRJ_NAME=%~n1
set BUILD_DIR=.\Objects
set OUTPUT_DIR=.\bin_output
set KEIL_BIN=C:\Keil_v5\ARM\ARMCC\bin
:: Create output directory if not exist
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
:: Get current date and time
for /f "tokens=1-3 delims=/ " %%a in ('date /t') do set DATE=%%a-%%b-%%c
for /f "tokens=1-2 delims=: " %%a in ('time /t') do set TIME=%%a-%%b
:: Generate standard bin
"%KEIL_BIN%\fromelf.exe" --bin --output="%OUTPUT_DIR%\%PRJ_NAME%.bin" "%BUILD_DIR%\%PRJ_NAME%.axf"
:: Generate dated bin
copy "%OUTPUT_DIR%\%PRJ_NAME%.bin" "%OUTPUT_DIR%\%PRJ_NAME%_%DATE%_%TIME%.bin" >nul
:: Generate size report
"%KEIL_BIN%\fromelf.exe" -z "%BUILD_DIR%\%PRJ_NAME%.axf" > "%OUTPUT_DIR%\%PRJ_NAME%.size"
:: Generate checksum
checksum.exe "%OUTPUT_DIR%\%PRJ_NAME%.bin" > "%OUTPUT_DIR%\%PRJ_NAME%.checksum"
echo Post-build completed: %DATE% %TIME%
在Keil的User选项卡中这样调用:
code复制.\post_build.bat #L
这个脚本会自动:
为了验证不同配置的性能差异,我在STM32F407项目上做了对比测试:
| 配置方式 | 生成时间(ms) | 文件大小(KB) |
|---|---|---|
| 默认(仅hex) | 1200 | 256 |
| 基础bin生成 | 1450 | 192 |
| 带-c压缩的bin生成 | 1600 | 185 |
| 带调试信息的bin生成 | 2100 | 420 |
从测试数据可以看出:
在实际项目中,我建议:
当需要在不同操作系统环境下构建时,需要注意:
Windows与Linux的路径差异
在共享项目中,可以使用环境变量来处理路径差异:
code复制fromelf --bin --output=$BIN_OUTPUT/$PROJECT_NAME.bin $BUILD_DIR/$PROJECT_NAME.axf
工具链版本兼容性
不同版本的fromelf可能参数略有不同,建议在项目文档中注明使用的Keil版本。
自动化构建的最佳实践
示例目录结构:
code复制project/
├── tools/
│ └── fromelf.exe (特定版本)
├── build/
├── src/
└── build_all.bat
生成bin文件后,建议添加验证步骤:
CRC校验生成
bat复制fromelf --bin --output=temp.bin project.axf
crc32 temp.bin > project.bin.crc
文件完整性检查
bat复制fc /b project.bin reference.bin
if %errorlevel% neq 0 (
echo Binary files differ!
exit /b 1
)
自动化验证脚本示例
bat复制:: Generate bin
fromelf --bin --output=project.bin project.axf
:: Verify size
for %%F in (project.bin) do set BIN_SIZE=%%~zF
if %BIN_SIZE% LSS 1024 (
echo Error: Bin file too small
exit /b 1
)
:: Verify content
hexdump -C -n 16 project.bin | find "FF FF FF FF" >nul
if %errorlevel% equ 0 (
echo Error: Blank check failed
exit /b 1
)
将bin生成流程与Git等版本控制系统结合:
自动版本标记
bat复制for /f "delims=" %%V in ('git describe --tags --always') do set BUILD_TAG=%%V
fromelf --bin --output=project_%BUILD_TAG%.bin project.axf
变更检测构建
bat复制git diff --quiet HEAD HEAD~1 -- src/
if %errorlevel% equ 0 (
echo No source changes, skipping build
) else (
call build.bat
)
自动化发布流程
bat复制:: Build and tag
call build.bat
for /f "delims=" %%V in ('git describe --tags --always') do set TAG=%%V
:: Create release package
7z a -tzip releases/%TAG%.zip bin/project.bin docs/*
:: Upload
curl -X POST -F "file=@releases/%TAG%.zip" http://server/upload