CMake+Ninja+arm-none-eabi-gcc+OpenOCD+VScode+AI在Windows系统下代替keil进行stm32程序的编译和调试
1. 背景
- keil虽然具备完备的代码编辑、编译、调试功能,且很多开发资料都是以keil为基础建立的工程。但是keil是付费软件,在企业使用就会吃律师函(我们公司最近就是)。
- VScode界面美观,配合AI进行代码提示和自动补全,再加上git等多种扩展插件,简直不要太香。
2. 开发环境安装
- 进行嵌入式开发,总体上有三个步骤:编写(VScode+AI)、编译(CMake+Ninja+arm-none-eabi-gcc)、调试(OpenOCD+STlink/JLINK)
- 在keil环境下,我们只需编写好代码,然后一键编译即可。CMake+Ninja+arm-none-eabi-gccg各司其职,实现C语言代码编译为单片机的HEX文件。总体过程为:Cmake是抽象的方便人类理解的“编译过程管理指挥官”,可以类比为keil中“魔术棒”选项卡里的各种设置,实现.c文件的添加、.h路径的包含设置、优化等级、宏定义等。Ninja与make工具通过makefile工作一样,ninja通过build.ninja文件进行指定编译顺序及路径。程序员可以直接编写build.ninja文件,但目前一般是通过更上层的工具如Cmake自动生成该文件了。CMake定义\"做什么\"(编译选项、文件依赖),Ninja负责\"如何做\"(高效执行命令),真正“干活”实现编译链接的是arm-none-eabi-gcc
- arm-none-eabi-gcc:交叉编译器
作用:将C/C++源码编译为目标单片机(ARM架构)的机器码。
交叉编译:在x86 Windows主机上生成ARM架构的二进制文件(如.o目标文件)。
全套工具链:包含编译器(gcc)、汇编器(as)、链接器(ld)、二进制工具(objcopy, objdump)。
生成ELF文件:编译链接后输出.elf文件(包含机器码、调试信息、内存布局)。 - CMake:构建系统生成器
作用:管理构建流程(编译、链接、依赖),屏蔽平台差异。
跨平台配置:用统一的CMakeLists.txt文件定义项目结构、编译器选项、依赖关系。
生成构建脚本:根据平台生成底层构建文件(如为Ninja生成build.ninja)。
控制编译流程:指定交叉编译器、单片机参数(CPU型号、内存布局、启动文件等) - Ninja:构建引擎
作用:高效执行构建任务(调用编译器、链接器等)。
关键优势:
极速构建:比传统make更快(并行任务优化、低开销)。
最小化重建:精准追踪文件依赖,仅重新编译必要文件。
自动化驱动:由CMake生成的build.ninja脚本驱动,无需手动干预。
工作流程:
CMake生成build.ninja(包含编译命令、依赖关系)。
运行ninja命令,自动调用arm-none-eabi-gcc编译源码、链接ELF。
执行objcopy生成HEX(通过CMake定义的POST_BUILD规则) - 为什么需要三者配合?
arm-none-eabi-gcc必需:只有它理解ARM指令集,能生成单片机可执行的机器码。
CMake高效管理:简化多平台构建配置,避免手写复杂的Makefile/Ninja脚本。
Ninja加速构建:比传统Make更快,尤其适合大型项目频繁重建。
替代方案举例:
可用Makefile替代CMake+Ninja(但手写Makefile更复杂)。
可用IDE(如STM32CubeIDE) 内置工具链(底层仍是GCC+objcopy,但隐藏了配置细节)。
1.VScode安装
https://code.visualstudio.com/Download
在官网选择与自己电脑相适应的版本即可。
这里我选择了Windows系统下的64位版本。按照提示一步步安装即可。
这里建议全部勾选。
安装VScode扩展:
1.C/C++:用于C/C++ IntelliSense, debugging, and code browsing
2.CMake Language Support
3.CMake Tools
4.Cortex-Debug:ARM Cortex-M GDB Debugger support for VSCode
5.GitHub Copilot:AI代码提示和补全
6.文心快码 ( Baidu Comate ):AI代码提示和补全
2.编译工具链下载
- 方式1:https://www.st.com.cn/zh/development-tools/stm32cubeclt.html?icmp=tt38569_gl_lnkon_apr2024
ST官网上有集成好的工具链,下载安装好后其实就是一堆工具的集合,省的到处去找了
选择工具链要放置的路径
可以看到,基本上进行STM32开发的工具全在了。
将CMake、Ninja、arm-none-eabi-gcc工具的路径添加到系统环境变量 - 方式2:各个工具去官网下载,解压并将路径添加到系统环境变量中
CMake:https://cmake.org/download/
Ninja:https://github.com/ninja-build/ninja/releases
arm-none-eabi-gcc:https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
3. Openocd调试工具
OpenOCD(Open On-Chip Debugger)是一款功能强大的开源调试工具,专为嵌入式系统开发设计,支持多种处理器架构和调试接口。以下是其核心要点解析:
调试功能:支持设置断点、单步执行代码、查看寄存器/内存状态、监视变量变化,帮助开发者定位代码问题。
编程功能:可将编译后的固件(如HEX/BIN文件)烧录到目标设备的闪存(Flash)中,支持NOR/NAND Flash及外部存储器
边界扫描:用于硬件验证和电路板测试(如检测焊点故障)。
兼容 JTAG、SWD(Serial Wire Debug) 和 cJTAG 等调试协议。
适配多种调试器硬件,如ST-Link、J-Link、FTDI芯片(如FT2232H)、CMSIS-DAP等。
支持Windows、Linux、macOS系统,可与GDB(GNU Debugger)无缝集成,通过GDB服务器(默认端口3333)实现源码级调试。
提供Telnet接口(默认端口4444)执行底层命令(如内存读写、复位控制)。
下载:https://gnutoolchains.com/arm-eabi/openocd/
同样的Openocd工具的路径也需要添加到环境变量中,例如:F:\\OpenOCD-20250613-0.12.0\\bin
3. 实战使用案例
1. STM32CubeMx生成工程
工具链选择Cmake,自动生成初始化代码和Cmake工程
2. VScode配置
- Cmake配置
CubeMx自动生成的Cmake文件中预设了Debug和Release两种模式。CMake默认支持四种模式,四种编译模式是-O优化等级和-g调试信息的区别,用户可以根据自己需要进行选择和改写
根据实际需求,添加所需的.c和.h路径
CubeMx自动生成的Cmake文件不会生成常用的hex文件,这里需要我们手动添加
# 添加自定义命令生成 BIN/HEX/SREC 文件add_custom_target(generate_outputs ALL COMMENT \"Generating binary output files...\" COMMAND ${CMAKE_OBJCOPY} -O binary $ ${CMAKE_PROJECT_NAME}.bin COMMAND ${CMAKE_OBJCOPY} -O ihex $ ${CMAKE_PROJECT_NAME}.hex COMMAND ${CMAKE_OBJCOPY} -O srec $ ${CMAKE_PROJECT_NAME}.srec DEPENDS ${CMAKE_PROJECT_NAME})
修改好后,CMake Tools会自动的帮我们编译生成build.ninja文件,用于“指挥”arm-none-eabi-gcc进行代码编译
到目前为止,我们只是修改了CubeMx自动生产的工程里的Cmake文件,让其能够生成hex文件,可以尝试编译一下看看效果。
- C\\C++扩展配置
打开main.c,会发现一堆报错,这是因为VScode只是编辑器,并不知道各个.c,.h之间的关系,因此需要借助C\\C++扩展辅助进行代码的语法检查等
在文件夹内新建.vscode文件夹,并新建三个文件
c_cpp_properties.json的内容如下:核心其实是告诉C/C++扩展,该工程需要的头文件的路径,编译器的路径以及宏定义
{ \"configurations\": [ { \"name\": \"STM32\", \"includePath\": [ \"Core/Inc\", \"Drivers/STM32F1xx_HAL_Driver/Inc\", \"Drivers/STM32F1xx_HAL_Driver/Inc/Legacy\", \"Drivers/CMSIS/Device/ST/STM32F1xx/Include\", \"Drivers/CMSIS/Include\" ], \"defines\": [ \"UNICODE\", \"USE_HAL_DRIVER\", \"STM32F103xB\" ], \"windowsSdkVersion\": \"8.1\", \"compilerPath\": \"F:/STM32CubeCLT_1.18.0/GNU-tools-for-STM32/bin/arm-none-eabi-gcc.exe\", \"intelliSenseMode\": \"windows-gcc-arm\", \"cStandard\": \"c99\", \"cppStandard\": \"gnu++17\" } ], \"version\": 4}
- tasks.json的内容如下:主要定义了两个任务,一个编译,一个通过STlink借助Openocd下载程序
{ \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"Flash with OpenOCD\", \"type\": \"shell\", \"command\": \"openocd\", \"args\": [ \"-f\", \"openocd.cfg\", \"-c\", \"program build/Debug/${workspaceFolderBasename}.elf verify reset exit\" ], \"problemMatcher\": [] }, { \"type\": \"cmake\", \"label\": \"CMake: build\", \"command\": \"build\", \"targets\": [ \"all\" ], \"preset\": \"${command:cmake.activeBuildPresetName}\", \"group\": \"build\", \"problemMatcher\": [], \"detail\": \"CMake 模板 生成 任务\" } ]}
在工程根目录下,新建openocd.cfg文件,内容为
source [find interface/stlink.cfg]source [find target/stm32f1x.cfg]
用于指向Openocd文件夹中配置好的接口和目标单片机
这里连接好STlink,运行Flash with OpenOCD任务
运行结果如下,烧录成功。
- 在线调试
为实现该功能,launch.json内容如下:
{ \"version\": \"0.2.0\", \"configurations\": [ { \"name\": \"Cortex Debug (OpenOCD)\", \"cwd\": \"${workspaceRoot}\", \"executable\": \"build/Debug/${workspaceFolderBasename}.elf\", \"request\": \"launch\", \"type\": \"cortex-debug\", \"servertype\": \"openocd\", \"device\": \"STM32F103C8\", \"configFiles\": [\"openocd.cfg\"], \"preLaunchTask\": \"CMake: build\", \"runToEntryPoint\": \"main\", \"showDevDebugOutput\": \"raw\", \"liveWatch\": { \"enabled\": true, \"samplesPerSecond\": 4 } } ]}
该文件的目的是将指定的elf文件通过Openocd进行下载并在线调试,且在调试前会自动调用Cmake的build命令进行编译。使用快捷键F5,进入调试模式
至此实现了代码的编译、下载、调试功能
4. 注意事项
- 与keil设置堆栈不同的是,需要在ld文件中根据改写所需的堆栈空间和程序起始地址。且栈顶地址_estack永远为最大的RAM空间,并不会像keil一样根据实际的空间占用设定栈顶地址
想要像keil一样根据实际的空间占用设定栈顶地址,只需要添加这么一句话即可 - 启动文件
keil的启动文件在进入main函数之前,会自动编译一些初始化的操作,使用gcc编译器需要显式写出。
在具有BootLoader的应用中,需要在BootLoader中__set_MSP,重新初始化堆栈。如果BootLoader没有重新初始化堆栈,则需要在APP代码的启动文件的Reset_Handler,显式的添加栈顶指针的赋值
5. 使用建议
VScode配合AI代码扩展可以极大的提高编码效率,内置的Git可以方便的进行代码版本的管理