
简介本资源是一款面向单片机开发者的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 skip1 tokens1-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.123456480skip1跳过表头行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 skip1 tokens1-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 在构建失败时定位环节如b2HEX缺失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读取时将 BOMEF BB BF解析为不可见字符。解法用记事本打开version.txt→另存为→ 编码选ANSIWindows 默认→ 保存。或用 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 PATHC:\Program Files\Git\cmd;%PATH%或确保系统级PATH已添加 Git。5.5 现象After Build命令执行后Keil 界面卡住数秒光标变成沙漏原因脚本中git describe在网络仓库如 GitHub上执行DNS 解析或连接超时尤其在无网环境。解法为git命令添加超时git -c core.askpasstrue -c credential.helper -c http.lowSpeedLimit0 -c http.lowSpeedTime0 -C %PROJECT_DIR% describe --always --dirty --abbrev7 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 --abbrev7 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$nullPowerShell 中重定向 stderr 更简洁Get-Date -Format原生支持任意格式无需字符串切片Join-Path自动处理路径分隔符彻底规避\vs/问题-ForceCopy-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 的缝隙里埋下一颗可生长的种子。希望帮到你。本文还有配套的精品资源点击获取