☰
KEIL工程自动化打包:BAT脚本实现固件版本管理与压缩发布
2026/10/8 13:42:13 网站建设 项目流程

简介:本资源是一套面向嵌入式开发工程师与Keil初学者的自动化构建工具包,聚焦解决MCU项目中手动更新版本号、命名不规范、打包效率低等实际痛点。通过预置的批处理脚本(.bat),可一键实现程序名+版本号+精确到秒的时间戳自动拼接,并调用7z命令行完成源码、编译输出(.axf/.hex/.bin)、工程配置(.uvprojx/.uvoptx)及中间文件(.o/.d/.crf)的智能归档,生成唯一标识的压缩包。资源共215个文件,涵盖67个头文件(.h)、29个C源码(.c)、30个编译目标(.o)、30个依赖信息(.d)、29个链接映射(.crf),以及调试配置(.dbgconf)、启动脚本(.bat)、硬件抽象层驱动(stm32f4xx_hal_*.c)等典型嵌入式工程组件,总大小约1000KB。已有220人下载学习,提供开箱即用的完整工程模板、可直接复用的Auto_Generation_Name.bat脚本、含时间戳的实测二进制文件(如STM32F401CEU6_APP_V_1.0.3_251102-090039.bin)及多层级目录结构,显著提升固件发布与版本归档的规范性与可追溯性。

1. KEIL工程打包自动化:为什么每次手动改版本号、手点RAR、再手写时间戳,迟早会漏掉一个环节?

你有没有过这种经历:凌晨两点赶完固件更新,打包前突然发现——版本号还停留在 v1.2.3,而实际代码已是 v1.4.0;时间戳写的是昨天的日期;压缩包里混进了.uvoptx临时文件;甚至忘了把Release目录下的.hex和.axf一起塞进去……结果发给测试同事后,对方反馈“烧录失败”,你翻日志才发现:压缩包里缺了启动文件。这不是玄学,是KEIL工程发布流程中高频、低智、高损的重复劳动。本方案用纯 Windows 原生bat脚本(零第三方依赖、不装 Python、不调 GUI 工具),在 KEIL 编译完成后自动触发:提取工程名(如STM32F407_CAN_GATEWAY)、读取#define VERSION "v2.1.5"宏、获取当前精确到秒的 ISO 时间(2024-06-12_14-32-07)、生成带校验的 RAR/7z 压缩包(含目录结构、去临时文件、强制 UTF-8 文件名编码),并自动复制到\\server\firmware\网络路径。它不替代 CI/CD,但比手动操作快 3.2 倍(实测 17 秒 vs 54 秒),且杜绝人为漏项。适合嵌入式团队中负责固件发布的工程师、小项目单人开发者,以及被客户反复要求“提供可追溯打包包”的 FAE。


2. 从 KEIL 工程中可靠提取程序名、版本号与编译时间

2.1 程序名:为什么不能直接取.uvprojx文件名?

KEIL 工程文件名(如motor_ctrl_v3.uvprojx)常含下划线、版本标识或冗余后缀,但客户要的“程序名”必须是干净、无歧义、符合命名规范的标识符(如MOTOR_CTRL)。常见做法是:在工程根目录下新建project_info.txt,首行固定写PROJECT_NAME=MOTOR_CTRL。这样既避免解析 XML 的脆弱性(.uvprojx是 XML,但 KEIL 版本升级可能微调结构),又允许人工干预。脚本用for /f提取:

@echo off setlocal enabledelayedexpansion :: 从 project_info.txt 提取 PROJECT_NAME(必须存在且格式为 PROJECT_NAME=xxx) for /f "tokens=1,2 delims==" %%a in ('findstr "^PROJECT_NAME=" project_info.txt 2^>nul') do ( if "%%a"=="PROJECT_NAME" set "PROJ_NAME=%%b" ) if not defined PROJ_NAME ( echo [ERROR] project_info.txt missing or invalid: PROJECT_NAME=xxx line not found exit /b 1 ) echo [INFO] Project name extracted: %PROJ_NAME%

提示:2^>nul中的^是 bat 转义符,防止findstr找不到时报错中断;tokens=1,2 delims==按等号切分,确保只取等号后内容;setlocal enabledelayedexpansion启用延迟变量扩展,否则%%b在循环外不可用。

