STM32使用CMake+VSCode实现全平台开发

STM32和VSCode开发环境

Tips:本文章撰写环境为Archlinux和STLinker调试器,Windows版本的配置理论上一致,只需要在VSC里面手动选择STM32CubeClt和内部工具链的路径即可(实测)。DAPLink和JLink等SWD调试器也可以使用,但需要自行修改配置文件(本人推荐STLinkerV3 MINIE,价格实惠且性能卓越)

1. 背景

众所周知,自固件库停止维护和CubeMX盛行以来,STM32开发主要依赖几种IDE:

  • Keil MDK - 最广为人知,99%的嵌入式初学者首选
  • IAR - 商业工具链,功能强大但价格昂贵
  • STM32CubeIDE - ST官方基于Eclipse开发的IDE

然而,Keil MDK由于ARM公司更新缓慢(半年到一年才修个小版本)以及开发方向的摆烂(Keil6文档不全,功能难用),已成为跟不上时代步伐的老古董。其中的痛点包括:

  • 编码乱码问题严重
  • 智能提示卡顿(你先别管智能不智能吧,打错一个字母就卡IDE十多秒也太逆天了)
  • 与现代IDE相比功能落后

虽然Keil的AC5和AC6编译器仍然性能不错,配置简单,但ARM"贴心"地只允许你使用随Keil捆绑的编译器,单独下载则需要商业License,这样的做法也就杜绝了Linux的配置使用可能,因为Keil以及配套的编译器只提供了Windows的版本。STM32CubeIDE同样存在智能提示不佳、配置繁琐、缺乏现代化体验等问题

最新悲报,亲测最新版本的Keil 5.42a破解License失效了,编译大小还是受限,必须降级回去使用,但是没有License的情况下ARM不提供在官网的旧版本下载,只能找第三方下载站,又要和下崽站斗智斗勇惹。。。。

2. 解决方案

那么,能否使用更现代化的VSCode来进行STM32开发呢?答案是肯定的!

2.1 历史尝试

  1. Keil6 VSCode扩展:ARM曾尝试将Keil做成VSCode插件系列,但实用性差,配置文档至今仍是TODO状态。

  2. EIDE插件:通过逆向KEIL、IAR、Eclipse的工程配置文件来创建VSCode工作空间。

    • 可使用Keil的AC编译器和STM32CubeIDE工具链
    • Debug功能存在诸多问题(无法调试、运行异常、芯片卡启动)
    • 开发时需VSCode写代码,Keil专门Debug
    • Keil的AC编译器仅限Windows使用,Linux用户需切换系统

2.2 官方解决方案

重大进展:2024年7月1日,ST发布了STM32CubeMX 6.12.0版本!

这次更新带来了两项关键功能:

  1. CMake工程生成选项:在CubeMX中直接生成CMake项目
  2. STM32 VS Code Extension:官方VSCode插件,支持CMake工程

虽然插件看起来相对简陋,但它能自动根据STM32工程生成配置文件和脚本,提供必要的编译、调试和下载功能,且允许用户自定义CMake文件以实现个性化配置

这种方案的最大优势在于:

  • 跨平台支持:摆脱Windows限制
  • 现代开发体验:利用VSCode的插件生态
  • 完全自定义:通过CMake控制编译过程
  • 全套功能:编译、调试、烧录一应俱全

经过实测,编译下载调试功能完全无问题,但是下载程序默认下载在用户空间起始地址,如果想要分散加载的话可以使用不同工程编译后将文件使用STM32CubePrg下载到对应地址即可(分散加载、BOOT和OTA实现另开文章叙述)

3. 安装

3.1 插件安装与环境配置

首先,我们需要在VSC里面安装ST的官方插件、Cortex-Debug(用于Debug)、C/C++和CMake插件(这两当然不用我说),扩展商店搜索STM32 VS Code Extension

STM32插件

根据STM32插件描述的内容,我们需要准备4个东西

  • STM32CubeCLT v1.15.0 or later
  • STM32CubeMX v6.11.0 or later
  • ST-MCU-FINDER
  • Libncurses

STM32插件描述

其实必要的是两个,STM32CubeCLT和Libncurses,STM32CubeCLT提供工具链。

Libncurses提供linux上使用的UI,使用 Linux 时必须安装此要求。(Win不需要)

STM32CubeCLT可以在Arch的AUR里面直接安装,也可以从ST官网手动下载然后配置环境变量,这里推荐AUR直接装(Win可以从官网直接下载解压)

yay -Sy stm32cubeclt

