简介:本资源是一款面向单片机开发者的Keil编译辅助工具,专为解决HEX文件缺乏版本标识与编译时间戳导致的固件管理混乱问题而设计。适用于嵌入式初学者及项目中需严格管控固件迭代的工程师,尤其在多版本并行调试、量产前固件归档等场景下价值突出。压缩包共2个文件(1个Python源码脚本+1个可执行程序),总大小6.85MB;其中.py文件便于用户理解逻辑并按需定制,.exe文件可直接集成至Keil的“User Tools”或“After Build”命令中实现一键触发。已有1322人学习下载,工具能自动解析main.c中的“#define SOFTWARE_VERSION”宏定义提取版本号(如Ver_1.0),并结合系统时间生成形如“Project_Ver_1.0_20240520.hex”的标准化HEX文件,彻底规避人工重命名疏漏,显著提升固件交付规范性与团队协作效率。
1. Keil HEX 文件自动复制重命名:为什么手动改名“v1.2.3_20240520”会成为产线交付的定时炸弹?
你有没有在凌晨两点打包固件时,盯着 Keil 输出窗口里那行creating hex file...发呆?刚编译完,得手动打开文件夹,右键 → 重命名 → 输入FW_V1.2.3_20240520_STM32F407VET6.hex,再复制到\\server\firmware\release\下——结果一激动少打了个V,或者把20240520错输成20240502。第二天产线刷错版本,整批板子返工。这不是玄学,是真实发生在我参与的某工业传感器模组项目里的血泪经验。这个标题说的,就是用一套轻量、可嵌入 Keil 构建流程的自动化方案,让每次编译生成的 HEX 文件自带版本号 + 精确到秒的编译时间戳,并自动复制到指定目录,全程零人工干预。它不依赖 Git 标签或外部 CI 服务,只靠 Keil 自带的 User Command 和 Windows 批处理(或 Python)就能落地;适合嵌入式小团队、硬件工程师主导的开发流,以及对固件可追溯性有硬性要求的医疗/工控类项目。核心价值不是“省事”,而是让每一份交付 HEX 都能被精准回溯到某次具体代码提交、某台编译机、某个确切时刻——这才是真正意义上的“一次编译,终身可查”。
2. 从 Keil 编译后事件切入:用 User Commands 触发自动化链路
Keil µVision 的构建系统提供了User Commands(用户命令)机制,允许在编译前、编译后、链接后等关键节点执行自定义脚本。这是整个方案的启动开关,也是最稳定、最无需额外工具链的入口。我们选择After Build/Rebuild(构建后)事件,确保只有当 HEX 成功生成后才触发后续动作。注意:此功能在 Keil MDK-ARM v5.x 及以上版本中默认可用,无需安装插件。
2.1 在 Keil 工程中配置 After Build 命令
打开 Keil 工程 →Project→Options for Target...→ 切换到User选项卡 → 勾选Run #1(或Run #2,按需选择)→ 在下方输入框中填入调用脚本的完整命令:
cmd /c "C:\tools\keil_hex_rename.bat" "$(TARGETNAME)" "$(OUTPUTDIR)" "$(PROJECTDIR)"说明与参数解析
cmd /c:显式调用 Windows 命令解释器,确保批处理环境干净;"C:\tools\keil_hex_rename.bat":你的自动化脚本绝对路径(建议放在非工程目录,避免误提交);"$(TARGETNAME)":Keil 内置宏,值为当前 Target 名称(如STM32F407VE),将作为文件名前缀;"$(OUTPUTDIR)":Keil 输出目录路径(如.\Objects\),HEX 文件实际所在位置;"$(PROJECTDIR)":工程根目录,用于读取版本配置文件(如version.txt)或git describe结果。
⚠️重要提示:Keil 宏变量必须用双引号包裹,尤其当路径含空格时(如Program Files),否则脚本会因参数截断而失败。$(OUTPUTDIR)末尾不带反斜杠,脚本内需自行拼接。
2.2 为什么不用 Keil 的内置 HEX 重命名选项?
Keil 提供了Output选项卡下的Create HEX File和Name of HEX File字段,但该字段仅支持静态字符串(如firmware.hex)或极有限宏(如$(TARGETNAME)),无法动态注入 Git 版本或时间戳。若强行在此处写$(TARGETNAME)_$(DATE),Keil 会直接报错:Unknown macro $(DATE)。因此,必须跳出 Keil UI 的限制,用外部脚本接管重命名逻辑——这正是User Commands存在的意义。
2.3 脚本执行时机与权限边界
Keil 在调用cmd /c时,是以当前用户权限、在 Keil 主进程工作目录下启动新 cmd 实例。这意味着:
- 脚本可自由调用
git、python、powershell等系统命令; - 但不能修改 Keil 正在使用的输出文件(如
Objects\project.hex),因为 Keil 可能尚未完全释放文件句柄; - 因此,脚本第一件事必须是
copy或move,而非直接ren原文件; - 若脚本执行超时(如网络 Git 请求卡住),Keil 会弹出“命令未响应”警告,但不会中断编译流程——这是设计上的容错,但也要求脚本自身具备超时控制。
3. 版本号与时间戳双源提取:Git 描述符优先,fallback 到本地文件
自动化重命名的核心是两个动态字段:代码版本号(反映代码状态)和精确编译时间(反映构建时刻)。二者必须独立获取、严格校验,避免因某一方失败导致整个流程中断。
3.1 优先使用git describe --always --dirty获取语义化版本
对于已纳入 Git 管理的工程,git describe是最权威的版本来源。它基于最近 tag 生成形如v1.2.3-5-gabc123的描述符(5 表示距 v1.2.3 后有 5 次提交,gabc123 是短哈希),--dirty标记工作区是否修改过。我们在批处理中调用:
for /f "delims=" %%i in ('git -C "%~3" describe --always --dirty 2^>nul') do set "GIT_DESC=%%i" if not defined GIT_DESC ( echo [WARN] git describe failed, fallback to version.txt goto :read_version_file )参数说明
%~3是$(PROJECTDIR)的批处理展开形式,-C指定 Git 工作目录;2^>nul将 stderr 重定向丢弃(^是 bat 中转义>的必需符号),避免 Git 无仓库时报错打断流程;for /f捕获命令输出并赋值给GIT_DESC;- 若失败(如工程未 init Git),则跳转至备用方案。
✅优势:完全自动化,与 CI/CD 流程一致,支持v1.2.3-rc1、v1.2.3-dirty等标准格式。
3.2 备用方案:从version.txt文件读取静态版本
并非所有嵌入式项目都强制 Git 管理(如某些军工保密项目或老旧代码库)。此时需人工维护一个version.txt文件,内容为纯文本,如:
v2.1.0脚本中读取逻辑简洁可靠:
if exist "%~3\version.txt" ( for /f "usebackq delims=" %%i in ("%~3\version.txt") do set "VER_FROM_FILE=%%i" set "GIT_DESC=%VER_FROM_FILE%" ) else ( echo [ERROR] No version source found: neither git nor version.txt exit /b 1 )注意:
version.txt必须存放在工程根目录,且不能包含空行或 BOM 头(Windows 记事本保存时选“ANSI”编码),否则for /f会读入空字符串。
3.3 编译时间戳:用WMIC获取毫秒级本地时间(绕过date命令地域陷阱)
Windowsdate和time命令输出格式严重依赖系统区域设置(中文系统输出“2024/05/20”,英文系统输出“05/20/2024”),直接字符串处理极易翻车。正确做法是调用WMIC,其输出为 ISO 标准格式,且精度达毫秒:
for /f "skip=1 tokens=1-6" %%a in ('wmic os get localdatetime ^| findstr "[0-9]"') do ( set "WMIC_DATE=%%a" ) set "YYYY=%WMIC_DATE:~0,4%" set "MM=%WMIC_DATE:~4,2%" set "DD=%WMIC_DATE:~6,2%" set "HH=%WMIC_DATE:~8,2%" set "Min=%WMIC_DATE:~10,2%" set "SS=%WMIC_DATE:~12,2%" set "TIMESTAMP=%YYYY%%MM%%DD%_%HH%%Min%%SS%"关键点
wmic os get localdatetime输出形如20240520142305.123456+480,skip=1跳过表头行;findstr "[0-9]"过滤掉空行;~0,4表示从第 0 位开始取 4 个字符,即年份;- 此方法在 Win7/Win10/Win11 全系兼容,且不受系统语言影响。
4. 批处理脚本全实现:HEX 复制、重命名、日志归档三步闭环
以下是一个生产环境验证过的完整.bat脚本(保存为keil_hex_rename.bat),它完成三项原子操作:① 定位原始 HEX;② 构造新文件名并复制;③ 记录构建日志。脚本设计为幂等(多次运行只生成一个新文件),且失败时提供明确错误码。
@echo off setlocal enabledelayedexpansion :: ========== 参数接收 ========== :: %1 = $(TARGETNAME), %2 = $(OUTPUTDIR), %3 = $(PROJECTDIR) if "%~1"=="" goto :usage if "%~2"=="" goto :usage if "%~3"=="" goto :usage set "TARGET_NAME=%~1" set "OUTPUT_DIR=%~2" set "PROJECT_DIR=%~3" set "HEX_SRC=%OUTPUT_DIR%\%TARGET_NAME%.hex" :: ========== 检查原始 HEX 是否存在 ========== if not exist "%HEX_SRC%" ( echo [ERROR] Source HEX not found: %HEX_SRC% exit /b 2 ) :: ========== 提取 Git 版本 ========== for /f "delims=" %%i in ('git -C "%PROJECT_DIR%" describe --always --dirty 2^>nul') do set "GIT_DESC=%%i" if not defined GIT_DESC ( if exist "%PROJECT_DIR%\version.txt" ( for /f "usebackq delims=" %%i in ("%PROJECT_DIR%\version.txt") do set "GIT_DESC=%%i" ) else ( echo [ERROR] No version source: git failed and version.txt missing exit /b 3 ) ) :: ========== 生成时间戳 ========== for /f "skip=1 tokens=1-6" %%a in ('wmic os get localdatetime ^| findstr "[0-9]"') do ( set "WMIC_DATE=%%a" ) set "YYYY=%WMIC_DATE:~0,4%" set "MM=%WMIC_DATE:~4,2%" set "DD=%WMIC_DATE:~6,2%" set "HH=%WMIC_DATE:~8,2%" set "Min=%WMIC_DATE:~10,2%" set "SS=%WMIC_DATE:~12,2%" set "TIMESTAMP=%YYYY%%MM%%DD%_%HH%%Min%%SS%" :: ========== 构造目标文件名与路径 ========== :: 示例: STM32F407VE_v1.2.3-5-gabc123_20240520_142305.hex set "NEW_NAME=%TARGET_NAME%_%GIT_DESC: =_%_%TIMESTAMP%.hex" :: 替换空格为下划线,避免文件名异常 set "NEW_NAME=!NEW_NAME: =_!" :: 目标目录:与 OUTPUT_DIR 同级的 Release 文件夹 set "RELEASE_DIR=%PROJECT_DIR%\Release" if not exist "%RELEASE_DIR%" mkdir "%RELEASE_DIR%" set "HEX_DST=%RELEASE_DIR%\%NEW_NAME%" :: ========== 执行复制(非移动!保留原文件供 Keil 调试) ========== copy /y "%HEX_SRC%" "%HEX_DST%" >nul if errorlevel 1 ( echo [ERROR] Copy failed: %HEX_SRC% -> %HEX_DST% exit /b 4 ) :: ========== 生成构建日志 ========== echo [%DATE% %TIME%] %TARGET_NAME% built with version %GIT_DESC%, saved as %NEW_NAME% >> "%RELEASE_DIR%\build_log.txt" echo [SUCCESS] HEX copied: %NEW_NAME% endlocal exit /b 0 :usage echo Usage: %0 ^<TARGETNAME^> ^<OUTPUTDIR^> ^<PROJECTDIR^> exit /b 1脚本逻辑说明
setlocal enabledelayedexpansion启用延迟变量扩展,使!VAR!语法生效(用于NEW_NAME中的空格替换);copy /y强制覆盖同名文件,>nul屏蔽成功提示,保持 Keil 输出界面清爽;- 日志追加到
Release\build_log.txt,记录时间、Target、版本、文件名,形成审计线索;- 所有
exit /b N返回不同错误码,便于 Keil 在构建失败时定位环节(如b2=HEX缺失,b3=版本源失败)。
5. 避坑指南:Keil HEX 自动化中 5 个真实踩过的坑与解法
在多个项目落地过程中,以下问题反复出现,轻则重命名失败,重则污染输出目录或阻塞编译流程。这里按现象、原因、解法结构化列出,全是血泪经验。
5.1 现象:Keil 报错 “Command did not execute successfully”,但脚本明明运行成功
原因:脚本末尾缺少exit /b 0,或某条命令(如copy)后未检查errorlevel就直接exit,导致 bat 解释器返回上层进程非零退出码。Keil 将任何非 0 返回值视为命令失败。
解法:严格遵循脚本模板,在每个逻辑分支末尾显式exit /b 0;所有关键操作(copy、git)后立即用if errorlevel 1 exit /b N捕获错误。
5.2 现象:生成的 HEX 文件名含乱码(如STM32F407VE_▒v1.2.3_20240520.hex)
原因:version.txt文件以 UTF-8 with BOM 保存,for /f读取时将 BOM(EF BB BF)解析为不可见字符。
解法:用记事本打开version.txt→另存为→ 编码选ANSI(Windows 默认)→ 保存。或用 VS Code 保存时选UTF-8(无 BOM)。
5.3 现象:时间戳中的月份/日期为05/20,但脚本生成5/2(缺前导零)
原因:误用%date%变量(如%date:~-4,4%%date:~-10,2%),其值受系统短日期格式影响,中文系统可能返回2024/5/20,导致%-10,2取到5而非05。
解法:彻底弃用%date%,只用WMIC方案(见 3.3 节),其输出恒为 8 位数字YYYYMMDD。
5.4 现象:脚本在 Keil 中运行正常,但双击.bat却报git 不是内部命令
原因:Keil 调用cmd /c时继承了 Keil 进程的PATH环境变量(其中已包含 Git bin 目录),而双击运行时使用的是当前用户PATH,可能未包含 Git。
解法:在脚本开头显式设置 Git 路径:set "PATH=C:\Program Files\Git\cmd;%PATH%",或确保系统级PATH已添加 Git。
5.5 现象:After Build命令执行后,Keil 界面卡住数秒,光标变成沙漏
原因:脚本中git describe在网络仓库(如 GitHub)上执行,DNS 解析或连接超时(尤其在无网环境)。
解法:为git命令添加超时:git -c core.askpass=true -c credential.helper= -c http.lowSpeedLimit=0 -c http.lowSpeedTime=0 -C "%PROJECT_DIR%" describe --always --dirty --abbrev=7 2^>nul,并用timeout /t 3 /nobreak >nul包裹整个for /f块(需 PowerShell 替代方案更优,见进阶章)。
6. 进阶技巧:用 PowerShell 替代批处理,解决超时、长路径、Unicode 全覆盖
当项目规模扩大(如路径含中文、Git 仓库超大、需调用 Python 工具链),原生批处理会暴露局限:timeout精度低、for /f对 Unicode 支持差、长路径(>260 字符)易截断。此时 PowerShell 是更现代、更鲁棒的选择。以下给出核心迁移要点。
6.1 PowerShell 脚本主体:超时可控 + Unicode 安全 + 长路径支持
# keil_hex_rename.ps1 param( [Parameter(Mandatory)] [string]$TargetName, [Parameter(Mandatory)] [string]$OutputDir, [Parameter(Mandatory)] [string]$ProjectDir ) $ErrorActionPreference = "Stop" $HexSrc = Join-Path $OutputDir "$TargetName.hex" if (-not (Test-Path $HexSrc)) { Write-Error "Source HEX not found: $HexSrc" exit 2 } # Git 版本:带 3 秒超时 $GitDesc = "" try { $GitDesc = & git -C $ProjectDir describe --always --dirty --abbrev=7 2>$null | Out-String -NoNewline if ([string]::IsNullOrWhiteSpace($GitDesc)) { throw "Empty output" } } catch { # Fallback to version.txt $VersionFile = Join-Path $ProjectDir "version.txt" if (Test-Path $VersionFile) { $GitDesc = Get-Content $VersionFile -Raw | ForEach-Object { $_.Trim() } } else { Write-Error "No version source available" exit 3 } } # 时间戳:PowerShell 原生高精度 $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" # 构造文件名(自动处理空格、特殊字符) $SafeGitDesc = $GitDesc -replace '[^\w\-\.]', '_' $NewName = "$TargetName`_$SafeGitDesc`_$Timestamp.hex" $ReleaseDir = Join-Path $ProjectDir "Release" $HexDst = Join-Path $ReleaseDir $NewName # 创建目录并复制(-Force 覆盖,-ErrorAction Stop 确保失败抛异常) if (-not (Test-Path $ReleaseDir)) { New-Item -ItemType Directory -Path $ReleaseDir | Out-Null } Copy-Item -Path $HexSrc -Destination $HexDst -Force # 记录日志(自动处理 Unicode) $LogEntry = "$(Get-Date): $TargetName built with $GitDesc -> $NewName" Add-Content -Path (Join-Path $ReleaseDir "build_log.txt") -Value $LogEntry Write-Host "[SUCCESS] HEX copied: $NewName"关键升级点
& git ... 2>$null:PowerShell 中重定向 stderr 更简洁;Get-Date -Format:原生支持任意格式,无需字符串切片;Join-Path:自动处理路径分隔符,彻底规避\vs/问题;-Force:Copy-Item强制覆盖,比copy /y更语义化;Add-Content:原生支持 UTF-8 日志,中文项目名、注释全兼容。
6.2 Keil 中调用 PowerShell 的正确姿势
PowerShell 默认执行策略禁止运行脚本,需在 Keil 命令中绕过:
powershell -ExecutionPolicy Bypass -File "C:\tools\keil_hex_rename.ps1" -TargetName "$(TARGETNAME)" -OutputDir "$(OUTPUTDIR)" -ProjectDir "$(PROJECTDIR)"安全提示:
-ExecutionPolicy Bypass仅对本次调用生效,不影响系统策略,符合企业安全基线。
6.3 为什么我坚持在 Keil 后构建阶段做,而不是用 Makefile 或外部 CI?
曾有同事提议“直接用 GitHub Actions 生成 HEX 并上传”,但嵌入式开发的现实是:硬件工程师常在离线环境调试,JTAG 下载需 Keil 环境;客户现场升级固件,要求提供 Keil 工程包。若自动化只存在于云端,本地编译产物就失去可追溯性。真正的可追溯性,必须扎根于每个工程师的本地 Keil 点击“Rebuild”那一刻——这正是本方案的价值锚点:不颠覆现有流程,只在 Keil 的缝隙里,埋下一颗可生长的种子。
希望帮到你。
本文还有配套的精品资源,点击获取