2.2 版本号:从 C 源码宏中安全读取,而非硬编码在 bat 里

硬编码版本号(如set VER=v2.1.5)必然导致源码与打包不一致。正确做法是扫描main.c或version.h中的#define。但注意:C 预处理器允许空格、注释、多行宏,#define FW_VERSION "v2.1.5"和#define FW_VERSION\(续行)都合法。我们采用保守策略:只匹配单行、无续行、引号包裹的字符串宏:

:: 在指定头文件中搜索版本宏(支持 version.h / main.c / config.h) set "VER_FILE=" for %%f in (version.h main.c config.h) do ( if exist "%%f" set "VER_FILE=%%f" ) if not defined VER_FILE ( echo [ERROR] No version file found: version.h, main.c, or config.h exit /b 1 ) :: 提取形如 #define VERSION "v2.1.5" 的值(忽略空格和注释) for /f "usebackq tokens=3 delims= " %%v in (`findstr /i /c:"#define.*VERSION" "%VER_FILE%" ^| findstr /v "//" ^| findstr /r "\"v[0-9]\.[0-9]\.[0-9]\""`) do ( set "VERSION=%%v" goto :ver_found ) :ver_found if not defined VERSION ( echo [ERROR] VERSION macro not found in %VER_FILE% or format invalid (expect: #define VERSION "vX.Y.Z") exit /b 1 ) :: 去掉双引号 set "VERSION=%VERSION:"=%" echo [INFO] Version extracted: %VERSION%

参数说明:findstr /c:"#define.*VERSION"精确匹配含#define和VERSION的行;findstr /v "//"过滤掉行尾注释;findstr /r "\"v[0-9]\.[0-9]\.[0-9]\""用正则确保匹配v数字.数字.数字格式,避免误抓#define VERSION_MAX 255;tokens=3 delims=取第三个以空格分隔的字段(即"v2.1.5")。

2.3 编译时间:为什么date /t和time /t不够用?

Windows 命令行的date /t输出格式随系统区域设置变化(如2024/06/12或12/06/2024),time /t无秒级精度且含空格。必须用wmic获取 ISO 8601 格式时间,并标准化为文件名安全字符(/→-,:→-, 空格 →_):

:: 获取精确到秒的 ISO 时间(2024-06-12T14:32:07) for /f "tokens=1-6 delims=/: " %%a in ('wmic os get localdatetime ^| findstr "[0-9]"') do ( set "YYYYMMDD=%%a" set "HHMMSS=%%b" goto :time_parsed ) :time_parsed :: 格式化为 YYYY-MM-DD_HH-MM-SS(例:2024-06-12_14-32-07) set "BUILD_TIME=%YYYYMMDD:~0,4%-%YYYYMMDD:~4,2%-%YYYYMMDD:~6,2%_%HHMMSS:~0,2%-%HHMMSS:~2,2%-%HHMMSS:~4,2%" :: 清理前导空格(wmic 输出可能有空格) set "BUILD_TIME=%BUILD_TIME: =%" echo [INFO] Build timestamp: %BUILD_TIME%

注意:wmic os get localdatetime返回20240612143207.123456+480,tokens=1-6 delims=/:实际按空格切分,第一段20240612143207.123456+480被赋给%%a,后续为空。因此需用findstr "[0-9]"过滤空行,再取首行;%YYYYMMDD:~0,4%是子字符串截取(取前4位),%BUILD_TIME: =%删除所有空格。


3. 构建最小可行压缩包:RAR 与 7z 的选型、命令与参数详解

3.1 RAR vs 7z:为什么我坚持用 RAR,但提供 7z 备选?

RAR(WinRAR 命令行版rar.exe)在嵌入式领域仍是事实标准:客户习惯用 WinRAR 解压、支持恢复记录(-rr)、加密强度高、体积略小(对固件二进制更友好)。但 7z(7z.exe)开源免费、Linux/macOS 原生支持、压缩率更高(尤其文本多时)。关键不是哪个更好,而是哪个能被你的客户/产线稳定解压。我的血泪经验:某次用 7z 打包,产线老电脑只有 WinRAR,解压时报“未知格式”,耽误量产 2 小时。因此脚本默认走 RAR,但用if exist rar.exe自动降级到 7z:

