ChipCode 接入 STM32Cube:从芯片包到烧录的完整链路
STM32 的工程链路比 RISC-V、ESP 那些都长。
一条完整的路是这样的:.ioc 配置 → CubeMX 生成代码 → CMake 或 Makefile → arm-none-eabi-gcc 编译出 .elf → 转 .hex/.bin → OpenOCD 或 ST-LINK 烧录 → 串口确认。
中间任何一环断了,表现都是“烧进去没反应”。这篇把每一环上我们踩过的东西写下来。
第一环:芯片包在哪
STM32CubeMX 生成工程时会引用一个芯片包目录,通常在用户目录下的 STM32Cube/Repository。
问题是这个路径不固定。Windows 上可能在 C:\\Users\\<name>\\STM32Cube\\Repository,也可能被用户改到 D 盘;Linux 上在 ~/.stm32cubemx 或环境变量 STM32CUBEMX_REPOSITORY 指的位置;还有人是直接装了 STM32CubeIDE,包跟着 IDE 走。
我们的做法是不猜。安装插件时让用户指一次,把路径写进插件配置;之后每次构建前校验这个路径下有没有对应的器件包(STM32Cube_FW_F1_V1.8.x 这种目录名)。
没有就报一个明确的错:缺哪个系列、哪个版本、去哪儿装。而不是让 gcc 在编译时报 cannot find -lSTM32...。
第二环:路径里的空格
这个问题很蠢,但它每个月都会有人撞上一次。
用户的用户名可能带空格,项目路径可能在“我的文档”下面,Windows 的 Program Files 更不用说。
Makefile 里如果写成:
CFLAGS += -I$(CUBE_DIR)/Drivers/CMSIS/Include
$(CUBE_DIR) 里有空格,这一行就断了。
正确写法是加引号,但 Make 对引号的处理和 shell 不一样,光加引号还不够。我们的处理方式是在生成命令参数时按数组传参,不拼字符串——也就是 spawn(cmd, ["-I", includePath]),让操作系统去处理转义。
这条听起来是常识,但只要你有一处是拼字符串拼出来的,就会在某个用户的机器上炸掉。
第三环:构建系统有两种
CubeMX 可以生成 Makefile,也可以生成 CMake 工程(新版本默认偏向 CMake)。
我们两个都支持,但走不同的路径:
- Makefile:
make -j直接构建 - CMake:先确认有没有
build/目录,没有就cmake -B build -G "Unix Makefiles",然后cmake --build build -j
有个坑:CMake 生成的工程里,工具链文件(gcc-arm-none-eabi.cmake)如果路径写死成 CubeMX 所在机器的绝对路径,换机器就构建不了。
我们检测到这种情况会给一条提示,让用户在项目里重新生成一次。检测方法很土:读 CMakeCache.txt 里的 CMAKE_TOOLCHAIN_FILE,看那个路径存不存在。
第四环:产物要验,不能只信退出码
make 返回 0 不代表产物是新的。
我们遇到过这种情况:源文件改了,但因为时间戳问题(跨时区、或者是从压缩包里解出来的),make 认为目标文件是最新的,什么都没编,直接返回 0。
所以构建之后我们做一次产物检查:.elf 的修改时间必须晚于本次构建开始时间;文件大小不能是 0;如果能解析 ELF 头,还要确认机器类型是 ARM。
这一步拦下过两次“改了代码但烧进去还是旧固件”的事故。当事人当时的判断是“代码没生效”,实际是根本没编。
第五环:烧录
STM32 的烧录器有三种常见情况:ST-LINK(SWD)、J-Link、串口 ISP(只有部分型号支持)。
我们默认走 OpenOCD,因为它跨平台、开源、命令行友好:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "program build/firmware.elf verify reset exit"
target/ 那个文件名跟芯片系列绑定:stm32f1x.cfg、stm32f4x.cfg、stm32g0x.cfg……写错了会报 “unknown device”。
我们的做法是把芯片型号到 cfg 的映射做成一张表,跟着插件走。加新型号就是加一行,不用改代码。
烧录成功的判定不看 OpenOCD 输出的 “ Verified OK ” 这行字符串——虽然它确实会打。我们看的是退出码,加上对输出里 Error 关键字的扫描。两者都要满足。
理由:OpenOCD 有几种情况会打印错误但仍然返回 0(比如 verify 失败但 program 成功)。只看退出码会漏,只看字符串会误判。
第六环:串口确认
最后一步,也是最容易被跳过的一步。
烧录成功只说明“写进去了”,不说明“跑起来了”。
所以联调阶段默认会开一次串口,等一小段时间,看有没有任何字节输出。没有输出不算失败——有些固件本来就不打印——但我们会把“无输出”作为一条 inconclusive 记进证据里。
这个区分很重要:fail 会让人去修,inconclusive 会让人去看。
一条链的价值在于每一环都可查
上面这些单拎出来都不复杂,难的是它们串起来之后,出问题时你不知道断在哪。
ChipCode 在这里做的事情很朴素:每一环结束都留一条记录——用的哪个芯片包、构建命令是什么、产物多大、什么时候写的盘、烧录用了哪个 cfg、串口收到几个字节。
下次再遇到“烧进去没反应”,第一件事不是猜,是翻记录看断在第几环。
这套东西不聪明,但它省掉的是那种“折腾一晚上最后发现是路径带空格”的时间。