STM32CubeMX原则上不需要作为插件工作的依赖,插件可以独立使用STM32CubeCLT完成编译、下载、调试等工作,但是你不可能开发不使用STM32CubeMX,所以还是装一下,使用Arch也是可以AUR直接装(如果出现校验出错问题说明ST又不让没登陆的用户下载了,需要手动从ST官网下载包安装,具体方法在stm32cubemx的AUR仓库下的评论区里有,照做就行)

至于STM32Finder其实已经内置在STM32CubeMX里面了,只是一个选型工具,有最新的芯片信息,不安装也无所谓

yay -Sy stm32cubeclt

Windows的STM32CubeMX的安装和登陆就不在这里赘述了

关于Libncurses,Arch可以直接在pacman的core里面安装ncurses

sudo pacman -Sy ncurses

ubuntu用户可以使用APT安装

sudo apt install libncurses 

安装插件后打开配置,将STM32CubeCLT的目录填写进设置中

STM32插件路径

Arch AUR安装的STM32CubeCLT默认目录在/opt/stm32cubeclt,另外两个可以不填写,因为作用只是从扩展启动一下,不是很有必要,想填也行,AUR安装的都在/opt/下。Windows就填写自己选择的安装/解压的目录即可

至此,我们就完成了环境的配置

3.2 工程的创建

插件创建工程的方式非常简单,也和我们传统使用Keil的工作流大差不差

首先,打开STM32CubeMX,登陆账号

STM32CubeMX

(可以看到左下角写的大大的CMake支持(笑))

然后按照基本流程,选择MCU,我这里使用我最常用的STM32G431CBU6作为演示

MCU选择

Tips:这个页面其实就是STM32Finder,单独下载的Finder只有这个按条件查询和介绍的选型功能,所以说不是很有必要,而且CubeMX也包含在内了。

双击打开,进入配置页面,然后按照大家平常自己的习惯配置晶振、时钟 And so on…

我自己的板子一般喜欢用YXC的25MHz的有源晶振,贵一点点钱但是极大程度的确保了时钟系统的稳定性和试错Debug成本,所以就按有源配置了,配置如图所示(开发板硬件设计以后再出一篇文章讲讲吧~~)

时钟源配置

时钟树配置

然后就可以直接生产工程了,我们选到ProjetManager页面,决定工程名字和目录

工程配置

填写好路径和名称后,这里和Keil不一样的地方就来了,我们把工具链选择的选项选到CMake,然后再选择生成,就会得到一个配置好CMake的工程目录

代码生成

打开工程文件夹,你会看见:

工程文件夹

现在可以从这个文件夹打开VSC了

VSC界面

打开以后就是这个样子,第一次打开CMake工程的时候,CMake插件会让你选择编译类型,这一步先别管,随便点一下外面就会消失

