尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CubeMX 工程迁移到 VSCode:基于 CMake 的嵌入式开发完整移植指南

CubeMX 工程迁移到 VSCode:基于 CMake 的嵌入式开发完整移植指南 CubeMX 工程迁移到 VSCode 工作流的完整移植方案做 STM32 开发的朋友应该都有体会CubeMX 负责初始化配置生成外设代码这一步确实省心。可真正到了写业务逻辑的时候IDE 的选择直接影响每天的工作效率。我见过不少团队一直用 Keil 或者 STM32CubeIDE 写代码但也有人早就把日常开发迁移到了 VSCode利用它的插件生态和轻量特性把 CubeMX 生成的工程“移植”进 VSCode 环境里完成编译、烧录和调试。这里说的“porting plan”并不是让你去写一个 VSCode 插件而是规划一套完整的工作流CubeMX 继续做图形化初始化和代码生成VSCode 负责后续的代码编辑、编译、烧录和调试。这套方案不需要等官方出集成插件用现成的工具链组合就能落地而且实测下来非常稳。这篇文章我会从整体思路、环境准备、CubeMX 配置、VSCode 工程配置、调试器接入一直到常见问题排查完整走一遍移植流程。适合已经在用 CubeMX 但想摆脱 IDE 绑定、或者团队想统一到 VSCode CMake 工作流的嵌入式开发者参考。1. 整体设计思路为什么要做这次“移植”动手之前先想清楚一个问题CubeMX 工程迁移到 VSCode到底迁移的是什么CubeMX 生成的东西核心其实有两部分。第一部分是图形化配置文件xxx.ioc它记录了你在 CubeMX 里做的所有引脚分配、时钟树、外设参数设置这相当于项目的“源图”后续任何外设调整都靠它重新生成代码。第二部分是代码工程包括Core/Src、Core/Inc、Drivers这些目录以及一套带 IDE 工程文件的编译体系。以前 CubeMX 生成的工程文件默认绑定的是你选择的工具链MDK 生成.uvprojxIAR 生成.ewpSTM32CubeIDE 生成.project和.cproject。这些工程文件最大的问题是“锁死”在特定 IDE 里换了环境就得整份工程重来。而 VSCode 根本不识别这些工程文件它需要的是 CMake 或者 Makefile 这类通用的构建系统再配合编译器直接编译源码。所以这次移植的核心目标可以拆成四条保留.ioc文件维持 CubeMX 的图形化配置能力初始化代码改动仍然通过 CubeMX 重建。把构建系统从 IDE 私有格式切换到 CMake让 VSCode 的 CMake Tools 插件能直接驱动编译。接上交叉编译器让 PC 上编译出的二进制是 ARM Cortex-M 能跑的东西。接入调试器在 VSCode 里直接完成烧录和断点调试不停留在“只写代码”的层面。我把这个方案称为“CubeMX 负责初始化VSCode 负责工程化”。它不像 STM32CubeIDE 那样把所有功能打包在一起而是各取所长CubeMX 的图形化配置是最顺手的VSCode 的编辑体验、Git 集成和插件生态也是最顺手的把两者用 CMake 粘起来就是一条非常清晰的工作流。选型上还有一个关键决策为什么选 CMake 而不是直接用 Makefile早期有人用 CubeMX 生成 Makefile 工程再用 VSCode 的 Makefile Tools 插件编译也能跑通。但 CMake 的好处在于它和 VSCode 的 CMake Tools 插件配合最紧密——你只需要选择合适的 Kit工具链点一下 Build 按钮就能编译还能自动解析编译参数给 IntelliSense 用省去大量手工配置c_cpp_properties.json的功夫。而 CubeMX 从某个版本开始也直接支持生成 CMake 工程选择它作为中间层是阻力最小的路径。2. 环境准备工具链选型与安装要跑通这套工作流电脑上需要准备四个核心组件缺一不可。我按依赖顺序列出来每一步都用最简单的方式完成安装。2.1 交叉编译器arm-none-eabi-gcc这是整个工具链的“发动机”。CubeMX 生成的 C 代码要变成 STM32 能跑的二进制必须用 ARM 交叉编译器而不是 PC 上自带的 GCC。Windows 用户建议直接到 ARM 官网下载gcc-arm-none-eabi的 Windows 安装包安装时勾选“Add path to environment variable”装完在命令行执行arm-none-eabi-gcc --version能输出版本号就说明 OK。Linux 用户更简单Ubuntu/Debian 系直接sudo apt install gcc-arm-none-eabi不过要注意版本有些发行版的仓库版本比较老建议用 ARM 官方提供的tar.bz2包解压后加入 PATH。这里有个细节部分新版工具链比如 10.3 之后默认的浮点 ABI 是hard如果你用的 MCU 不带 FPU编译时链接可能报错。不过 CubeMX 生成的 CMake 工程里已经写好了-mcpu和-mfloat-abi参数直接用即可不需要自己手工干预。2.2 构建系统CMake 和 NinjaCMake 是构建系统的生成器Ninja 是实际执行编译任务的构建工具。二者配合比 Makefile 快不少尤其是增量编译时效果明显。Windows 上我推荐先装 Visual Studio Build Tools不需要装完整 VS或者直接下载 CMake 官方安装包安装时选“Add CMake to system PATH”。Ninja 就更轻量了单个 exe把它丢到任意 PATH 目录就行。装完之后验证命令行执行cmake --version和ninja --version能正常输出版本号即可。这一步建议顺手做一下很多后面的奇怪报错都是环境变量没配好导致的提前排除能省很多时间。2.3 调试器相关OpenOCD 与 ST-Link 驱动OpenOCD 是开源的片上调试器它负责把 PC 端的 GDB 命令转换成 SWD/JTAG 协议和 STM32 的调试接口通信。VSCode 的 Cortex-Debug 插件就是通过它来和开发板交互的。Windows 可以直接下载 xpack 发布的 prebuilt OpenOCD 包解压后把bin目录加进 PATH。还需要安装 ST-Link 驱动ST 官网有STSW-LINK009驱动安装包装完后插入 ST-Link设备管理器里能看到STMicroelectronics STLink dongle或者类似设备就说明驱动正常。Linux 下通常不需要额外驱动但需要注意权限问题OpenOCD 访问 USB 设备时如果当前用户不在plugdev组里可能报libusb权限错误。解决办法是把用户加入plugdev组或者写一条 udev 规则。2.4 VSCode 插件清单工具链装完轮到 VSCode 这边的插件。这套工作流中真正必需的插件只有三个其他都是锦上添花插件作用是否必需C/CMicrosoft提供 IntelliSense、语法高亮、调试支持必需CMake ToolsMicrosoft加载 CMake 工程选择工具链执行构建必需Cortex-Debug针对 ARM Cortex-M 的调试插件驱动 OpenOCD GDB必需串口监视器Serial Monitor实时查看串口输出建议GitLens增强 Git 集成可选有人问为什么不用 STM32 VS Code ExtensionST 官方出的那个。它确实做了很多集成但当前版本的成熟度参差不齐部分功能需要配合 STM32CubeCLI 使用而且配置链路更长。相比之下直接用 CMake Tools Cortex-Debug 这套经典组合反而更透明、更容易排查问题遇到报错也知道去哪个环节修。3. CubeMX 侧配置与工程生成环境准备好之后进入正题怎么让 CubeMX 输出一个 VSCode 能直接使用的工程结构。3.1 关键设置把 Toolchain 选成 CMake打开 CubeMX载入你现有的.ioc工程或者新建一个 STM32 工程。在Project Manager - Project选项卡下有一个Toolchain/IDE下拉框这里列出了 MDK-ARM、IAR、STM32CubeIDE、Makefile 等选项。我们要选的是CMake。有人会疑惑这里选了 CMakeCubeMX 不生成 Keil 工程了那以后还能不能用 Keil 打开答案是 CubeMX 的.ioc文件才是真正的“源”只要不改.ioc随时可以再选 MDK 重新生成一份 Keil 工程。工程文件本来就是可再生的不需要担心选错。还有个设置也在同一个页面Linker Settings - Minimum Heap Size和Minimum Stack Size。如果项目用到了printf重定向到串口Heap 建议给到0x4001KB以上否则运行时malloc容易失败。这个和 VSCode 移植本身关系不大但既然进到 Project Manager 页面了顺手检查一遍没有坏处。3.2 生成后的工程结构选好 CMake 点击 GenerateCode 生成出来的目录结构会是这样project_name/ ├── .ioc ├── CMakeLists.txt ├── Core/ │ ├── Inc/ │ └── Src/ ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── startup_stm32f103xb.s ├── STM32F103RBTx_FLASH.ld └── cmake/ └── gcc-arm-none-eabi.cmake重点看几个文件CMakeLists.txt整个构建的核心包含了源文件列表、头文件路径、编译选项、链接脚本路径。CubeMX 每次重新生成代码时这个文件会被同步更新。cmake/gcc-arm-none-eabi.cmake工具链配置文件里面定义了编译器路径和一系列编译参数。CMake Tools 加载工程时会自动读取它不需要手工改。.ld链接脚本定义了 Flash/RAM 的地址和大小链接时用到。只要不在 CubeMX 里改存储区配置这个文件不需要动。还有一个容易忽略的点如果你在项目里新增了自己的.c文件CMakeLists.txt 不会自动感知。CubeMX 重新生成的 CMakeLists 只包含它自己创建的源文件你手动加的文件要么在 CubeMX 里通过Add按钮加进去要么改 CMakeLists.txt 的target_sources。这也是不少新手第一次用这套工作流时会卡住的地方后面排查章节会详细说。4. VSCode 侧的工程配置与编译CubeMX 生成完工程把整个文件夹用 VSCode 打开。这一步开始所有操作都在 VSCode 里完成。4.1 CMake Tools 加载工程打开文件夹后CMake Tools 插件会自动检测根目录的CMakeLists.txt。如果没有自动加载可以点击 VSCode 底部的 CMake 图标或者执行CMake: Scan for Kits先扫描系统里可用的工具链。这里有一个必须注意的坑CMake Tools 默认扫描到的是 PC 上自带的 GCC比如 MinGW 或者 Visual Studio 的 cl.exe而不是 ARM 交叉编译器。如果直接点 Build会报一堆莫名其妙的头文件错误什么stm32f1xx_hal.h: No such file or directory原因就是它用 x86 的编译器去编译 ARM 代码头文件路径和 CPU 指令集全都不对。解决办法是执行CMake: Select a Kit在弹出的列表里找到类似GCC for arm-none-eabi的选项。如果找不到点“Scan for kits”强制扫描或者直接在cmake-tools-kits.json文件里手动添加一条{ name: GCC arm-none-eabi, compilers: { C: C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin/arm-none-eabi-gcc.exe, CXX: C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin/arm-none-eabi-g.exe } }路径以你本机的实际安装位置为准。选定 Kit 之后再点 BuildCMake Tools 会识别到cmake/gcc-arm-none-eabi.cmake这个工具链文件自动传入正确的-mcpucortex-m3 -mthumb等参数。4.2 编译参数与 IntelliSense 的关系如果你用的是 VSCode 自带的 C/C 插件它默认的智能提示解析引擎和 CMake 是独立的。也就是说即使编译能通过VSCode 的代码提示可能仍然标红提示找不到 HAL 库头文件。这个问题的常规解法是让 CMake Tools 生成 compile_commands.json。在CMakeLists.txt里加上一行set(CMAKE_EXPORT_COMPILE_COMMANDS ON)然后在c_cpp_properties.json里指定compileCommands{ configurations: [ { name: STM32, compileCommands: ${workspaceFolder}/build/compile_commands.json, intelliSenseMode: linux-gcc-arm } ], version: 4 }这样 C/C 插件会从编译命令里自动提取头文件路径和宏定义不再需要手工维护includePath代码提示会非常准确。如果你不想用 compile_commands.json也可以手工配置includePath和defines需要把Core/Inc、Drivers/STM32F1xx_HAL_Driver/Inc、Drivers/CMSIS/Device/ST/STM32F1xx/Include等目录加进去。但这种方式维护成本高每次 CubeMX 重新生成代码后如果路径有变化就得手工同步不推荐。4.3 构建产物在哪里全部配置好之后点击 VSCode 底部的Build按钮或者执行CMake: Build。第一次编译会比较慢因为要编译全部 HAL 库源文件我测试的小工程大概需要 40 到 60 秒。编译完成后产物在build目录下.elf带调试信息和符号表调试时用这个。.hexIntel HEX 格式烧录工具用。.bin纯二进制看情况用。生成路径在 CMakeLists.txt 里有set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ...)之类的设置默认就是build目录。如果找不到看 CMakeTools 的输出面板里面有详细的构建日志。5. 烧录与调试VSCode 里直接跑单片机的最后一步编译能过只完成了工程化的一半真正舒服的是在 VSCode 里按 F5 直接下载固件并打断点调试。这一步要让 Cortex-Debug 插件工作起来。5.1 配置 OpenOCDCortex-Debug 调用 OpenOCD 需要知道两件事目标芯片是什么、调试器是什么。在项目根目录建一个.vscode/launch.json写入下面这段基本配置{ version: 0.2.0, configurations: [ { name: STM32 Debug, cwd: ${workspaceFolder}, executable: ${workspaceFolder}/build/stm32_project.elf, request: launch, type: cortex-debug, servertype: openocd, device: stm32f103rb, configFiles: [ interface/stlink.cfg, target/stm32f1x.cfg ], svdFile: ${workspaceFolder}/STM32F103xx.svd, runToEntryPoint: main, gdbPath: arm-none-eabi-gdb, openocdPath: openocd } ] }逐项解释一下executable指向你编译生成的.elf文件路径按实际工程名改。servertype固定为openocd。configFiles是 OpenOCD 的配置文件interface/stlink.cfg告诉它用的是 ST-Linktarget/stm32f1x.cfg告诉它目标芯片是 STM32F1 系列。不同芯片对应的目标配置文件不一样比如 STM32F4 用stm32f4x.cfgF0 用stm32f0x.cfg。svdFile是可选但强烈推荐的项目。SVD 文件里定义了芯片所有外设寄存器的名字和位域加载之后调试时鼠标悬停在寄存器上就能看到具体含义不用再去翻参考手册。CubeMX 生成的工程目录里通常没有 SVD 文件需要从 ST 官网或者芯片厂商的 SDK 里找。5.2 把烧录固化到任务里VSCode 调试附带了一个“下载并运行”的能力——F5 启动调试后OpenOCD 会把固件写进 Flash 然后停在main。如果你只是想快速烧录、不调试可以额外配一个tasks.json任务{ version: 2.0.0, tasks: [ { label: flash, type: shell, command: openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c \program ${workspaceFolder}/build/stm32_project.elf verify reset exit\, problemMatcher: [] } ] }把启动调试和烧录分开调试用 F5烧录用 CtrlShiftB 跑任务这两种动作就不互相干扰了。实测下来OpenOCD 通过 ST-Link 烧录 STM32F103 的 64KB Flash大约 4 到 5 秒完成体验其实比不少 IDE 内置的烧录器还快。5.3 外设寄存器窗口调试时如果想看外设寄存器的实时值比如想确认定时器CNT计数到多少了Cortex-Debug 的变量区里切到“Peripherals”标签页它会根据 SVD 文件列出所有外设。点开TIM2下面就是CR1、SR、CNT、ARR这些寄存器的实时值还能看到位域解析后的结果。不过要注意一点Cortex-Debug 的外设视图刷新需要暂停在断点上全速运行时它不会主动去刷新寄存器内容。想看实时值得先打个断点再说。6. 常见问题与排查技巧实录这套工作流跑久了多多少少会遇到一些问题我把踩过的坑和排查思路整理成一个速查表希望对你有帮助。问题现象可能原因排查方法编译报找不到stm32f1xx_hal.hCMake Tools 选择了错误的 Kit用了 x86 GCC重新执行CMake: Select a Kit选 arm-none-eabi代码提示标红但编译能过IntelliSense 没有使用 compile_commands.json在 CMakeLists 里开启CMAKE_EXPORT_COMPILE_COMMANDS并在 c_cpp_properties 里指定路径自己新增的.c文件编译时没被编译CMakeLists.txt 里的target_sources没更新在 CubeMX 里添加源文件后重新生成或手工编辑 CMakeLists.txt 追加源文件路径F5 调试报Error: open failedOpenOCD 无法访问 ST-Link驱动没装好或者被其他程序占用检查设备管理器确认 ST-Link 驱动正常确认没有其他 IDE 占用调试器调试时 Flash 烧录成功但无法运行芯片读保护RDP开启用 ST-Link Utility 或 OpenOCD 命令解除读保护CMake 缓存指定了错误的编译器之前用别的工具链配置过 build 目录删除 build 目录重新 Build链接报cannot find -larm_cortexM3l_math之类DSP 库路径没配对或者芯片不支持检查 CubeMX 里是否勾选了 DSP 库确认链接参数中的库路径和工具链目录匹配6.1 手动添加源文件的最佳实践前面提过CubeMX 重新生成代码时不会把你手动添加的.c文件加进 CMakeLists.txt。这个问题我后来找到了一个比较优雅的解法不要直接改 CMakeLists.txt而是在工程根目录建一个user_sources.cmake文件把所有用户自己的源文件和头文件路径集中放进去然后在 CMakeLists.txt 末尾加一句include(user_sources.cmake)。这样做的好处是CubeMX 每次重新生成代码都会覆盖 CMakeLists.txt但不会碰你的user_sources.cmake。只要记住这个文件是自己的“专属地盘”就不会出现重新生成后代码丢失的情况。# user_sources.cmake target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/User/App/app_main.c ${CMAKE_CURRENT_SOURCE_DIR}/User/App/hmi.c ) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/User/App )测试下来重新生成代码五次、八次自定义源文件都完好保留基本可以放心用。6.2 一个困了我两天的配置问题CMake 缓存残留有一次我从一个 MDK 转 CMake 的工程切换到另一个工程CMake Tools 一直报头文件找不到但命令行用cmake ..手动构建又能成功。排查了很久最后发现问题是两个工程共用了同一个build目录CMake 缓存里残留了上一个工程的编译参数。解决方式很简单删掉build目录让 CMake Tools 重新 configure。CMake 的缓存机制虽然省时间但在切换工程或者改动工具链的时候是最大的坑。遇到任何奇怪的编译问题先删build目录再试这个动作应该养成肌肉记忆。6.3 openocd 报告target not halted的修复有时候调试器能连上但烧录时报target not halted。大多数情况是目标芯片进入了低功耗模式或者调试时钟异常。先让开发板断电再重新上电然后快点 F5成功率能提高不少。如果还不行按复位键的同时执行烧录命令利用“连接时复位”的手段强制把内核拉回 halt 状态。这个方法其实不算正规解法但实测对不少 STM32 芯片都有效尤其是刚从 low-power stop 模式唤醒不来的情况。正规一点的方案是在 OpenOCD 配置里加reset_config srst_only或者adapter_nsrst_delay 100之类的参数来调节复位时序但新手阶段先用断电复位大法简单粗暴。6.4 SVD 文件去哪找一部分人在配置svdFile时会卡住因为 CubeMX 生成器不直接输出 SVD 文件。最快的获取方式是到芯片厂商官网的“工具与软件”页面搜索“SVD”下载对应系列的 SVD 压缩包。如果你想用 GitHub也可以搜一下“cmsis-svd”社区维护的仓库覆盖了绝大多数 STM32 系列。加载 SVD 之后调试体验提升不小。原来调试看定时器寄存器都是自己算地址对应关系现在直接把ARR、PSC这些字段显示出来谁用谁知道。7. 写在最后的几点体会这套 CubeMX VSCode 的工作流我前后用了有两年多从一开始的频繁踩坑到现在基本零障碍中间踩过不少弯路。我的核心心得就是不要试图让 CubeMX 变成 VSCode 的插件而是让 CMake 成为两者之间的翻译官。CubeMX 老老实实负责外设初始化代码的生成VSCode 负责更舒服的代码编辑和调试体验各司其职问题就变得简单很多。如果你是从 Keil 转过来最不适应的可能是“工程文件管理方式”——CubeMX 生成的是一个“源工程”用任何构建系统都能编译它而你用 MDK 时那个.uvprojx更像一个“配置集合”代码文件树、宏定义、编译参数都藏在图形界面里。转移到 CMake 之后所有配置都变成了文本可审查、可版本控制团队协作时 code review 也能看到编译参数的变化这是之前 IDE 工程做不到的。最后分享一个实用的小技巧把.vscode/launch.json和tasks.json以及user_sources.cmake都提交到 Git 仓库这样新同事 clone 代码之后只要装好工具链和插件按 F5 就能直接进调试。真正做到了“代码到位跑起来就是半小时的事”。如果你也厌倦了拿 IDE 重壳写嵌入式代码的日子这套方案值得一试。
返回列表