简介:面向使用KEIL进行STM32等ARM嵌入式开发的工程师,这份资源提供了一套“程序名+版本号+时间+自动打包”的完整实践方案,解决每次编译后手动修改版本号、逐个压缩备份文件既繁琐又易错的问题。压缩包共215个文件、约1000KB,包含c/h源码与STM32 HAL库文件、uvprojx工程配置、自动生成批处理脚本、axf/bin/hex可执行输出,以及o/d/crf等编译中间文件;以STM32F401CEU6串口Ringbuffer示例工程为载体,清晰展示脚本如何读取工程信息、拼接系统时间并调用7z/RAR命令行工具,生成带唯一标识的压缩包。其中bat脚本是自动化的关键入口,示例工程内已配置好相关调用关系,便于直接观察整个流程。目前已有220人学习,适合希望用bat脚本自动化构建流程的中级嵌入式开发者。资源不仅可直接借鉴脚本写法,修改项目名与路径即可移植到自己的工程,还能帮助理解编译器输出文件与归档工具的配合细节,有效减少人为错误,让版本备份和管理更加规范高效。
1. 受够手改版本号了:KEIL 用批处理自动命名加打包,一次配置长期省事
做嵌入式固件开发的同行应该都有这种体会:代码写完编译通过只是第一步,最烦的是每次出包都要手动把版本号从 V_1.0.2 改成 V_1.0.3,还要加上当前日期时间,再去找压缩软件打包。某个下午我因为连续三次忘记改版本号,把旧固件发给了现场同事,结果排查了两天才发现烧错包。从那以后我就强制自己找自动化方案,最后落地的就是这套基于 KEIL 工程、bat 脚本、RAR/7z 命令行工具的配置。它的核心逻辑不复杂:编译完成后自动提取工程名和当前时间,拼出“项目名_版本号_年月日-时分秒”的唯一文件名,再调用压缩工具把 bin、axf、源文件一起归档。适合所有用 KEIL 做 STM32 或 ARM 开发的工程师,尤其是多版本迭代、需要归档留痕的项目。
2. 想让 KEIL 编译完自动收尾:先理清批处理在哪个环节介入
2.1 传统手动出包的三个痛点和自动化后的流程变化
手动出包的流程一般是:修改版本号 — 编译 — 找到输出目录 — 复制 bin 文件 — 重命名加上日期时间 — 打开压缩软件打包 — 归档到指定目录。这套流程里最容易翻车的是文件复制路径和版本号遗漏,一旦项目目录切换过或者 KEIL 输出目录改过,旧路径失效后生成的包就是空的或旧的。
自动化方案把流程收缩成两步:修改版本号(仍然需要人改)— 编译。其余全部由 bat 脚本在编译完成后自动执行。KEIL 提供了 After Build 选项,也就是编译通过后可以执行用户命令,这一步是整套方案能衔接的关键。它不依赖外部插件,用的都是 Windows 自带的批处理能力和压缩工具的命令行接口。
这里要先明确一个细节:KEIL 的 After Build 执行的是命令行字符串,它本质上就是在 cmd 环境里跑你指定的命令,所以你能在 cmd 里做的事它都能做。但注意它默认的当前工作目录通常不是工程所在目录,如果脚本里用了相对路径,最常见的结果是脚本找不到文件。我一般会在脚本开头强制 cd /d %~dp0,让工作目录切到脚本自身所在路径,这一步能省掉后续大量路径类报错。
2.2 KEIL 的 After Build 配置位置和命令行参数格式
打开 KEIL 工程后,从菜单 Options for Target 进入,切到 User 选项卡。这个页面分两块:Before Build/Rebuild 和 After Build/Rebuild,我们只用 After 部分。那里有一行 Run #1,勾选后填入调用 bat 的完整命令。
建议的写法是:
cd /d D:\work\STM32F401CEU6_UART_Ringbuffer && Auto_Generation_Name.bat STM32F401CEU6这条命令的含义:先切到工程目录,再执行 Auto_Generation_Name.bat,并给它传一个参数 STM32F401CEU6,这个参数用于拼接产物文件名。如果工程路径里没有空格,可以不加引号;但绝大多数人的路径带中文或空格,建议写成:
cd /d "D:\work\STM32F401CEU6_UART_Ringbuffer" && call "Auto_Generation_Name.bat" STM32F401CEU6注意这里用了 call 而不是直接写文件名,原因是 bat 脚本中如果不加 call,执行完子脚本后当前命令行会退出,KEIL 的后续日志可能不完整。加了 call 相当于子进程返回后再继续,结构更稳。
2.3 为什么把版本号放在脚本里而不是 KEIL 配置里
版本号这种东西每个团队都有自己的习惯。放在 KEIL 配置里输出的文件名固定,但每次发版要改 KEIL 工程配置,属于没省事。放在 bat 脚本里作为变量,好处是版本号可以和 KEIL 工程的 C 代码版本宏分开控制,脚本里只维护一个标记位,发版时改一次即可。
另一个推荐做法是把版本号用变量写在脚本顶部集中管理:
set APP_VERSION=V_1.0.3后面打包和重命名都引用这个变量,想升级版本号只需改一行。关键点是脚本里所有用到版本号的地方都不要硬编码文件名,统一走变量拼接,否则容易出现改了版本号但压缩包名字没跟着变的尴尬情况。
3. bat 脚本的完整实现:从工程名提取到 RAR/7z 命令行压缩
3.1 提取时间并格式化成“年份-小时分钟秒”的唯一标识
Windows 的 cmd 环境里获取时间最常用的办法是 %date% 和 %time%,但这两个变量在不同语言和日期格式下输出差异很大。最常见的翻车现场是日期里带“星期”字样,比如“2025/11/02 周日”,直接用字符串截取很容易切错位置。
我一般不用系统自带的 %date%,而是用 wmic 命令获取干净的日期时间字段:
@echo off setlocal enabledelayedexpansion for /f "tokens=2 delims==" %%i in ('wmic OS Get LocalDateTime /value ^| find "="') do set datetime=%%i set YEAR=%datetime:~0,4% set MONTH=%datetime:~4,2% set DAY=%datetime:~6,2% set HOUR=%datetime:~8,2% set MINUTE=%datetime:~10,2% set SECOND=%datetime:~12,2% set STAMP=%YEAR%%MONTH%%DAY%-%HOUR%%MINUTE%%SECOND% echo 生成时间戳: %STAMP%这段脚本的逻辑:wmic 输出的是标准格式的日期时间字符串,例如 20251102090039.500000+480,通过 for /f 提取等号后面的部分,再用变量切片分别取出年月日时分秒。最终拼成 20251102-090039 这样的时间戳,和输入项目中的 bin 文件命名格式 STM32F401CEU6_APP_V_1.0.3_251102-090039.bin 保持一致。
注意 setlocal enabledelayedexpansion 这行不是摆设,后面如果有循环或括号块内引用变量,延迟展开能避免变量值为空的问题。
3.2 从 KEIL 输出目录找到编译产物并拼接完整文件名
KEIL 编译后的产物主要是 .axf 调试文件和 .bin 烧录文件。脚本要做的是找到这些文件、按规则重命名、复制到归档目录。下面这段脚本放在同一份 bat 里,承接上面的时间戳变量:
set PROJ_NAME=STM32F401CEU6_UART_Ringbuffer set APP_VERSION=V_1.0.3 set OUTPUT_DIR=.\Objects set ARCHIVE_DIR=.\Release if not exist "%ARCHIVE_DIR%" mkdir "%ARCHIVE_DIR%" set BIN_NAME=%PROJ_NAME%_APP_%APP_VERSION%_%STAMP%.bin set AXP_NAME=%PROJ_NAME%_APP_%APP_VERSION%_%STAMP%.axf copy /y "%OUTPUT_DIR%\%PROJ_NAME%.bin" "%ARCHIVE_DIR%\%BIN_NAME%" copy /y "%OUTPUT_DIR%\%PROJ_NAME%.axf" "%ARCHIVE_DIR%\%AXP_NAME%" echo 已生成: %BIN_NAME%这里 OUTPUT_DIR 要根据实际 KEIL 工程配置修改。KEIL 里设置的输出目录在 Options for Target — Output 页面的 Select Folder for Objects。如果这里路径和脚本不一致,copy 命令会报“系统找不到指定的文件”,这一步是最常见的配置错误点。
命名格式里我加了 APP 标记,和项目正文里的 STM32F401CEU6_APP_V_1.0.3_251102-090039.bin 对齐。如果你只想保留 bin 不复制 axf,把第二个 copy 注释掉即可。
3.3 调用 RAR 命令行工具打包:路径、压缩级别和包含文件清单
压缩这一步取决于你机器上装了 WinRAR 还是 7-Zip。WinRAR 的命令行工具是 Rar.exe,7-Zip 是 7z.exe。两个都支持在 cmd 里带参数调用。Rar.exe 通常位于安装目录下,脚本里需要写全路径,否则 cmd 找不到命令。
推荐在脚本开头做一个变量定义,方便切换压缩工具:
set RAR_TOOL="C:\Program Files\WinRAR\Rar.exe" set ZIP_TOOL="C:\Program Files\7-Zip\7z.exe"然后调用的压缩命令为:
%RAR_TOOL% a -ep1 -m5 "%ARCHIVE_DIR%\%PROJ_NAME%_APP_%APP_VERSION%_%STAMP%.rar" "%ARCHIVE_DIR%\%BIN_NAME%" "%ARCHIVE_DIR%\%AXP_NAME%" ".\Src\*.c" ".\Inc\*.h"参数说明:a 表示添加文件到压缩包,-ep1 表示压缩包内不包含盘符和绝对路径,-m5 是压缩等级,数字越大压缩率越高但耗时越长。文件清单部分可以只压 bin 和 axf,也可以加入源文件和头文件,我习惯把 Src 和 Inc 目录里的 .c 和 .h 一起打进去,这样出问题的时候拿到压缩包就能直接回溯到源码。
如果用 7z 工具,命令几乎一样:
%ZIP_TOOL% a -t7z "%ARCHIVE_DIR%\%PROJ_NAME%_APP_%APP_VERSION%_%STAMP%.7z" "%ARCHIVE_DIR%\%BIN_NAME%" -mx=5区别是 7z 格式压缩比更高,但同事电脑上不一定有能解压 7z 的软件,所以团队协作时我更倾向发 rar 或 zip 格式。
3.4 用脚本参数接收工程名:一个脚本服务多个工程
同一个脚本如果只服务一个工程,那 PROJ_NAME 写死没问题。但很多开发者电脑上同时摊着好几个 KEIL 工程,每个工程的输出文件名不同,写死就意味着每个工程都要复制一份脚本。更好的做法是把工程名作为参数传入,KEIL 的 Run #1 里填:
cd /d "D:\work\STM32F401CEU6_UART_Ringbuffer" && call Auto_Generation_Name.bat STM32F401CEU6_UART_Ringbuffer然后在脚本开头用 %1 接收:
set PROJ_NAME=%1 if "%PROJ_NAME%"=="" ( echo 错误:未传入工程名参数 exit /b 1 )这样脚本里的 BIN_NAME 变量会拼出以传入参数开头的文件名。同一份脚本可以被多个 KEIL 工程通过 After Build 调用,只要把参数换成各自的工程名。脚本维护成本降到接近零。
4. 避坑与排查:脚本报错先看这四个环节
4.1 提示“系统找不到指定的文件”
现象:KEIL 编译正常完成,After Build 执行后弹出的命令行窗口显示找不到复制源文件。 原因:大多数情况是脚本里的 OUTPUT_DIR 和 KEIL 实际输出目录不一致。KEIL 的默认输出目录是工程目录下的 Objects 文件夹,但如果你在 Options for Target — Output 里手动改过 Select Folder for Objects,默认路径就不成立了。 解决:打开 KEIL 的 Options for Target,切到 Output 标签页,查看 Objects 输出路径,把脚本里的 OUTPUT_DIR 改成实际路径。如果路径带空格,记得用引号包住。另外确认 KEIL 勾选了 Create HEX File 或 Create Batch File,因为 bin 文件默认不生成,需要额外配置。
4.2 脚本运行后没生成任何文件,窗口一闪而过
现象:双击 bat 测试时 cmd 窗口瞬间关闭,看不到任何输出,也没有产物文件。 原因:脚本语法错误导致解析失败,或者变量拼接后命令行为空。最常见的是 for /f 里的特殊字符没转义,比如 wmic 行里的|管道符在 for 循环里需要写成^|,否则解析器会把整行拆断。 解决:在脚本第一行加 pause,这样即使出错窗口也不会立即关闭,能看到错误信息。定位到具体行以后检查特殊字符转义和括号闭合。延迟展开变量后引用路径时,如果路径里有!字符也会出问题,这是相对少见的边缘情况。
4.3 Rar.exe 命令执行不成功但没报明显错误
现象:复制文件成功后,压缩步骤没有生成 .rar 文件,命令行窗口输出停留在“正在创建归档”。 原因:Rar.exe 路径带空格时调用失败,或者压缩文件清单里的路径引用错误。另一个隐蔽原因是 WinRAR 安装后 Rar.exe 不一定在默认路径下,64 位系统里可能在 C:\Program Files\WinRAR\Rar.exe,32 位则在 C:\Program Files (x86)\WinRAR\Rar.exe。 解决:在脚本开头用 if exist 判断 Rar.exe 是否存在,不存在时输出提示并退到 7z 或 zip 工具。压缩清单里的每个文件都用绝对路径或脚本自身目录下的相对路径,不要写桌面那种跟工程无关的路径。
4.4 KEIL 提示 After Build 命令执行失败,exit code 非 0
现象:KEIL 的 Build Output 窗口显示 User Command #1 exited with code 1。 原因:这个报错等于说 bat 脚本执行过程中有一条命令返回了非零退出码。最常见的是 copy 命令找不到源文件,或者 rar 命令没有正确执行。它不一定是脚本最后一行导致的,cmd 里默认退出码是最后一条命令的结果。 解决:在脚本所有关键命令后面加判断,比如:
copy /y "%OUTPUT_DIR%\%PROJ_NAME%.bin" "%ARCHIVE_DIR%\%BIN_NAME%" if errorlevel 1 ( echo 复制 bin 文件失败,请检查源路径 exit /b 1 )这样如果失败,exit code 是 1 但你能在命令行窗口里看到具体是哪一步挂了,而不是一头雾水只看 KEIL 报错。
4.5 生成时间戳比本地时间慢了 8 小时或日期错位
现象:脚本跑完后的文件名日期和系统时间不一致。 原因:wmic 的 LocalDateTime 返回的是本地时间,但如果你在虚拟机里或者系统时区设置异常,或者用了 UTC 时钟而没同步,会导致时间偏差。另外如果系统日期格式包含农历或周几信息,取字符串切片会错位。 解决:在脚本开头先 echo %date% %time% 检查系统输出格式。确认系统时区设置正确后用 wmic 方式获取时间戳,它的输出是定长字符串,不管系统日期显示格式怎么变,切片位置都不会漂移。这是它相比 %date% 最大的优势。
5. 把这套流程再往前推一步:版本号自动递增和归档目录按日期分片
如果只是解决“打包命名”,前面四章已经够用。但实际用下来你会发现还有两个可以顺手优化的点。第一个是版本号能不能自动加一,省掉每次手动改脚本里的 APP_VERSION;第二个是归档文件多了之后,Release 目录会堆满,最好按日期建子目录。
版本号自动递增在纯 bat 里稍微绕一点,但可以用文本文件记录上次的版本号。做法是建一个 version.txt,内容是 1.0.3 之类的数字,脚本每次读取后加 0.0.1 再写回去。下面的代码实现了这个逻辑:
set /p VER=<version.txt for /f "tokens=1-3 delims=." %%a in ("%VER%") do ( set /a major=%%a set /a minor=%%b set /a patch=%%c+1 ) set NEW_VER=%major%.%minor%.%patch% echo %NEW_VER%>version.txt set APP_VERSION=V_%NEW_VER% echo 当前版本: %APP_VERSION%这段的逻辑是先把 version.txt 里的值按句点切分成三段,第三段加一,再拼回完整版本号并写回文件。注意 set /a 算数运算里 patch 加一后如果得到 10,会变成 1.0.10,这是正常递增逻辑,不会回退到 1.0.0。如果你想在主版本升级时重置后面的位数,需要额外加判断,但对日常迭代来说这个规则够用。
归档目录分片更简单,复制命令改成:
set DAY_DIR=%ARCHIVE_DIR%\%YEAR%-%MONTH%-%DAY% if not exist "%DAY_DIR%" mkdir "%DAY_DIR%" copy /y "%OUTPUT_DIR%\%PROJ_NAME%.bin" "%DAY_DIR%\%BIN_NAME%"这样每天的包都落在独立的日期文件夹里,找历史版本时不用翻一整屏文件。唯一的注意点是配合压缩命令时,RAR 命令行里归档目录也要改成 %DAY_DIR%,否则压缩包还是落在旧位置。我当时就是在压缩命令里没改路径,导致归档目录和压缩包所在地不一致,差点又把新版固件漏发。从那以后我每次改脚本路径类变量,都会强制走一遍“复制命令、压缩命令、最终输出位置”三处对照检查。
这套方案的本质是把“记忆工作”交给脚本,把“判断工作”留给人。版本号该升多少还是你说了算,但文件名和压缩包格式永远不会因为手误而出错。希望这套思路能帮你在固件发布流程里少踩几个坑,把省下来的时间用在真正需要动脑子的地方。
本文还有配套的精品资源,点击获取