☰
告别Keil与IAR:Windows下VSCode+ARM GCC搭建STM32开发环境
2026/10/2 18:43:46 网站建设 项目流程

前阵子翻出一块STM32F103的最小系统板,想找个轻量又免费的工具链重新编译旧工程。试了一圈下来,最后留在Windows上配置VSCode支持ARM GCC开发环境,把Keil、IAR全部卸掉,直接用开源工具链。这套组合的好处很直接:没有代码大小限制、工程文件完全是文本、可以放进Git好好做版本管理,写起来也比老牌IDE顺手。如果你也是想在Windows下搭STM32或者其他ARM单片机开发环境,正在纠结要不要脱离Keil和IAR,那这篇文章应该能给你一个完整可落地的方案,也能帮你把编译、烧录、调试三个环节全部跑通。

1. 为什么我会在Windows上放弃Keil转投VSCode + ARM GCC

1.1 这套组合到底解决了什么问题

先说痛点。Keil MDK的社区版/免费版对Cortex-M有代码量限制,刚开始写Demo感觉不到,等工程稍微大一点,链接阶段直接报“Error: L6220E”,搓火得很。IAR功能确实强,但是License价格不低,个人开发者很难下手。就算不计较费用,这两个IDE的默认风格还停留在十年前,配色刺眼,自动补全体验一般,工程文件到处是二进制状态,想把工程丢到Git上做分支、做CI都费劲。

换成VSCode + ARM GCC之后,这些麻烦基本消失:

  • 工具链完全免费,arm-none-eabi-gcc没有代码量限制,编译、汇编、链接全部命令行可执行。
  • VSCode里插件生态丰富,代码补全、语法检查、格式化都用得上。
  • 工程文件由Makefile或CMake描述,纯文本,Git友好,环境迁移成本极低。
  • 调试不再依赖某个IDE专用的Debugger,用OpenOCD加一个几十块钱的ST-Link就能跑起来。
  • 可以随时换编译器版本,升级工具链就像换一个目录那么简单。

可能有人问,那是不是立刻就要把公司老工程全部从Keil迁过来?我建议别冲动。老工程里如果用了太多MDK专有的中间件、分散加载文件、ARM Compiler特有语法,迁移成本会比较高。VSCode + ARM GCC更适合新开项目、学习项目,或者说你把底层驱动和HAL库都用标准方式组织,那随时可以切。

1.2 哪些场景适合直接用这套组合

我实际使用下来,觉得下面这几类人最合适:

  • 学生党,手上做的是课程设计、毕业设计,核心诉求是“不花钱、能编译、能烧录、能调试”。
  • 个人开发者,玩STM32、NXP、GD32这些Cortex-M芯片,工程规模中等,想用Git管理代码。
  • 喜欢命令行和自动化的人,后面想把编译挪到CI服务器,或者自己写脚本批量烧录。
  • 被老IDE审美劝退的人,想在一个现代编辑器里写嵌入式代码。

不适合的情况也有,比如你大量使用某个厂商闭源库,只有对应IDE的集成包;或者你的协作同事都用Keil,改一个工程文件发来发去,那就不太适合强行切。大多数情况下,新工程直接上VSCode + ARM GCC,体验和效率都能超过传统IDE。

最后放一张简易对比表,方便你判断:

对比维度Keil MDKIAR EWARMVSCode + ARM GCC
授权费用免费版有代码量限制商业授权较贵完全免费
代码大小限制有无无
工程文件形态二进制工程文件为主二进制工程文件为主文本(Makefile/CMake)
Git/CI友好度一般一般很好
调试支持需要ULINK/ST-Link等需要J-Link等OpenOCD/J-Link/pyOCD都行
上手成本传统但直接传统但直接需要自己配置,略陡

我到现在还保留着一个Windows虚拟机,专门用来给同事转Keil工程。但凡是自己能完全控制的工程,全部用VSCode + ARM GCC。

2. Windows下ARM GCC工具链的安装与版本选择

2.1 工具链差异:别和ARM Compiler搞混

ARM GCC这个说法,指的一般是面向ARM裸机/嵌入式开发的GNU交叉编译器,最常见的名字叫arm-none-eabi-gcc。这里的none代表没有操作系统,eabi是嵌入式应用二进制接口,编出来的目标文件是ELF格式,适合STM32这类跑裸机或RTOS的环境。