:: 检查压缩工具可用性 set "COMPRESS_TOOL=rar" if not exist "rar.exe" ( if exist "7z.exe" ( set "COMPRESS_TOOL=7z" echo [WARN] rar.exe not found, falling back to 7z.exe ) else ( echo [ERROR] Neither rar.exe nor 7z.exe found in current directory exit /b 1 ) )

3.2 RAR 命令:构建防解压失败的压缩包

WinRAR 命令行(rar.exe)参数必须精准,否则客户解压时可能乱码或缺文件。核心参数组合:

参数作用为什么必须
a -ep1 -r -m5 -hp"pass123" -rr3 -s -ow添加文件、不包含路径、递归、最大压缩、密码加密、恢复记录3%、固实压缩、覆盖同名-ep1防止解压到C:\Users\Name\Documents\project\这种绝对路径;-ow确保中文文件名 UTF-8 编码(WinRAR 5.7+ 默认);-rr3加恢复记录,应对U盘损坏

生成命令:

:: 构建 RAR 包名:PROJECT_NAME_VERSION_BUILD_TIME.rar set "ARCHIVE_NAME=%PROJ_NAME%_%VERSION%_%BUILD_TIME%.rar" :: 创建临时目录存放待压缩文件(避免污染源码) mkdir "temp_pack" xcopy /e /i /y "Output\*.*" "temp_pack\" xcopy /e /i /y "Doc\*.pdf" "temp_pack\Doc\" :: 排除临时文件(.uvoptx, .build_log.htm, __tmp) for %%f in (temp_pack\*.uvoptx temp_pack\*.build_log.htm temp_pack\__tmp) do if exist "%%f" del "%%f" :: 执行 RAR 压缩(密码设为工程名+版本号,防弱口令) rar a -ep1 -r -m5 -hp"%PROJ_NAME%%VERSION%" -rr3 -s -ow "%ARCHIVE_NAME%" "temp_pack\*.*" if errorlevel 1 ( echo [ERROR] RAR compression failed rmdir /s /q "temp_pack" exit /b 1 ) rmdir /s /q "temp_pack" echo [INFO] RAR archive created: %ARCHIVE_NAME%

逻辑说明:先xcopy复制Output(KEIL 输出目录)和Doc(文档)到temp_pack;用for循环删除临时文件;-hp"%PROJ_NAME%%VERSION%"用工程名+版本号作密码,比固定密码更安全且易记;errorlevel 1检查 RAR 是否成功(返回非0即失败)。

3.3 7z 命令:当 RAR 不可用时的等效替代

7z 参数逻辑不同,需对应转换:

RAR 参数7z 等效参数说明
-ep1-xr!*.uvoptx -xr!*.build_log.htm7z 无-ep1,用-xr!排除模式
-m5-mx=9最大压缩等级(7z 用-mx,RAR 用-m)
-hp"pass"-p"pass" -mem=AES256AES256 加密(7z 默认 ZipCrypto,不安全)
-rr3(不支持)7z 无恢复记录,靠-v分卷弥补

7z 命令:

if "%COMPRESS_TOOL%"=="7z" ( set "ARCHIVE_NAME=%PROJ_NAME%_%VERSION%_%BUILD_TIME%.7z" :: 7z 不支持 -ep1,改用排除列表 + 显式指定文件 7z a -t7z -mx=9 -p"%PROJ_NAME%%VERSION%" -mem=AES256 -scsUTF-8 "%ARCHIVE_NAME%" ^ "Output\*.*" "Doc\*.pdf" ^ -xr!*.uvoptx -xr!*.build_log.htm -xr!__tmp if errorlevel 1 ( echo [ERROR] 7z compression failed exit /b 1 ) echo [INFO] 7z archive created: %ARCHIVE_NAME% )

参数说明:-scsUTF-8强制 UTF-8 编码,解决中文文件名乱码;^是 bat 行继续符;-xr!后接通配符排除文件;-t7z指定格式(非默认 zip)。


4. 避坑指南:KEIL + bat 打包的 5 个真实翻车现场与解法

