
1. 为什么现在越来越多嵌入式工程师放弃Keil/IAR转投VS Code“STM32开发环境”这个词过去十年几乎等同于“Keil MDK”或“IAR Embedded Workbench”的安装包和授权码。但最近两年我带的6个应届生实习生里有5个第一反应不是去官网下Keil而是打开VS Code官网下载安装包——不是因为赶时髦而是他们实习的三家汽车电子初创公司、一家工业网关厂商和两家IoT硬件团队清一色用VS Code GCC ARM工具链做STM32项目交付。这不是趋势是已经落地的生产现实。核心关键词STM32、VS Code、开发环境、工具链背后其实藏着三个硬需求跨平台一致性、CI/CD可集成性、团队协作透明度。Keil在Windows上确实点几下就能跑起来但当你需要把STM32F407的CAN FD固件自动编译、静态分析、单元测试、烧录到100台样机并生成覆盖率报告时Keil的GUI操作就成了自动化流水线里的断点。而VS Code本身是开源编辑器所有配置tasks.json、launch.json、c_cpp_properties.json都是纯文本文件Git一提交新同事clone仓库后执行一条./setup.sh就能拉起完全一致的开发环境——这点对车载以太网这类多节点协同开发的项目几乎是刚需。更实际的是成本结构变化。IAR的授权按核收费一个STM32H7双核项目就要买两份LicenseKeil的Pro版授权年费动辄上万。而GCC ARM工具链是GNU GPL v3协议VS Code是MIT协议整个工具链零授权成本。我们去年做的一个基于STM32U5的电池管理系统项目光License节省就覆盖了3名工程师半年的VS Code定制化开发投入。当然这不意味着Keil/IAR过时了——它们在超低功耗调试、复杂中断时序分析、商业级RTOS集成方面仍有不可替代性。但如果你的项目需要快速迭代、多人并行、持续集成或者涉及FreeRTOS移植比如你搜到的“freertos学习篇一:stm32f103c8t6下的移植”VS CodeGCC的组合就是更轻、更稳、更易传承的选择。我见过太多团队踩坑新人装完Keil发现芯片包版本不对调试时突然弹出License过期用IAR的工程师换项目后发现旧工程里一堆绝对路径导致无法共享甚至有客户要求提供自动化构建脚本结果发现Keil的命令行工具armcc参数文档比STM32参考手册还难啃。而VS Code环境里make flash一条命令从编译到烧录全走完所有路径都是相对路径.vscode/目录下配置文件加进Git整个环境就完成了版本化。这不是“能不能用”的问题而是“要不要为非核心事务消耗工程时间”的决策点。2. 工具链选型为什么必须用GCC ARM而不是MinGW或Clang很多人第一次搭VS Code STM32环境时会本能地想“我电脑上已经有MinGW了能不能直接用”或者看到VS Code支持Clang就想试试clang编译裸机代码。这是典型的“桌面开发思维”误入嵌入式领域——就像试图用家用搅拌机打碎花岗岩。关键不在“能不能编译”而在“编译出的东西能不能在STM32上跑”。2.1 交叉编译的本质目标架构与运行时环境的双重适配STM32是ARM Cortex-M系列处理器指令集是ARM Thumb-2M0/M3或ARMv7-MM4/M7。你的PC是x86_64或Apple Silicon指令集完全不同。MinGW是为Windows x86_64生成可执行文件的工具链它产生的二进制代码根本无法被STM32的CPU解码。Clang本身是编译器前端但默认配置下它调用的是x86_64的后端同样无法生成ARM机器码。真正需要的是交叉编译工具链Cross-compilation Toolchain在x86_64主机上运行但输出ARM Cortex-M指令、链接ARM CMSIS库、生成符合ARM ELF格式的可执行文件。GCC ARM工具链官方名称GNU Arm Embedded Toolchain正是为此而生。它包含arm-none-eabi-gccC/C编译器none-eabi表示无操作系统环境bare-metaleabi是Embedded Application Binary Interface标准arm-none-eabi-gC编译器arm-none-eabi-gdb调试器客户端arm-none-eabi-size查看代码段/数据段大小arm-none-eabi-objcopy将ELF转为BIN或HEX格式用于烧录。提示arm-none-eabi-前缀中的none指不依赖特定OS如Linux或FreeRTOSeabi确保生成的二进制符合ARM嵌入式ABI规范包括寄存器使用约定、栈帧布局、函数调用规则等。这是STM32启动代码startup_stm32f103xb.s能正确跳转到main()的前提。2.2 为什么不用Keil/IAR自带的ARMCC/ICCARMKeil的ARMCC和IAR的ICCARM确实是优秀的ARM编译器但它们是闭源商业工具不提供命令行接口的完整文档且编译产物格式AXF与开源生态OpenOCD、pyOCD兼容性差。更重要的是它们无法与VS Code的C/C扩展深度集成——VS Code的IntelliSense需要解析GCC生成的编译数据库compile_commands.json而ARMCC不原生支持此格式。我们做过对比测试同一份STM32F429的LCD驱动代码在GCC ARM 10.3.1下编译出的代码体积比ARMCC 5.06小3.2%执行效率差异小于1.5%基于Dhrystone基准测试。差距不大但GCC的可预测性更强它的优化行为-O2,-Os有公开文档每个警告-Wimplicit-function-declaration都能查到确切含义而ARMCC的某些警告如#177-D: variable was declared but never referenced在不同版本中行为不一致曾导致我们一个车载项目在升级Keil后出现未初始化变量被优化掉的隐蔽bug。2.3 实操选择官方GCC ARM vs. PlatformIO内置工具链目前主流选择有两个官方GNU Arm Embedded Toolchain推荐https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm 下载。版本号如10-2020-q4-major其中10是GCC主版本2020-q4表示发布季度。我们固定使用10.3.1因其对C17支持稳定且与STM32CubeMX 6.5生成的代码兼容性最佳。PlatformIO内置工具链PlatformIO是VS Code插件它会自动下载并管理GCC ARM版本。优点是省心缺点是版本锁定不透明升级时可能意外引入不兼容变更如某次更新将-mcpucortex-m4默认改为-mcpucortex-m4fp导致无FPU的STM32F401出错。注意不要用Ubuntu系统自带的gcc-arm-none-eabi包sudo apt install gcc-arm-none-eabi。Ubuntu仓库版本通常滞后2-3年缺少对新芯片如STM32H5、U5的支持且arm-none-eabi-gcc --version显示的版本号常与实际功能不符。务必从Arm官网下载完整安装包解压后添加bin/目录到系统PATH。3. VS Code环境搭建从零开始的完整实操流程含STM32F103C8T6实测别被网上那些“5分钟搞定”的教程误导。一个真正可用于量产项目的VS Code STM32环境需要解决四个层次的问题编辑感知IntelliSense、构建控制Build、调试交互Debug、烧录部署Flash。下面以最经典的**STM32F103C8T6俗称‘蓝色药丸’**为例全程实测记录所有路径、参数、配置均来自我们团队正在维护的车载诊断仪项目。3.1 基础安装与路径规划步骤1安装VS Code官网下载https://code.visualstudio.com/ 选User Installer避免权限问题安装时勾选“Add to PATH”确保终端能直接调用code步骤2安装GCC ARM工具链下载gcc-arm-none-eabi-10.3.1-2021.10-win32.exeWindows或gcc-arm-none-eabi-10.3.1-2021.10-x86_64-linux.tar.bz2Linux解压到固定路径例如Windows:C:\tools\gcc-arm-none-eabi-10.3.1Linux:/opt/gcc-arm-none-eabi-10.3.1将bin/目录加入系统PATHWindows系统属性 → 高级 → 环境变量 → Path → 新建 →C:\tools\gcc-arm-none-eabi-10.3.1\binLinuxecho export PATH/opt/gcc-arm-none-eabi-10.3.1/bin:$PATH ~/.bashrc source ~/.bashrc验证终端输入arm-none-eabi-gcc --version输出应含10.3.1步骤3安装核心插件仅4个拒绝臃肿插件ID名称作用必要性ms-vscode.cpptoolsC/C提供IntelliSense、语法检查、跳转定义★★★★★marus25.cortex-debugCortex-DebugGDB调试前端支持OpenOCD/J-Link★★★★★platformio.platformio-idePlatformIO IDE可选提供项目模板和库管理★★☆☆☆twxs.cmakeCMake Tools若用CMake构建则必需★★★☆☆实操心得不要装“STM32 for VS Code”之类的一键插件。它们往往捆绑过时的工具链和私有配置一旦出错难以排查。我们坚持手动配置虽然初期多花30分钟但后续3个月的稳定性值得。3.2 创建最小可运行工程无STM32CubeMX很多教程依赖CubeMX生成代码但这掩盖了底层逻辑。我们从零手写理解每一行的意义目录结构stm32f103c8t6-blink/ ├── src/ │ ├── main.c │ └── startup_stm32f103xb.s ├── inc/ │ └── stm32f103xb.h ├── cmsis/ │ └── core_cm3.h ├── linker/ │ └── stm32f103c8tx.ld ├── Makefile ├── .vscode/ │ ├── c_cpp_properties.json │ ├── tasks.json │ └── launch.json关键文件内容src/main.c标准CMSIS初始化#include stm32f103xb.h int main(void) { // 使能GPIOA时钟 RCC-APB2ENR | RCC_APB2ENR_IOPAEN; // 配置PA0为推挽输出 GPIOA-CRH ~GPIO_CRH_MODE0; // 清除模式位 GPIOA-CRH | GPIO_CRH_MODE0_0; // 设置为50MHz输出 GPIOA-CRH ~GPIO_CRH_CNF0; // 清除配置位 GPIOA-CRH | GPIO_CRH_CNF0_0; // 设置为推挽输出 while(1) { GPIOA-BSRR GPIO_BSRR_BS0; // PA0置高 for(volatile int i0; i1000000; i); GPIOA-BSRR GPIO_BSRR_BR0; // PA0置低 for(volatile int i0; i1000000; i); } }linker/stm32f103c8tx.ld内存布局MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 64K RAM (rwx) : ORIGIN 0x20000000, LENGTH 20K } SECTIONS { .text : { *(.text) *(.rodata) } FLASH .data : { *(.data) } RAM AT FLASH .bss : { *(.bss) *(COMMON) } RAM }Makefile核心构建逻辑MCU cortex-m3 CPU_FLAGS -mcpu$(MCU) -mthumb -mfpuvfp -mfloat-abisoft CFLAGS $(CPU_FLAGS) -stdgnu11 -Wall -Wextra -O2 -g ASFLAGS $(CPU_FLAGS) -x assembler-with-cpp LDFLAGS $(CPU_FLAGS) -nostdlib -T linker/stm32f103c8tx.ld CC arm-none-eabi-gcc OBJCOPY arm-none-eabi-objcopy SOURCES src/main.c src/startup_stm32f103xb.s OBJECTS $(SOURCES:.c.o) $(SOURCES:.s.o) TARGET firmware.elf all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) $(LDFLAGS) -o $ $^ $(OBJCOPY) -O binary $ firmware.bin %.o: %.c $(CC) $(CFLAGS) -Iinc -Icmsis -c -o $ $ %.o: %.s $(CC) $(ASFLAGS) -Iinc -Icmsis -c -o $ $ flash: $(TARGET) # 使用ST-Link Utility命令行烧录需提前安装 st-flash --reset write firmware.bin 0x08000000 clean: rm -f $(OBJECTS) $(TARGET) firmware.bin .PHONY: all flash clean3.3 VS Code配置文件详解三文件联动.vscode/c_cpp_properties.json—— IntelliSense的“大脑”{ configurations: [ { name: STM32F103C8T6, includePath: [ ${workspaceFolder}/inc, ${workspaceFolder}/cmsis, /opt/gcc-arm-none-eabi-10.3.1/arm-none-eabi/include, /opt/gcc-arm-none-eabi-10.3.1/lib/gcc/arm-none-eabi/10.3.1/include ], defines: [STM32F103xB, __weak__attribute__((weak)), __packed__attribute__((__packed__))], compilerPath: /opt/gcc-arm-none-eabi-10.3.1/bin/arm-none-eabi-gcc, cStandard: c11, cppStandard: c17, intelliSenseMode: linux-gcc-arm } ], version: 4 }关键点includePath必须包含工具链的系统头文件路径否则#include stdint.h会报红defines中STM32F103xB是CMSIS库识别芯片的关键宏缺了IntelliSense就找不到寄存器定义。.vscode/tasks.json—— 构建任务的“肌肉”{ version: 2.0.0, tasks: [ { label: build, type: shell, command: make, group: build, presentation: { echo: true, reveal: always, focus: false, panel: shared, showReuseMessage: true, clear: true }, problemMatcher: [ $gcc ] }, { label: flash, type: shell, command: make flash, group: build, presentation: { echo: true, reveal: always, focus: false, panel: shared, showReuseMessage: true, clear: true } } ] }注意problemMatcher设为$gccVS Code才能把arm-none-eabi-gcc的错误行号精准定位到源码否则报错只显示在终端里。.vscode/launch.json—— 调试的“神经中枢”{ version: 0.2.0, configurations: [ { name: Debug STM32F103C8T6, type: cortex-debug, request: launch, servertype: openocd, executable: ./firmware.elf, configFiles: [ /usr/share/openocd/scripts/interface/stlink-v2.cfg, /usr/share/openocd/scripts/target/stm32f1x.cfg ], searchDir: [ /usr/share/openocd/scripts ], runToEntryPoint: main, preLaunchTask: build, cwd: ${workspaceFolder}, svdFile: ${workspaceFolder}/cmsis/STM32F103xx.svd } ] }实操要点svdFile指向CMSIS-SVD文件这是Cortex-Debug显示外设寄存器视图的基础。没有它调试时只能看内存地址无法直观看到RCC-APB2ENR的各位含义。SVD文件可从ST官网下载或从STM32CubeMX生成。3.4 烧录与调试实战ST-Link vs. OpenOCDST-Link方案推荐新手硬件ST-Link/V2调试器淘宝20认准意法原厂芯片软件STMicroelectronics官网下载ST-Link UtilityWindows或stlinkLinux/macOSVS Code中CtrlShiftB构建F5启动调试自动调用OpenOCD连接ST-Link优势即插即用无需额外配置OpenOCD脚本OpenOCD方案推荐CI/CD安装sudo apt install openocdUbuntu或从官网下载配置openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg优势命令行可脚本化适合Jenkins/GitLab CI自动烧录常见问题首次调试时OpenOCD报错Error: No JTAG chain found。90%原因是SWD引脚PA13/PA14被其他外设占用。检查main.c是否误初始化了这些引脚或硬件上确认SWDIO/SWCLK线没接反。我们曾因排针插反导致3小时排查记住SWDIO接PA13JTMSSWCLK接PA14JTCKGND必须共地。4. 深度配置技巧与避坑指南来自12个真实项目的经验4.1 CMake替代Makefile当项目规模超过5个源文件时Makefile在小型项目中简洁高效但当加入FreeRTOS、FatFS、USB Device库后依赖关系爆炸式增长。此时CMake是更可持续的选择。我们为STM32H750项目切换CMake后构建时间从42秒降至18秒增量编译且find_package(CMSIS REQUIRED)自动处理芯片包路径。关键CMakeLists.txt片段cmake_minimum_required(VERSION 3.15) project(stm32h750 LANGUAGES C ASM) set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR arm) # 工具链设置 set(CMAKE_C_COMPILER arm-none-eabi-gcc) set(CMAKE_ASM_COMPILER arm-none-eabi-gcc) set(CMAKE_OBJCOPY arm-none-eabi-objcopy) # 芯片定义 add_definitions(-DSTM32H750xx) add_definitions(-DUSE_HAL_DRIVER) # 链接脚本 set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/linker/stm32h750vbtx.ld) # 编译选项 set(COMMON_FLAGS -mcpucortex-m7 -mfloat-abihard -mfpufpv5-d16 -mthumb -O2 -g) set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} ${COMMON_FLAGS}) set(CMAKE_ASM_FLAGS ${CMAKE_ASM_FLAGS} ${COMMON_FLAGS}) # 添加源文件 file(GLOB_RECURSE SOURCES src/*.c src/*.s) add_executable(firmware.elf ${SOURCES}) # 链接 target_link_libraries(firmware.elf PRIVATE m) target_link_options(firmware.elf PRIVATE -T${LINKER_SCRIPT} -nostartfiles) # 生成BIN add_custom_target(flash ALL COMMAND ${CMAKE_OBJCOPY} -O binary firmware.elf firmware.bin COMMAND st-flash --reset write firmware.bin 0x08000000 )实操心得CMake的target_include_directories()比Makefile的-I更安全它自动处理子目录递归add_subdirectory()可将FreeRTOS、CMSIS作为独立子模块管理升级时只需改一行git submodule update。4.2 FreeRTOS移植的VS Code专项配置搜索热词中有“freertos学习篇一:stm32f103c8t6下的移植”这恰恰是VS Code环境最易出错的环节。FreeRTOS的portable/GCC/ARM_CM3/目录下有汇编启动文件必须被正确识别。关键配置在c_cpp_properties.json的includePath中添加FreeRTOS的include和portable/GCC/ARM_CM3路径在tasks.json中为FreeRTOS添加预编译宏-D__USE_CMSIS -DUSE_HAL_DRIVERlaunch.json中svdFile必须指向对应芯片的SVD如STM32F103xx.svd否则FreeRTOS的vPortSVCHandler中断服务函数无法在寄存器视图中展开。我们曾遇到一个致命问题FreeRTOS任务创建后立即进入HardFault_Handler。用arm-none-eabi-gdb调试发现SP指针异常。根源是VS Code的IntelliSense误将portmacro.h中的#define portSTACK_LIMIT_CHECKING_ENABLED 1识别为启用栈检查而实际GCC编译时未定义该宏。解决方案在c_cpp_properties.json的defines中显式添加portSTACK_LIMIT_CHECKING_ENABLED0保持与编译器一致。4.3 多芯片支持如何用同一套VS Code配置管理STM32F0/F1/F4/H7大型项目常需同时支持多个芯片系列如车载项目中F0做传感器节点F4做主控H7做AI加速。硬编码芯片型号会导致配置文件爆炸。优雅方案利用VS Code工作区文件.code-workspace创建stm32-all.code-workspace{ folders: [ { path: stm32f0 }, { path: stm32f1 }, { path: stm32f4 } ], settings: { C_Cpp.default.intelliSenseMode: linux-gcc-arm, C_Cpp.default.compilerPath: /opt/gcc-arm-none-eabi-10.3.1/bin/arm-none-eabi-gcc } }然后在每个子目录的.vscode/c_cpp_properties.json中只定义芯片相关部分// stm32f1/.vscode/c_cpp_properties.json { configurations: [ { name: STM32F1, defines: [STM32F103xB], includePath: [${workspaceFolder}/inc, ${workspaceFolder}/cmsis] } ] }这样打开工作区后VS Code自动为每个文件夹加载对应配置CtrlClick跳转始终精准。4.4 性能监控实时查看Flash/RAM占用率嵌入式开发最怕“代码越写越大最后发现Flash爆了”。VS Code可集成arm-none-eabi-size实现构建后自动报告。在tasks.json中增强build任务{ label: build, type: shell, command: make arm-none-eabi-size -A firmware.elf, group: build, presentation: { echo: true, reveal: always, panel: shared } }输出示例section size addr .text 12456 134217728 .data 240 536903680 .bss 128 536903920结合STM32F103C8T6的64KB Flash和20KB RAM规格一眼看出.text占12.2KB19%余量充足。独家技巧在Makefile中添加size目标用awk计算百分比size: arm-none-eabi-size -A firmware.elf | awk /^\.text/ { text$$2; printf Flash used: %.1f%%\n, text/65536*100 } /^\.data/ { data$$2; bss$$3; printf RAM used: %.1f%%\n, (databss)/20480*100 }4.5 常见问题速查表附真实错误日志问题现象错误日志片段根本原因解决方案IntelliSense报红RCC_TypeDef {aka struct anonymous} has no member named APB2ENRRCC-APB2ENR标红stm32f103xb.h未正确包含或STM32F103xB宏未定义检查c_cpp_properties.json中defines和includePath确认头文件路径正确make flash报错st-flash: command not found终端提示command not foundstlink未安装或未加入PATHUbuntu:sudo apt install stlink-toolsWindows: 下载stlink.zip解压后添加bin目录到PATH调试时F5无响应OpenOCD卡在Info : STLINK V2J37S7 (API v2) VID:PID 0483:3748OpenOCD日志停在此行ST-Link固件过旧不支持当前芯片用ST-Link Utility升级固件至V2.J37.S7或更高arm-none-eabi-gcc: error: unrecognized command-line option -mfloat-abihard编译报错选项不识别GCC版本过低9.0不支持Cortex-M7硬浮点升级到GCC ARM 10.3.1或对M3/M0芯片改用-mfloat-abisoft烧录后LED不闪用arm-none-eabi-objdump -d firmware.elf发现main函数地址为0x08000000但实际跳转到0x08000004反汇编显示复位向量指向错误地址startup_stm32f103xb.s中Reset_Handler未正确导出或链接脚本.text段起始地址错误检查汇编文件末尾是否有.global Reset_Handler确认.ld文件中ORIGIN 0x08000000且.text段确实在FLASH中最后分享一个血泪教训某次为客户做STM32F103的OTA升级VS Code配置一切正常但现场烧录后设备死机。抓取JTAG信号发现复位后PC指针跳转到0x08000000但该地址存放的是中断向量表而非代码。排查3天才发现——客户提供的ST-Link/V2调试器是山寨版烧录时实际写入了错误偏移。解决方案用st-flash readmem 0x08000000 16读取前16字节对比标准向量表0x00000000, 0x20000000, ...。从此我们所有项目交付前必做“向量表校验”。5. 进阶场景车载以太网与VS Code的深度协同搜索热词中高频出现“stm32 车载以太网”这代表了STM32应用的最高复杂度层级。当STM32H7配合LAN8742A PHY实现100BASE-TXVS Code环境的价值才真正凸显。5.1 多线程调试FreeRTOS LwIP的上下文切换可视化车载以太网栈LwIP运行在FreeRTOS任务中网络收发、TCP状态机、应用层协议如DoIP交织运行。传统Keil调试只能单步跟踪一个任务而VS CodeCortex-Debug可同时查看所有任务堆栈在launch.json中启用FreeRTOS插件rtos: { type: freertos, heap: heap_4, tasks: { name: pcTaskGetTaskName, state: eTaskState, priority: uxPriority, stack: pxStack, topOfStack: pxTopOfStack, pxEndOfStack: pxEndOfStack } }调试时左侧“RUN AND DEBUG”面板自动列出所有FreeRTOS任务点击任一任务可切换其上下文查看该任务独占的寄存器和堆栈。我们曾用此功能定位一个DoIP协议栈的死锁tcpip_thread任务卡在sys_arch_sem_wait()而ethernetif_input任务在pbuf_alloc()失败后无限重试。在VS Code中同时观察两个任务堆栈发现内存池被netconn_write耗尽根源是未正确调用pbuf_free()释放发送缓冲区。5.2 自动化测试用Unity框架做单元测试车载软件强制要求MC/DC覆盖率≥90%。VS Code可无缝集成Unity测试框架测试文件test/test_main.c#include unity.h #include can_driver.h void setUp(void) {} void tearDown(void) {} void test_can_tx_success(void) { TEST_ASSERT_EQUAL(0, can_transmit(0x123, (uint8_t[]){1,2,3}, 3)); } void test_can_tx_timeout(void) { // 模拟硬件忙 TEST_ASSERT_EQUAL(-1, can_transmit(0x123, (uint8_t[]){1,2,3}, 3)); }tasks.json中添加测试任务{ label: test, type: shell, command: make test ./test_runner, group: test }make test会编译测试代码为x86_64可执行文件在PC上运行生成XML格式覆盖率报告再由VS Code插件Coverage Gutters可视化高亮。实操价值相比在真实硬件上跑测试慢、不稳定PC端单元测试快100倍且可模拟边界条件如CAN总线错误帧、以太网CRC校验失败。我们一个车载网关项目用此方案将回归测试时间从47分钟压缩到92秒。5.3 CI/CD流水线GitLab Runner自动构建与烧录最终极的验证是——当开发者git push后服务器自动完成git clone最新代码make build生成firmware.binssh piraspberrypi st-flash write firmware.bin 0x08000000发送企业微信通知这要求VS Code环境配置完全文本化、无GUI依赖。我们的.gitlab-ci.yml关键片段stages: - build - flash build_firmware: stage: build image: arm64v8/ubuntu:20.04 before_script: - apt-get update apt-get install -y wget make gcc-arm-none-eabi openocd - wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10-2020q4/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 - tar -xjf gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 - export PATH/gcc-arm-none-eabi-10-2020-q4-major/bin:$PATH script: -