要注意,它和Keil MDK自带的ARM Compiler是两码事。Keil用的是armcc/armclang,语法、预定义、链接方式都和GCC不同。网上搜“arm compiler 5”出来的东西,很多是Keil的老版本编译器,别下错。如果用STM32CubeMX生成Makefile工程,默认就是给GCC用的,头文件宏定义、链接脚本都准备好了,我们只需要装好arm-none-eabi-gcc就能跑。

工具链来源主要有几个,我按推荐顺序排一下:

来源优点缺点
Arm官方Arm GNU Toolchain版本新、官方维护、支持Win/mac/Linux默认安装路径带空格,下载速度看网络
xpack-dev-tools免安装zip解压即用,路径清爽版本比官方稍微滞后一点
MSYS2 pacman命令行安装、方便升级依赖MSYS2环境,新手容易绕晕

我个人习惯用官方的Arm GNU Toolchain,压缩包形式解压到某个没有空格的目录,比如D:\arm-gnu-toolchain,避免后续路径问题。

2.2 下载与安装路径建议

下载时选择Windows版本,官方提供的安装包有两种:exe安装器或者zip压缩包。我建议下zip,原因很直接:以后想换版本,直接删掉文件夹换个新版本,完全不留垃圾注册表项。要保留多个版本也很简单,目录写清楚就行。

比如这样组织目录:

D:\arm-toolchains\ ├── arm-gnu-toolchain-13.2.Rel1\ │ ├── bin\ │ ├── lib\ │ └── ... ├── arm-gnu-toolchain-10.3.Rel1\ └── openocd\

把arm-none-eabi-gcc.exe所在目录加到系统PATH,比如D:\arm-toolchains\arm-gnu-toolchain-13.2.Rel1\bin。路径里不要有中文,不要有空格,这是Windows下搞交叉编译少踩坑的第一条军规。

如果你下载的是exe安装版,默认安装到C:\Program Files\Arm GNU Toolchain arm-none-eabi\13.2 Rel1\bin,这个路径带空格。某些Makefile和脚本处理空格不严谨,编译到一半就报错。所以安装时最好自定义目录,改成C:\arm-gnu-toolchain或者D:\arm...这类纯英文路径。

2.3 环境变量与版本验证

配置完PATH之后,打开你的命令行工具,执行:

arm-none-eabi-gcc --version

正常情况下能看到类似输出:

arm-none-eabi-gcc (Arm GNU Toolchain 13.2.Rel1) 13.2.1 20231009 Copyright (C) 2023 Free Software Foundation, Inc. ...

再检查一下交叉编译配置是否匹配目标机器:

arm-none-eabi-gcc -dumpmachine

输出应该是arm-none-eabi,说明编译器本身以裸机为目标。如果想看更多编译配置,可以执行:

arm-none-eabi-gcc -v

里面会打印出configure时指定的--with-arch、--with-mode等参数。默认的arm-none-eabi-gcc架构参数用的是比较保守的armv7-a?不太准确,其实裸机工具链默认使用armv7-a+simd,但STM32常用的Cortex-M系列需要我们在编译选项里指定-mcpu=cortex-m3或-mthumb。CubeMX生成的Makefile里会带上这些flag,所以我们一般不直接手动编译。

验证以后,接下来把VSCode打开,继续下一步。

3. VSCode工程化配置:tasks.json、c_cpp_properties.json和构建脚本

3.1 用一个实际的工程来看文件结构

理想情况下,你不要从零手写启动文件、链接脚本和HAL库,直接用STM32CubeMX生成一个Makefile工程最省事。CubeMX里面选好芯片型号,在Project Manager里把Toolchain/IDE设成Makefile,生成之后工程结构大概是:

MyProject\ ├── Core\ │ ├── Inc\ │ ├── Src\ │ ├── Startup\ │ └── ... ├── Drivers\ │ ├── CMSIS\ │ └── STM32F1xx_HAL_Driver\ ├── Makefile └── MyProject.ioc

用VSCode打开这个文件夹,我们要做的就是用C/C++插件让代码提示不飘红,再用tasks.json把Makefile构建接进来。如果你用的是CMake工程,思路也完全一样,只是命令从make变成了cmake。

3.2 C/C++插件的IntelliSense配置

VSCode里先安装C/C++扩展,插件ID是ms-vscode.cpptools。装完之后,创建一个.vscode/c_cpp_properties.json,告诉IntelliSense编译器在哪、头文件在哪、宏定义是什么。

