CMake默认使用本地gcc,必须通过工具链文件(-DCMAKE_TOOLCHAIN_FILE)显式指定arm-none-eabi-gcc,设置CMAKE_SYSTEM_NAME、CMAKE_SYSTEM_PROCESSOR和CMAKE_C_COMPILER,否则将静默生成x86二进制或报错。

怎么让 CMake 找到 arm-none-eabi-gcc 而不是本地 gcc
CMake 默认用 gcc,不配交叉编译工具链就直接失败或静默编译出 x86 二进制。必须显式指定工具链文件(Toolchain File),不能靠 CC=arm-none-eabi-gcc cmake ... 这种环境变量方式——CMake 会忽略它,或者只影响部分步骤,导致链接器仍用本地 ld。
实操建议:
- 新建一个
arm-none-eabi-toolchain.cmake文件,内容至少包含三行:set(CMAKE_SYSTEM_NAME Generic)<br>set(CMAKE_SYSTEM_PROCESSOR arm)<br>set(CMAKE_C_COMPILER arm-none-eabi-gcc)
- 调用时必须加
-DCMAKE_TOOLCHAIN_FILE=arm-none-eabi-toolchain.cmake,且不能省略-G "Unix Makefiles"(Ninja 可选,但别用 VS 或 Xcode Generator) - 如果
arm-none-eabi-gcc不在$PATH,在 toolchain 文件里用绝对路径写死,比如set(CMAKE_C_COMPILER "/opt/gcc-arm-none-eabi/bin/arm-none-eabi-gcc")
怎么生成 .bin 或 .hex 而不是默认的 ELF
CMake 本身不生成 .bin 或 .hex,这些是链接后用 arm-none-eabi-objcopy 从 ELF 提取的。关键在于:不能靠 add_custom_target 硬塞命令,得绑定到构建目标生命周期上,否则 IDE(如 CLion)或 make install 会漏掉。
实操建议:
- 先用
add_executable(firmware main.c)生成 ELF,再用add_custom_command定义转换逻辑,并通过add_custom_target把它挂载为依赖:
add_executable(firmware main.c) target_link_libraries(firmware PRIVATE cmsis_device) # 假设链接脚本叫 stm32f407vg.ld target_link_options(firmware PRIVATE "-Tstm32f407vg.ld" "-Wl,-Map=firmware.map") <p>add_custom_command( OUTPUT firmware.bin COMMAND arm-none-eabi-objcopy -O binary firmware firmware.bin DEPENDS firmware ) add_custom_target(bin ALL DEPENDS firmware.bin)</p>
这样执行 make bin 或 cmake --build . --target bin 就能拿到 .bin;.hex 同理,把 -O binary 换成 -O ihex 即可。
为什么编译后 flash 地址错乱或启动失败
常见现象:烧录后单片机不运行、HardFault、向量表读取异常。根本原因不是 CMake 配置错,而是没正确传递内存布局和入口点给链接器。
实操建议:
- 必须提供链接脚本(.ld 文件),并在
target_link_options中显式传入-T,不能只靠set(CMAKE_EXE_LINKER_FLAGS ...)——后者容易被覆盖 - 确保链接脚本里有
ENTRY(Reset_Handler),且你的启动文件(startup_*.s)中定义了该符号;CMake 不会自动识别汇编入口,得手动target_sources(firmware PRIVATE startup_stm32f407vg.s) - 检查是否禁用了默认启动代码:
set(CMAKE_CXX_STANDARD_REQUIRED ON)之类可能意外启用 C++ runtime,导致_start冲突;嵌入式项目建议加set(CMAKE_CXX_STANDARD 11)但禁用异常和 RTTI:target_compile_options(firmware PRIVATE -fno-exceptions -fno-rtti)
怎么避免调试信息污染固件体积
默认 Debug 模式下,arm-none-eabi-gcc 会把 DWARF 信息打进 ELF,虽然不影响运行,但 objcopy -O binary 会照单全收——导致 .bin 里混进大量无效字节,实际 Flash 大小远超预期。
实操建议:
- 构建时用
-DCMAKE_BUILD_TYPE=Release,并显式关掉调试符号:target_compile_options(firmware PRIVATE -g0) - 更稳妥的做法是在
objcopy步骤加--strip-all或--strip-unneeded,例如:arm-none-eabi-objcopy -O binary --strip-unneeded firmware firmware.bin - 如果用 OpenOCD 烧录,它支持直接加载 ELF;此时保留调试信息反而方便,但最终量产固件务必走 stripped 的
.bin流程
交叉编译链路里最易被忽略的其实是链接脚本与启动文件的耦合细节——工具链能跑通不代表硬件能跑通,每个芯片型号的 Reset_Handler 符号名、向量表偏移、堆栈段定义都得对得上,CMake 只负责把它们串起来,不替你校验语义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











