Keil 增量编译的坑:烧录的固件比代码老了一版,排查一下午
一句话: Keil 增量编译下,源码改了但没触发重编译时,hex 文件还是老版本的——直接把旧 hex 复制去烧录,芯片里跑的就是旧代码。烧完功能不对,先查 hex 时间戳,别一头扎进代码里。
适合谁读:用 Keil 命令行
UV4 -b编译 + J-Link 脚本烧录,烧完功能不对第一反应是查代码的嵌入式工程师。
现象:烧录很顺利,功能却"丢"了
一次固件改动,改完代码、Keil 命令行编译、日志 0 Error 0 Warning,复制 hex 烧录,输出Downloading file+Program & Verify全都有——烧录过程完美。
然后测功能,坏事了:
- 新加的逻辑(cmd 6 未使能推 0)完全不存在
- 另一条命令(cmd 12 带地址读)发出去无响应
于是开始查代码:逻辑明明写了啊?编译也没报错啊?是不是哪里没使能?是不是初始化顺序问题?排查了一下午,一无所获。
最后对了一下 hex 文件时间戳,真相大白:烧录的 hex 是上一次编译的旧产物,这次的源码改动根本没编译进去。
根因:Keil 增量编译不重编译 = hex 不更新
Keil 增量编译(Incremental Build)的机制:
- 源码没变→ 跳过该文件编译 → 链接产物不变 →hex 时间戳不更新
- 源码变了→ 重编译该文件 → hex 更新
看起来没毛病。但命令行批量编译时有个致命场景:你改的源码和 hex 的时间戳只差几分钟,甚至相同——因为你上一轮编译过,这一轮 Keil 判断"没有变化"直接跳过了,但你自己不知道,还当它编译过了。
更隐蔽的是:上一轮编译是另一份代码(比如调试中途改坏又改回),或者 hex 是从别的目录复制来的。你手上这个 hex,可能比源码老了好几版。
关键认知:"编译日志 0 Error" ≠ "hex 包含最新代码"。增量编译下,日志只能证明"编译没报错",不能证明"编译发生过了"。
对策:烧录前三步走,一步都不能省
从此烧录流程固定为三步,中间不插任何其他操作:
① UV4 -b 重新编译 ② 对比 hex 时间戳 vs 最近源码修改时间(不一致就停) ③ 复制 hex 到烧录目录 → J-Link 烧录第 ② 步是核心,命令行一条命令就能比:
# 烧录前:hex 时间必须 >= 最近源码修改时间 test "$(stat -c %Y hex/firmware.hex)" -ge "$(find src -name '*.c' -o -name '*.h' | xargs stat -c %Y | sort -n | tail -1)" \ || echo "警告:hex 比源码旧,禁止烧录!"没有比对工具也可以人肉看:编译输出到烧录复制之间,看一眼 hex 文件的修改时间。它是"几分钟前"还是"昨天"?一目了然。
还有个铁律:编译和复制之间不插别的操作。先编译 → 立刻复制 → 烧录,一气呵成。中间哪怕去改了个宏、加了行注释,hex 都可能是旧的。
事后复盘:为什么排查了一下午?
回过头看,排查之所以浪费时间,是因为默认假设错了:
- 默认"烧的就是最新代码"—— 编译日志没问题,烧录输出没问题,自然怀疑代码逻辑。但这个假设恰恰不成立:增量编译 + 手动复制,中间任何一步都可能拿到旧产物。
- 没先做"版本自证"—— 设备有读版本号/读状态命令的话,烧完先读一发,功能对不对一目了然。我们这次设备有 cmd 19 读版本,但版本号没改,读出来一样,也没想到去对 hex 文件。
正确顺序应该是:功能不对 → 先自证烧的版本 → 再查代码。版本对了,才能信任"问题在代码里"。
总结
| 环节 | 陷阱 | 对策 |
|---|---|---|
| 增量编译 | 源码没变就不重编,hex 是旧的 | 烧录前对比 hex 与源码时间戳 |
| 手动复制 | 复制了上次的旧 hex | 编译 → 复制 → 烧录,中间不插操作 |
| 烧后测试 | 功能不对就查代码 | 先自证烧录版本,再查代码 |
"编译通过"只证明编译成功,"时间戳对得上"才证明烧的是最新代码。烧录前 30 秒的比对,省下来的是几个小时的无头排查。
实测对比:改完源码直接复制hex烧录:新功能全失效,排查一下午 | 编译后先比对hex时间戳:一次看穿是旧产物
有用的话点个收藏,下次命令行编译烧录前,先对一眼 hex 时间戳。有问题欢迎评论区交流,看到了都会回。