以STM32F103C8为例,一份能直接用的配置长这样:

{ "configurations": [ { "name": "ARM", "includePath": [ "${workspaceFolder}/**", "${workspaceFolder}/Core/Inc", "${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc", "${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F1xx/Include", "${workspaceFolder}/Drivers/CMSIS/Include" ], "defines": [ "STM32F103xB", "USE_HAL_DRIVER" ], "compilerPath": "D:/arm-toolchains/arm-gnu-toolchain-13.2.Rel1/bin/arm-none-eabi-gcc.exe", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "gcc-arm" } ], "version": 4 }

这里有个关键点:compilerPath里的路径分隔符尽量用正斜杠/,Windows也认,而且JSON里不会出现\转义问题。intelliSenseMode要设成gcc-arm,否则代码提示的语法解析可能不对。

defines里面的STM32F103xB和USE_HAL_DRIVER不是拍脑袋填的。这两个宏定义哪里来?在CubeMX生成的Makefile里可以看到,每个源文件编译命令前面都带-DSTM32F103xB -DUSE_HAL_DRIVER,我们把它抄到defines数组里,是为了让IntelliSense在解析代码的时候走同样的预处理分支。

如果你发现某些头文件还是标红,打开C/C++扩展的输出日志,它会明确告诉你哪个include路径找不到,照着日志加就行,不用瞎猜。

3.3 把编译动作交给tasks.json

接下来要让Ctrl+Shift+B直接触发编译。创建一个.vscode/tasks.json,绑定Makefile。

如果你在Windows上没有安装make,那么这个阶段必须先把make装上。CubeMX生成的Makefile本质是GNU Make语法,Windows自带CMD可跑不了。常见做法是装MSYS2,在MSYS2里执行pacman -S make,然后把C:\msys64\usr\bin也加入PATH。第二个办法是下载GnuWin32的make,但我推荐MSYS2版本,它跟CubeMX生成的Makefile兼容性更好。

有了make之后,tasks.json这样写:

{ "version": "2.0.0", "tasks": [ { "label": "Build Firmware", "type": "shell", "command": "make", "args": [ "-j4" ], "options": { "cwd": "${workspaceFolder}" }, "group": { "kind": "build", "isDefault": true }, "problemMatcher": [ "$gcc" ] } ] }

"problemMatcher": ["$gcc"]的意思是让VSCode解析GCC风格的编译输出,这样双击下方问题面板里的错误,会自动跳到对应源文件行号。没有这一行,编译报错就只能去终端里看,效率会低不少。

保存文件后按Ctrl+Shift+B,第一次会提示选择构建任务,选Build Firmware。如果一切正常,你会在输出面板看到GCC一长串编译日志,最后生成目标文件,比如build/MyProject.elf。

3.4 引入CMake/Ninja的另一种玩法

Makefile够用,但如果你习惯CMake,也可以用CMake工具链。这里简单说下思路:在项目根目录放一个CMakeLists.txt,指定CMAKE_TOOLCHAIN_FILE指向你的交叉编译工具链配置,然后配置用Ninja生成。

附一个最简的CMake交叉编译工具链文件arm-none-eabi.cmake:

set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR arm) set(TOOLCHAIN_PREFIX arm-none-eabi-) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

然后构建命令可以这样:

cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=arm-none-eabi.cmake cmake --build build

Ninja和CMake在Windows上的安装同样可以通过MSYS2或pip/包管理器搞定。这么做的好处是以后加模块、加编译选项都更灵活,而且IDE支持也更广。不过对只想快速跑通HAL库的人来说,CubeMX生成的Makefile已经够用,不需要额外折腾。

4. 烧录与调试:从OpenOCD到Cortex-Debug

4.1 OpenOCD:免费的调试服务器

编译搞定,下一步是把程序烧到板子里,然后能打断点看变量。这里有两个选择:一是直接用VSCode的Cortex-Debug插件配合OpenOCD,二是在命令行里用OpenOCD手动烧录。我建议先把OpenOCD跑明白,再回VSCode里接插件。

OpenOCD全称Open On-Chip Debugger,它支持ST-Link、J-Link、CMSIS-DAP这些常见的调试器。在Windows上安装很简单,下载官方编译好的Windows压缩包,解压到某个目录后把bin目录加入PATH。