Tips:我遇到的问题,就是ESP-IDF插件会和STM32的官方插件冲突,如果你使用ESP-IDF记得在工作空间禁用一下
顺带说一个好玩的事情,这两的工程结构都差不多,而且编译工具都是ninja,所以当我不小心在STM32的工程下用了ESP-IDF的编译按钮,他居然成功编译了,而且因为之后会提到的一个ST非常逆天的CMake配置上的问题,STM32插件自己的编译还失败了,就很戏剧性(

之后我们打开左侧的STM32拓展页面,选择Import CMake Project

导入CMake工程

然后在弹出来的目录选择里面选取CubeMX生成的工程目录

导入CMake工程

点击确定以后,就会看到STM32的拓展弹出来的配置窗口,正常情况下就确认一下信息是否正确就好了,一般不会有问题,确认没问题直接点最下面的导入

导入工程配置

这时候左下角会显示一个✅和Import down的字样,就说明导入成功了,这个时候我们就可以在左边CMake扩展的窗口选择编译类型,开始选择Debug就好,之后程序定稿了我们再选择Release

Tips:这里要注意,选择了Release编译出来的程序是没有调试信息的,所以你使用Release版本Debug会找不到调试信息和断点,点运行就跑飞,如果需要Release版本又要能Debug的话请选择RelWithDebInfo配置

编译类型配置

然后我们就可以点击页面最下面的生成来测试编译能否通过啦~

点击编译按钮,看到右边出现这个ROM和RAM占用的信息就说明编译成功了

Tips:这里会出现因为环境变量错乱的问题导致工具链指向不正确,因为这里使用的编译工具全部都是Cubeclt内置的,如果你电脑本身也装了这些工具就有可能导致环境变量冲突、版本冲突、目录冲突等问题导致编译失败,这种时候需要在环境变量里面明确指向Cubeclt文件夹内的工具链,这个问题已经是我很久很久以前遇到的了,后来再新配好像也没有复现,怎么配置环境变量解决的具体操作忘了(诶嘿~),所以如果又遇到了请留言给我我

编译

至此,工程的创建和初步的编译就已经完成了

3.3 工程配置相关

3.3.1 目录结构

这里需要了解一下工程目录的顶层目录结构

目录结构

📁 CMAKEPROJECTDEMO
├── 📁 .vscode                  # VS Code 配置文件夹
├── 📁 build                    # 构建输出目录
├── 📁 cmake                    # CMake 相关配置文件
├── 📁 CMakeFiles               # CMake 生成的中间文件
├── 📁 Core                     # 核心代码
├── 📁 Drivers                  # STM32 驱动
├── 📄 .mxproject               # STM32CubeMX 项目文件
├── 📄 build.ninja              # Ninja 构建系统文件
├── 📄 cmake_install.cmake      # CMake 安装脚本
├── 📄 CMakeCache.txt           # CMake 缓存
├── 📄 CMakeLists.txt           # 主 CMake 项目文件
├── 📄 CMakePresets.json        # CMake 预设
├── 📄 CMakeProjectDemo.ioc     # STM32CubeMX 配置文件
├── 📄 compile_commands.json    # 编译命令数据库(用于代码智能提示)
├── 📄 startup_stm32g431xx.s    # 启动汇编代码
└── 📄 stm32g431cbux_flash.ld   # 链接器脚本

VS Code 配置文件

📁 .vscode
├── 📄 c_cpp_properties.json    # C/C++ 配置
├── 📄 extensions.json          # 推荐扩展
├── 📄 launch.json              # 调试配置
└── 📄 tasks.json               # 任务配置

核心代码结构

📁 Core
├── 📁 Inc                      # 头文件目录
│   ├── 📄 main.h               # 主头文件
│   ├── 📄 stm32g4xx_hal_conf.h # HAL 库配置
│   └── 📄 stm32g4xx_it.h       # 中断处理函数声明
└── 📁 Src                      # 源文件目录
    ├── 📄 main.c               # 主源文件
    ├── 📄 stm32g4xx_hal_msp.c  # MSP (MCU Support Package) 初始化
    ├── 📄 stm32g4xx_it.c       # 中断处理函数实现
    ├── 📄 syscalls.c           # 系统调用实现
    ├── 📄 sysmem.c             # 内存管理
    └── 📄 system_stm32g4xx.c   # 系统初始化

驱动文件结构

📁 Drivers
├── 📁 CMSIS                    # Cortex 微控制器软件接口标准
│   └── ...
└── 📁 STM32G4xx_HAL_Driver     # 硬件抽象层驱动
    └── ...

主要文件说明

  • CMakeLists.txt: 项目的主 CMake 文件,定义项目设置、源文件、包含路径等
  • CMakeProjectDemo.ioc: STM32CubeMX 配置文件,包含芯片配置信息
  • stm32g431cbux_flash.ld: 链接器脚本,定义内存布局
  • startup_stm32g431xx.s: 启动文件,包含启动代码和向量表
  • main.c: 包含程序入口点和主循环
3.3.2 文件添加

这里要注意的是,添加源文件和配置文件需要在CMakeLists.txt文件里面添加包含路径,分头文件和源文件,如图所示,CMakeLists.txt文件里面已经标注好了如果需要添加文件在哪里添加

CMake

对于头文件来说,如果只是单一文件可以直接塞到Inc目录下,不需要做修改;如果是要添加一个新的头文件目录,就在对应target_include_directories位置添加目录的相对路径,要注意的是,.h文件是以目录为单位添加的,所以填写的是目录路径,而.c源文件是以文件为单位添加的,所以就算是在Src目录下添加.c文件,也需要在这里的target_sources下填写相对路径,下面给出一个我的工程例子

DemoInclude

这里我没用到独立的头文件目录,所以头文件就留空了,而自己添加了两个源文件,就把相对路径填写在了这里。

3.3.3 编译配置

在STM32生成的cmake目录下的cmake/gcc-arm-none-eabi.cmake配置中,我们可以看到ST已经帮我们写好了完善的编译器配置选项,包括启用硬件浮点,Debug和Rel的优化级别,如果需要修改的话可以在这里修改,但一般不需要

MCUFlags

这里需要注意一件事情,也是我自己遇到的,默认情况下编译的工程没法打印浮点数到字符串,也就是sprintf函数没法用于浮点数,需要手动在CMakeLists.txt文件中添加

target_link_options(${PROJECT_NAME} PRIVATE
    -u _printf_float  # 如果需要浮点打印支持
)

才可以开启浮点数打印支持

至此,配置相关的问题就说完了

4. 开发、下载与调试

4.1 开发

在VSC的开发和Keil也差不多,也是从main.c开始运行程序

使用CubeMX修改后重新生成,VSC也会自动重新读入文件,这里建议打开VSC的自动保存选项,在右上角选项的文件->自动保存,防止忘记保存时CubeMX修改后重新生成覆盖我们写的代码,也增加代码的同步性

然后其他内容也和Keil方法一致,编译点下面的生成即可,但是可以看出,用这套工具链我们的编译速度不Keil不知道高到哪里去了~

建议打开GitHub Copilot获得最佳极致体验哦~

4.2 下载

下载的实现很简单,就是给VSC写了个Task配置文件来调用STM32Cubeclt来选中编译好的文件进行下载,那我们下载的时候只要插上STLinker并确定识别,然后使用VSC运行Task即可

刚刚研究使用的时候我找了很久没找到怎么下载,插件也没设计按钮,后来翻生成的VSC配置文件才找到,所以我说插件简陋,按钮UI都舍不得做点((((

使用快捷键 Ctrl + Shift + P 我们可以打开VSC的运行页面,输入Task,选择如图所示的运行任务

Tips:如果使用过运行任务的话,之后按Ctrl + Shift + P 打开运行界面第一个就会是运行任务,此时直接按回车就可以直接调用了

Task

然后我们连接STLinker和开发板,这里我使用自制的IMU板子和ST官方的STLinkerV3 MINIE来演示

[外链图片转存中…(img-YRhnq0ys-1744042568268)]

这里可以使用STM32CubePrg来检查调试器和开发板的连接状态,STLinker如果连接成功就会直接显示,点击Connect有数据就说明连接正常,这里我们成功正常连接

STM32CubePrg

然后关闭STM32CubePrg,回到我们的VSC工程,Ctrl + Shift + P 输入Task ,选择运行任务,然后不出意外的话Build and Flash就会是第一个,这个选项就是编译并下载,我们直接按回车就可以完成下载

TaskFlash

也就是说,当你运行过一次以后,下次按 Ctrl + Shift + P 加上两次回车就可以直接编译并下载了,很方便吧~

如果成功下载,下面的控制台会输出如下所示画面

Flash

没错,我的输出那么完整是因为我拉开了截图的,滚动查看一样就行

如此,我们就完成了一次下载

4.3 调试

完成了下载,接下来我们就需要调试了,确定插件Cortex-Debug已经安装,并且编译选项选择Debug,然后我们可以直接切换到VSC左侧选项栏的Debug界面直接点击Debug,理论上不出意外的话就可以直接开始调试了

Debug

很正常的获取到了调试信息,运行到断点也没有问题,变量值也可以正常显示和监控

至此,调试部分也就结束了,很简单对吧~

Q&A

CMake清理

前面我提到过 “而且因为之后会提到的一个ST非常逆天的CMake配置上的问题,STM32插件自己的编译还失败了” 这个这句话,那你一定很好奇是什么问题吧?

没错,这个工程没有配置CMake缓存清理类的脚本或者CMake任务,而他的缓存保存的是 绝对路径

没错!绝对路径!!!!

也就是说,一旦你第一次编译后,把工程移动一下位置,诶,这个工程就不能编译了,我第一次发现是在两台电脑上开发,一台推上GitHub另一台拉下来

诶,编译不能

就很逆天,然后我研究了半天他的缓存机制,总算手动解决这个问题,于是乎现在,我写了一个bash脚本来自动化解决这个问题,只要把这个脚本放在工程目录下,每次移动完以后运行一下,就可以正常编译了


#!/bin/bash
#获取当前脚本所在目录作为项目根目录

PROJECT_ROOT=$(cd `dirname $0` && pwd)
BUILD_DIR="$PROJECT_ROOT/build/Debug"

echo "=== 清理旧构建缓存 ==="
if [ -d "$BUILD_DIR" ]; then
    rm -rf "$BUILD_DIR" && echo "已删除旧构建目录"
fi

echo -e "\n=== 生成新构建系统 ==="
cmake -DCMAKE_BUILD_TYPE=Debug \
      -DCMAKE_TOOLCHAIN_FILE="$PROJECT_ROOT/cmake/gcc-arm-none-eabi.cmake" \
      -S "$PROJECT_ROOT" \
      -B "$BUILD_DIR" \
      -G Ninja

echo -e "\n=== 开始编译 ==="
cmake --build "$BUILD_DIR" 

在工程下新建文件,名字随意后缀为.sh,把这个脚本复制进去保存,然后别忘了

chmod +x xxx.sh

然后运行即可,下面给出运行结果

Rebuild

尾声

至此,本篇教程就结束啦,希望你通过本篇教程可以得到一个舒适完美的STM32开发环境!

Q&A部分目前就想到这个,很多问题我可能自己解决了但是忘记遇到过了,后续如果再遇到相关的会在这个部分持续更新,如果有遇到问题也可以留言给我,我会把问题在Q&A部分继续更新

感谢您的观看!

相关链接

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