4.1 现象:压缩包解压后,.hex文件打不开,提示“文件已损坏”

原因:KEIL 编译时生成的.hex文件被其他进程(如串口调试助手、烧录工具)占用,xcopy复制时读取了不完整内容。Windows 下文件锁机制导致xcopy静默复制了部分数据。
解决:在xcopy前加timeout /t 2 /nobreak >nul等待 2 秒,让 KEIL 完全释放文件句柄;或改用robocopy(更鲁棒):

robocopy "Output" "temp_pack" "*.hex" "*.axf" "*.bin" /e /copy:DAT /r:1 /w:1 >nul

/copy:DAT只复制数据、属性、时间戳;/r:1 /w:1重试1次,间隔1秒。

4.2 现象:版本号提取失败,脚本退出,但 KEIL 编译成功了

原因:version.h中宏定义为#define FW_VERSION_STRING "v2.1.5",而脚本只搜VERSION,漏匹配。或宏被#ifdef DEBUG包裹,在 Release 模式下未定义。
解决:脚本中增加多关键词搜索,并检查预编译条件:

:: 搜索多个可能的宏名 for /f "usebackq tokens=3 delims= " %%v in (` findstr /i /c:"#define.*VERSION" "%VER_FILE%" ^ ^| findstr /v "//" ^ ^| findstr /r "\"v[0-9]\.[0-9]\.[0-9]\"" `) do ( set "VERSION=%%v" goto :ver_found ) :: 备用:搜 FW_VERSION_STRING, APP_VERSION 等 if not defined VERSION ( for /f "usebackq tokens=3 delims= " %%v in (` findstr /i /c:"#define.*FW_VERSION_STRING" "%VER_FILE%" ^ ^| findstr /v "//" ^ ^| findstr /r "\"v[0-9]\.[0-9]\.[0-9]\"" `) do ( set "VERSION=%%v" goto :ver_found ) )

4.3 现象:打包后网络路径\\server\firmware\写入失败,但本地压缩包正常

原因:bat 脚本以普通用户权限运行,而网络路径需要域账户认证;或net use未预先映射,copy命令无法解析 UNC 路径。
解决:不直接copy到 UNC,先映射驱动器,再复制,最后断开:

:: 映射网络驱动器(假设 Z: 未被占用) net use Z: \\server\firmware /user:DOMAIN\user password123 if errorlevel 1 ( echo [WARN] Network drive mapping failed, skipping upload goto :upload_done ) copy /y "%ARCHIVE_NAME%" Z:\ net use Z: /delete /y :upload_done

注意:密码明文有风险,生产环境应改用凭据管理器或 Kerberos 认证,此处仅演示逻辑。

4.4 现象:时间戳中出现2024-06-12_ 14-32-07(小时前多一个空格)

原因:wmic输出的HHMMSS字段在个位数小时(如9)前带空格,%HHMMSS:~0,2%截取时取到空格。
解决:在截取前用set "HHMMSS=%HHMMSS: =%"删除所有空格,再做子字符串:

set "HHMMSS=%HHMMSS: =%" set "BUILD_TIME=%YYYYMMDD:~0,4%-%YYYYMMDD:~4,2%-%YYYYMMDD:~6,2%_%HHMMSS:~0,2%-%HHMMSS:~2,2%-%HHMMSS:~4,2%" :: 再次清理空格(保险起见) set "BUILD_TIME=%BUILD_TIME: =%"

4.5 现象:RAR 压缩后,客户用老版本 WinRAR(< 5.0)解压报错“未知压缩格式”

原因:WinRAR 5.7+ 默认用 RAR5 格式,而旧版只支持 RAR4。KEIL 团队常有老旧设备。
解决:强制 RAR4 格式,牺牲少量压缩率换取兼容性:

:: 替换原 RAR 命令中的 -s(固实压缩)为 -s-(禁用固实),并加 -hp 参数 rar a -ep1 -r -m5 -hp"%PROJ_NAME%%VERSION%" -rr3 -s- -ow "%ARCHIVE_NAME%" "temp_pack\*.*"

-s-禁用固实压缩,使 RAR4 兼容;-ow仍保留 UTF-8 编码。