连上ST-Link和板子,先验证设备能不能被识别:

openocd --version openocd -f interface/stlink.cfg -c "transport select hla_swd" -f target/stm32f1x.cfg

正常的话会启动一个GDB Server,监听在3333端口,终端停在类似Info : Listening on port 3333 for gdb connections的日志。说明探针和芯片都活了。

如果你用的是J-Link,把接口脚本换成interface/jlink.cfg,传输方式通常是SWD,写-c "transport select swd"。CMSIS-DAP则用interface/cmsis-dap.cfg。

4.2 Cortex-Debug插件与launch.json

命令行能跑通之后,回到VSCode,安装Cortex-Debug扩展,插件ID是marcus.cortex-debug。然后创建.vscode/launch.json:

{ "version": "0.2.0", "configurations": [ { "name": "Cortex Debug STM32", "cwd": "${workspaceFolder}", "executable": "${workspaceFolder}/build/MyProject.elf", "request": "launch", "type": "cortex-debug", "servertype": "openocd", "device": "STM32F103C8", "configFiles": [ "interface/stlink.cfg", "target/stm32f1x.cfg" ], "svdFile": "${workspaceFolder}/STM32F103xx.svd", "runToEntryPoint": "main", "showDevDebugOutput": "none" } ] }

几个字段说下:

  • executable必须指向ELF文件,不要填hex或bin,调试器需要ELF里的符号表。
  • configFiles顺序有讲究,第一个是调试器接口,第二个是目标芯片配置,跟命令行保持一致。
  • svdFile是芯片外设寄存器描述文件,填了之后在调试窗口可以直接看寄存器位域,非常实用。这个文件STM32CubeMX一般不带,可以去芯片厂商或官方SDK里找,也可以从OpenOCD的contrib目录拷贝。
  • runToEntryPoint设为main,按F5之后会自动运行到main函数,不用手动跳过一堆启动代码。

保存后按F5,Cortex-Debug会拉起OpenOCD,然后连接板子并下载ELF。如果看到调试工具栏出现,断点命中,说明整套链路已经通了。

4.3 烧录命令与常见失败点

有时候不需要进调试,只想把固件烧进去,用OpenOCD命令行是最快的:

openocd -f interface/stlink.cfg -c "transport select hla_swd" -f target/stm32f1x.cfg -c "program build/MyProject.elf verify reset exit"

这条命令会把ELF文件写入Flash,自动校验,然后复位运行,最后退出OpenOCD。如果你手头只有hex或bin,也可以写:

program build/MyProject.hex verify reset exit

烧录失败的常见原因主要是这几类:

  • ST-Link驱动没装好,设备管理器里能看到未知设备,下载ST官方驱动装上即可。
  • 芯片SWD口被复用成GPIO,导致连接不上。这种情况需要按住复位键、启动OpenOCD,再在芯片复位的瞬间松开,靠时间差先把连接建立起来。
  • 接口脚本写错,STM32F1系列一般需要指定transport select hla_swd,其他部分芯片默认用SWD,可能不需要额外命令。
  • 路径里有空格或者ELF文件路径写错,导致OpenOCD找不到固件文件。

5. 踩坑记录:路径、变量、版本不对齐这些事

5.1 环境变量改了,终端里却还是旧版本

这是一个特别常见的问题:明明装好了新版本arm-none-eabi-gcc,也改了系统PATH,但VSCode终端里执行arm-none-eabi-gcc --version还是显示旧版。

原因很简单,Windows环境变量的修改不会实时注入到已经打开的进程里。VSCode在启动时继承了当时的PATH,所以你在VSCode内部开的终端还是旧环境。这时候不是工具链的问题,是整个VSCode没有重启。把所有VSCode窗口关干净再开,终端里的PATH才会刷新。

注意这里有个细节:如果你是先装了新版本工具链,之后才打开VSCode,按理说VSCode会拿到新PATH。就怕你装了多个交叉编译器,比如MSYS2里也装了一个老版GCC,那就要看PATH顺序了。用where arm-none-eabi-gcc看一下实际找到的是哪个路径,如果是老版本目录排在前面,就手动调整PATH顺序,把新版本目录往前放。

这个坑和很多人在Linux上折腾“升级gcc后还是旧版本”是同一个道理,本质都是系统里有多个gcc,shell找错了。

5.2 安装目录里的空格和中文