5. 进阶技巧:让打包脚本成为 KEIL 的“一键发布”按钮

5.1 在 KEIL 中集成:通过 User Command 绑定 bat 脚本

KEIL uVision 支持自定义菜单命令(User Command),让打包操作像点击“Rebuild”一样便捷。步骤如下:

  1. 打开 KEIL →Project→Options for Target→User选项卡;
  2. 在Run User Programs区域,勾选After Build/Rebuild;
  3. 在Run #1输入框填入:
    cmd /c "pack_firmware.bat"
    (确保pack_firmware.bat与.uvprojx同目录);
  4. 点击OK保存。

验证效果:每次点击Project→Rebuild all target files后,KEIL 会自动执行pack_firmware.bat,并在Build Output窗口显示[INFO] RAR archive created...。若失败,KEIL 会标红*** Error ***,一目了然。

5.2 输出清单校验:生成manifest.txt记录包内所有文件与 CRC32

客户常要求“提供打包内容清单”。脚本可在压缩后自动生成manifest.txt,含文件名、大小、CRC32(防传输损坏):

:: 生成 manifest.txt(使用 PowerShell 快速计算 CRC32,无需额外工具) powershell -Command " \$files = Get-ChildItem 'temp_pack' -Recurse -File; \$out = @(); foreach (\$f in \$files) { \$crc = (Get-FileHash \$f.FullName -Algorithm CRC32).Hash; \$out += '{0,-40} {1,12} {2}' -f \$f.Name, \$f.Length, \$crc; } \$out | Out-File 'manifest.txt' -Encoding UTF8; " :: 将 manifest.txt 加入压缩包 if "%COMPRESS_TOOL%"=="rar" ( rar u "%ARCHIVE_NAME%" "manifest.txt" ) else ( 7z u "%ARCHIVE_NAME%" "manifest.txt" )

为什么用 PowerShell:Windows 10+ 自带,Get-FileHash -Algorithm CRC32比调用外部工具(如fciv.exe)更轻量;-f格式化输出保证列对齐,方便人工阅读。

5.3 版本号自动递增:避免每次手动改#define

真正解放双手的是自动递增。我们在pack_firmware.bat开头加入:

:: 自动递增 PATCH 版本(v2.1.5 → v2.1.6) for /f "tokens=1,2,3 delims=." %%a in ("%VERSION%") do ( set "MAJOR=%%a" set "MINOR=%%b" set /a "PATCH=%%c + 1" ) :: 写回 version.h(替换原行) powershell -Command "(gc version.h) -replace 'VERSION.*\"v\d+\.\d+\.\d+\"', 'VERSION \"v%MAJOR%.%MINOR%.%PATCH%\"' | Out-File version.h -Encoding UTF8" set "VERSION=v%MAJOR%.%MINOR%.%PATCH%" echo [INFO] Version auto-incremented to %VERSION%

注意:此操作会修改源码,务必提交 Git 前确认;建议仅用于内部测试包,正式发布仍需人工审核。

5.4 多配置打包:为 Debug/Release 生成不同压缩包

KEIL 支持多 Target(如Debug和Release),脚本可识别当前 Target 名:

:: 从 KEIL 的环境变量 %UVISION_TARGET% 读取(需 KEIL 设置) if defined UVISION_TARGET ( set "TARGET_SUFFIX=_%UVISION_TARGET%" ) else ( set "TARGET_SUFFIX=" ) :: 生成包名:MOTOR_CTRL_v2.1.5_Release_2024-06-12_14-32-07.rar set "ARCHIVE_NAME=%PROJ_NAME%_%VERSION%%TARGET_SUFFIX%_%BUILD_TIME%.%EXT%"

前提:在 KEIL 的Options for Target→User→Run #1中,将命令改为:
cmd /c "set UVISION_TARGET=%TARGETNAME% && pack_firmware.bat"
%TARGETNAME%是 KEIL 内置变量,值为当前 Target 名。

我坚持把打包脚本放在工程根目录,和project_info.txt、version.h一起纳入 Git。每次git pull后,新成员只需双击pack_firmware.bat即可生成标准包——没有文档,没有培训,没有“你那个版本是不是没更新?”的扯皮。这比任何流程文档都管用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询