Windows下交叉编译最常见的问题就是路径里出现空格或中文。比如官方Arm工具链默认安装到C:\Program Files\...,Makefile里如果直接拼路径,不写引号,编译器就会把路径截断,报No such file or directory。

所以我前面特别强调,解压工具链时目录名坚持用纯英文、无空格,比如D:\arm-toolchains\...。工程路径也别放在C:\Users\张三\我的项目这种地方。如果你已经在中文路径下建了工程,最好现在就把目录改名,不然后面每次编译、烧录都可能遇到莫名其妙的问题。

如果实在没法避免空格,可以在Makefile里给工具的路径加引号,或者在CMake里正确转义,但这些都是补救措施,不如一开始就避免。

5.3 GCC升级后老工程突然编译不过

从GCC老版本切到新版本,最典型的一个问题:原来编译通过的工程,换了工具链后报multiple definition of 'xxx'。这是因为GCC 10开始默认把-fno-common打开了,C语言里如果在头文件定义全局变量,多个源文件包含之后就会产生重复定义。

解决办法有两个方向。一是修改代码,把变量定义放到.c文件,头文件里用extern声明,这是干净的做法。二是临时在Makefile里加回-fcommon,让老工程先跑起来。

另一个常见的编译报错是头文件里用了asm关键字,而新版本GCC要求写成__asm__或__asm,这通常是因为代码写在#ifdef __GNUC__保护区域里,但编译器标准变了。遇到这种问题,先看报错定位到哪个宏,再针对性加兼容定义。

换新编译器之前,我建议你先看一遍工程Makefile里的优化选项和警告选项,老工程经常带着-Werror,新GCC对某些代码的警告更激进,直接就把警告升级成错误,导致编译中断。

5.4 OpenOCD识别不到ST-Link

OpenOCD启动后如果一直卡在Error: open failed或者unable to find a matching CMSIS-DAP device,先分两步排查。第一步看系统是否识别了ST-Link:打开设备管理器,确认ST-Link出现在“通用串行总线设备”或“通用串行总线控制器”下面,并且没有黄色感叹号。如果有感叹号,先装ST官方的ST-Link驱动,装完重新插拔。

第二步看OpenOCD用的接口脚本和实际调试器是否匹配,ST-Link和J-Link的脚本配置完全不同。如果用的是ST-Link V2,接口脚本一般是interface/stlink.cfg,不要用interface/cmsis-dap.cfg,也别用interface/jlink.cfg。之前我见过有人手里明明是ST-Link,却照着网上教程写了个JTAG接口的脚本,当然连不上。

还有一个小问题:某些ST-Link的固件太老,新版OpenOCD可能不兼容,这时候要用ST官方工具升级一下ST-Link固件。

5.5 工作区文件太多导致的“灵异问题”

VSCode一旦打开整个工程目录,它会默认把.git、build、Drivers这些通通索引进来。集成终端跑任务还好,但代码提示偷偷扫描太多文件,编辑时会明显卡顿。

我的经验是,在.vscode/settings.json里把无关目录排除掉:

{ "files.exclude": { "build": true, ".git": true }, "search.exclude": { "build": true, "Drivers": true }, "C_Cpp.files.exclude": { "build": true } }

Build目录如果不排除,C/C++插件的IntelliSense会把几千个编译中间文件也纳入解析范围,轻则卡顿,重则内存占用飙到几个GB。排除之后,工作区干净很多,打开工程速度会明显提升。

6. 最后再分享一点个人体会

如果让我总结这次搭建Windows VSCode + ARM GCC开发环境最大的感受,那就是“把工具链拆分到极致”:编辑器是编辑器,编译器是编译器,调试服务器是调试服务器,各管一摊,出了问题很容易定位。比把所有功能塞进IDE里,出现问题一片黑盒要舒服得多。

我建议第一次搭的时候,不要一股脑装一堆扩展。先老老实实把编译器装好,Makefile编译通,OpenOCD命令行能烧录,再回头配VSCode的调试插件。这样每一步都有明确验证节点,出问题也容易知道是哪一层挂了。我一向推荐这种方式,因为它是在为后续长期开发打基础。

另外,如果你以后想换电脑或者去别的环境复现,只需要保证工具链版本一致,然后把整个工程目录和.vscode配置一起拷走,几分钟就能恢复开发环境。这种可移植性就是VSCode + ARM GCC相比传统IDE最大的隐形福利。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询