多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32开发环境迁移:从Keil到VSCode的完整指南

STM32开发环境迁移:从Keil到VSCode的完整指南 1. 为什么我最终把STM32的开发环境从Keil搬到了VSCode第一次接触STM32的时候我和大多数人一样老老实实装了Keil MDK用着那个上世纪的界面忍受着偶尔抽风的代码补全和龟速的编译。后来项目里开始用Git做版本管理Keil的工程文件冲突几乎每次合并都要手动处理团队里几个人同时改一个工程.uvprojx文件能给你整出一堆莫名其妙的报错。再后来我开始写一些跨平台的上位机工具习惯了VSCode的插件生态和终端体验回头再看Keil那个编辑器实在回不去了。于是就有了这个折腾的过程把STM32的开发环境完整地搬到VSCode上。这件事听起来简单实际上涉及工具链选型、编译系统配置、调试器对接、工程结构组织等一连串问题。我前后踩了大概两轮坑第一轮用Makefile手写构建规则第二轮换成了CMake加官方工具链才算把整个流程跑顺。现在这套环境我已经在三个实际项目上用过包括一个基于STM32F103的温湿度采集报警器和一个用STM32G431做的数字电源控制板编译、下载、调试、串口输出全部在VSCode里完成体验相当稳定。这篇文章就是把这套流程完整拆开讲一遍。从工具链的选型逻辑到每一步的具体配置再到实际调试时遇到的典型问题和排查方法我都会尽量说清楚。目标读者是已经有一点STM32基础、想换一个更现代的开发环境的嵌入式工程师或者是刚入门、不想被Keil绑死的学生朋友。整套方案在Windows上验证通过Linux和macOS下思路一致只是工具链安装方式略有不同。2. 工具链选型为什么是这套组合2.1 编译器为什么选arm-none-eabi-gcc而不是Keil AC5/AC6Keil用的是ARM自家的编译器AC5和AC6。AC6基于LLVM/Clang优化能力确实强但它是闭源的只能在Keil MDK或者Arm Development Studio里用。一旦你想把构建流程搬到命令行或者CI/CD里就会遇到授权和可移植性的问题。arm-none-eabi-gcc是GNU工具链的ARM嵌入式版本完全开源Windows、Linux、macOS都能跑。它的编译质量在大多数嵌入式场景下和AC6差距不大对于F1、F4、G4这些常见系列完全够用。更重要的是它和CMake、Make这些构建工具是无缝配合的你可以把整个编译流程写成脚本放进Git仓库任何人clone下来就能编译不需要装Keil。有一个细节需要注意arm-none-eabi-gcc的启动文件、链接脚本和Keil用的不是同一套。Keil有自己的分散加载文件.sct而GCC用的是链接脚本.ld。STM32CubeMX可以直接生成GCC版本的Makefile和链接脚本这是最省事的路径。2.2 构建系统为什么选CMake而不是手写Makefile我第一轮折腾的时候用的是STM32CubeMX生成的Makefile直接make就能编译。但Makefile的问题是当你想加一个新的源文件目录、换一个芯片型号、或者调整编译选项的时候改起来很痛苦。而且Makefile的语法对新手不友好一个Tab和空格的混用就能让你排查半天。CMake的好处是抽象层次更高。你描述的是“我要编译这些源文件链接这些库生成一个elf文件”而不是“用gcc把a.c编译成a.o然后用ld把a.o和b.o链接起来”。STM32CubeMX从某个版本开始也支持生成CMake工程或者你可以用STM32CubeCLT里自带的CMake模板。CMake配合Ninja生成器编译速度比Makefile快不少尤其是在多核机器上。2.3 调试器为什么用OpenOCD加Cortex-DebugKeil用的是ULINK或者ST-Link的官方驱动调试体验确实好但它是和Keil IDE绑定的。在VSCode里调试STM32的标准方案是OpenOCD加Cortex-Debug插件。OpenOCD负责和ST-Link或者J-Link、DAPLink通信Cortex-Debug负责在VSCode里提供图形化的调试界面断点、单步、变量查看、寄存器查看都有。ST-Link的官方驱动在Windows上有时会和OpenOCD抢设备导致OpenOCD报“no device found”。解决办法是用Zadig把ST-Link的USB驱动替换成WinUSB或者直接用ST官方出的STM32CubeProgrammer里的ST-Link驱动它和OpenOCD兼容性更好。这个坑我在后面会详细说。2.4 整体工具链清单工具作用安装方式VSCode代码编辑和调试界面官网下载安装arm-none-eabi-gccARM交叉编译器通过MSYS2或xPack安装CMake构建系统官网下载或包管理器Ninja构建后端随CMake或单独安装OpenOCD调试器通信xPack或MSYS2安装Cortex-DebugVSCode调试插件VSCode扩展市场STM32CubeMX生成初始化代码和工程骨架ST官网下载STM32CubeCLTST官方命令行工具集ST官网下载这套组合的好处是全部免费、跨平台、可脚本化。你可以在CI里用同样的工具链做自动化编译本地和服务器环境完全一致。3. 环境搭建实操从零到点亮第一颗LED3.1 安装arm-none-eabi-gcc并验证Windows下安装GCC工具链最省事的方式是用MSYS2。装好MSYS2之后打开MSYS2的终端执行pacman -S mingw-w64-x86_64-arm-none-eabi-gcc pacman -S mingw-w64-x86_64-arm-none-eabi-binutils pacman -S mingw-w64-x86_64-arm-none-eabi-newlib装完之后把C:\msys64\mingw64\bin加到系统PATH里。然后在PowerShell或者CMD里验证arm-none-eabi-gcc --version如果输出了版本号说明安装成功。我实测下来MSYS2的包更新比较及时GCC版本一般在12以上对Cortex-M4和M33的支持都很好。另一个选择是用xPack的独立安装包它不依赖MSYS2直接解压就能用。xPack的版本更新更快但有时候newlib的配置和MSYS2略有不同如果你遇到printf浮点数输出问题可能需要检查newlib的编译选项。3.2 安装CMake和NinjaCMake官网下载Windows安装包安装时勾选“Add CMake to the system PATH”。Ninja可以单独下载也可以直接用CMake自带的。验证cmake --version ninja --versionCMake版本建议3.20以上因为STM32CubeMX生成的CMake工程用了一些较新的特性。Ninja版本无所谓1.10以上都行。3.3 安装OpenOCDOpenOCD在Windows下没有官方安装包推荐用xPack的版本。下载解压后把bin目录加到PATH。验证openocd --versionOpenOCD的配置文件在share/openocd/scripts目录下里面有各种调试器和芯片的配置文件。比如ST-Link的配置是interface/stlink.cfgSTM32F1的配置是target/stm32f1x.cfg。这些文件在后面的调试配置里会用到。3.4 安装VSCode插件在VSCode里装这几个插件C/CMicrosoft出品提供代码补全、跳转、错误提示CMake Tools提供CMake工程的配置、构建、调试集成Cortex-Debug提供STM32的调试支持ARM Assembly提供汇编语法高亮看启动文件的时候有用C/C插件的c_cpp_properties.json需要配置include路径否则代码补全会有大量红色波浪线。这个文件可以手动写也可以让CMake Tools自动生成。我一般是在.vscode目录下放一个c_cpp_properties.json把GCC的include路径和工程的头文件路径都加进去。3.5 用STM32CubeMX生成工程骨架打开STM32CubeMX选好芯片型号比如STM32F103C8T6配置好时钟、GPIO、外设然后在Project Manager里Toolchain/IDE选CMake勾选“Generate peripheral initialization as a pair of .c/.h files”设置好工程名称和路径生成之后你会得到一个包含CMakeLists.txt、Core/、Drivers/、Startup/等目录的工程。这个工程可以直接用CMake配置和构建。有一个细节CubeMX生成的CMakeLists.txt默认用的是arm-none-eabi-gcc但路径可能需要手动指定。你可以在CMakeLists.txt里加一行set(CMAKE_C_COMPILER arm-none-eabi-gcc) set(CMAKE_CXX_COMPILER arm-none-eabi-g) set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)或者在调用CMake的时候通过-DCMAKE_C_COMPILERarm-none-eabi-gcc指定。3.6 配置VSCode的构建任务在.vscode目录下创建tasks.json定义一个构建任务{ version: 2.0.0, tasks: [ { label: Build STM32, type: shell, command: cmake, args: [ --build, ${workspaceFolder}/build, --config, Debug ], group: { kind: build, isDefault: true }, problemMatcher: [$gcc] } ] }然后在终端里先执行一次配置cmake -B build -G Ninja -DCMAKE_BUILD_TYPEDebug再执行构建cmake --build build如果一切正常你会在build目录下看到.elf、.hex、.bin文件。.elf用于调试.hex和.bin用于烧录。3.7 配置OpenOCD和Cortex-Debug在.vscode目录下创建launch.json{ version: 0.2.0, configurations: [ { name: Debug STM32, type: cortex-debug, request: launch, servertype: openocd, cwd: ${workspaceFolder}, executable: ${workspaceFolder}/build/你的工程名.elf, device: STM32F103C8, configFiles: [ interface/stlink.cfg, target/stm32f1x.cfg ], svdFile: ${workspaceFolder}/STM32F103.svd, runToEntryPoint: main, preLaunchTask: Build STM32 } ] }几个关键点executable指向你的elf文件路径要对device填你的芯片型号Cortex-Debug会根据这个找对应的SVD文件configFiles里第一个是调试器配置第二个是芯片配置svdFile是可选的但强烈建议加上这样调试的时候能看到外设寄存器的值preLaunchTask指向前面定义的构建任务这样每次调试前会自动编译SVD文件可以从ST官网或者Keil的安装目录里找也可以从cmsis-svd仓库下载。放到工程目录下路径写对就行。3.8 第一次下载和调试把ST-Link插上板子供电然后在VSCode里按F5。如果一切正常你会看到程序被下载到芯片停在main函数入口左侧出现调用栈、变量、外设寄存器面板如果OpenOCD报错“no device found”大概率是ST-Link的驱动问题。解决办法下载Zadig在Zadig里选择ST-Link的USB设备把驱动替换成WinUSB替换之后OpenOCD就能正常识别ST-Link了。注意替换驱动后ST官方的STM32CubeProgrammer可能无法识别ST-Link如果你还需要用CubeProgrammer可以换一个ST-Link或者用另一台电脑。4. 调试与烧录的进阶配置4.1 用OpenOCD命令行单独烧录有时候你不想启动完整的调试会话只想把程序烧进去。可以用OpenOCD命令行openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c program build/你的工程名.elf verify reset exit这条命令会烧录、校验、复位、退出。适合在脚本里做自动化烧录。4.2 用STM32CubeProgrammer CLI烧录如果你装了STM32CubeCLT里面有一个STM32_Programmer_CLI也可以用来烧录STM32_Programmer_CLI -c portSWD -w build/你的工程名.hex -v -rst这个工具的好处是它用的是ST官方驱动不需要替换成WinUSB。缺点是它不能用于调试只能烧录。4.3 串口输出的配置调试的时候经常需要看串口输出。VSCode里可以用Serial Monitor插件或者直接用Cortex-Debug的SWO功能如果你的芯片支持。SWO是Cortex-M3/M4/M7特有的单线输出可以替代UART做printf输出。配置方法是在launch.json里加swoConfig: { enabled: true, source: probe, swoFrequency: 2000000, cpuFrequency: 72000000, decoders: [ { type: console, label: ITM, port: 0 } ] }然后在代码里用ITM_SendChar输出。SWO的好处是不占用UART缺点是配置稍微麻烦一点而且不是所有ST-Link都支持。4.4 多工程和库的管理实际项目里通常会有多个工程共享一些驱动库。CMake的add_subdirectory可以很好地处理这种情况。你可以把公共驱动放在一个单独的目录里每个工程通过add_subdirectory引入。比如add_subdirectory(${CMAKE_SOURCE_DIR}/../common_drivers ${CMAKE_BINARY_DIR}/common_drivers) target_link_libraries(${PROJECT_NAME} common_drivers)这样公共驱动只编译一次多个工程共享。比Keil里每个工程复制一份驱动文件要清爽得多。5. 常见问题与排查技巧实录5.1 编译报错“undefined reference to _sbrk”这是newlib的syscall桩函数缺失导致的。GCC的newlib需要你实现一些底层函数比如_sbrk、_write、_read、_close等。最简单的解决办法是在工程里加一个syscalls.c里面提供这些函数的空实现。STM32CubeMX生成的工程一般会自带这个文件如果没有可以从网上找一个模板加进去。5.2 程序下载后不运行几个常见原因启动文件不对GCC用的启动文件和Keil的不一样确认你用的是startup_stm32f103xb.s这种GCC版本的启动文件链接脚本不对确认.ld文件里的Flash和RAM地址和你的芯片匹配复位向量不对检查.ld文件里的.isr_vector段是否正确放置时钟配置不对如果用了外部晶振但板子上没有程序会卡在时钟初始化排查方法用调试器单步执行看程序停在哪里。如果停在HardFault_Handler大概率是时钟或者内存访问问题。5.3 OpenOCD连接不稳定ST-Link的SWD线太长或者质量差会导致连接不稳定。尽量用短的杜邦线SWCLK和SWDIO最好双绞。如果还是不稳定可以降低SWD时钟频率openocd -f interface/stlink.cfg -c adapter speed 1000 -f target/stm32f1x.cfgadapter speed的单位是kHz1000就是1MHz。默认可能是4MHz或者更高降低之后稳定性会好很多。5.4 代码补全不工作C/C插件的代码补全依赖compile_commands.json。CMake可以生成这个文件cmake -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDSON然后在.vscode/c_cpp_properties.json里把compileCommands指向这个文件{ configurations: [ { name: STM32, compileCommands: ${workspaceFolder}/build/compile_commands.json, cStandard: c11, cppStandard: c17, intelliSenseMode: gcc-arm } ] }这样代码补全、跳转、错误提示都会准确很多。5.5 常见问题速查表问题现象可能原因解决办法OpenOCD报no device foundST-Link驱动不兼容用Zadig替换为WinUSB编译报undefined reference缺少syscall桩函数添加syscalls.c程序下载后不运行启动文件或链接脚本不对检查GCC版本的启动文件和.ld文件调试时看不到外设寄存器缺少SVD文件在launch.json里指定svdFile代码补全不工作缺少compile_commands.jsonCMake加-DCMAKE_EXPORT_COMPILE_COMMANDSONSWO无输出ST-Link不支持或配置错误检查swoConfig和芯片是否支持SWO编译速度慢用了Makefile而不是Ninja换Ninja生成器浮点数printf输出异常newlib配置问题加-u _printf_float链接选项5.6 几个我踩过的坑第一个坑是ST-Link的驱动。我一开始用Zadig替换了驱动结果STM32CubeProgrammer用不了了。后来发现其实可以保留ST官方驱动只要在OpenOCD的配置文件里指定transport select hla_swd而不是默认的swd就能兼容。不过这个和OpenOCD版本有关新版本的OpenOCD对ST官方驱动的支持更好了。第二个坑是CMake的构建类型。CubeMX生成的CMakeLists.txt默认没有设置CMAKE_BUILD_TYPE导致编译选项里没有-Og和-g调试的时候看不到变量值。解决办法是在CMakeLists.txt里加if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif()第三个坑是SVD文件的版本。不同型号的STM32对应的SVD文件不一样用错了会导致外设寄存器显示不正确。最好从ST官网下载对应型号的SVD或者从CubeMX的安装目录里找。6. 从点亮LED到实际项目的扩展6.1 用CMake组织多文件工程实际项目不可能只有一个main.c。用CMake组织多文件工程的时候推荐按功能模块划分目录project/ ├── Core/ │ ├── Inc/ │ └── Src/ ├── Drivers/ │ ├── STM32F1xx_HAL_Driver/ │ └── CMSIS/ ├── App/ │ ├── Inc/ │ └── Src/ ├── Bsp/ │ ├── Inc/ │ └── Src/ └── CMakeLists.txt在顶层CMakeLists.txt里用add_subdirectory引入各个模块每个模块有自己的CMakeLists.txt。这样模块之间的依赖关系清晰编译的时候也容易做增量编译。6.2 集成FreeRTOSFreeRTOS的源码可以直接加到工程里用CMake编译。关键是要把FreeRTOS的portable目录里对应编译器的移植文件加进去。对于GCC和Cortex-M3/M4用的是portable/GCC/ARM_CM3或ARM_CM4F。在CMakeLists.txt里add_library(freertos_config INTERFACE) target_include_directories(freertos_config INTERFACE ${CMAKE_SOURCE_DIR}/FreeRTOS/include) target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/FreeRTOS/portable/GCC/ARM_CM4F/port.c ${CMAKE_SOURCE_DIR}/FreeRTOS/portable/MemMang/heap_4.c )FreeRTOS的FreeRTOSConfig.h需要根据你的芯片和需求配置比如configCPU_CLOCK_HZ、configTICK_RATE_HZ、configTOTAL_HEAP_SIZE等。6.3 用Git做版本管理这套环境的一个大优势是Git友好。.gitignore里排除build/目录和.vscode里的个人配置只保留tasks.json和launch.json的模板。CubeMX生成的代码和用户代码分开CubeMX重新生成的时候不会覆盖用户代码。.gitignore示例build/ .vscode/ipch/ .vscode/*.log *.o *.elf *.hex *.bin6.4 在CI里做自动化编译因为整个工具链都是命令行的所以很容易放进CI。GitHub Actions的配置示例name: Build STM32 on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Install toolchain run: | sudo apt-get install gcc-arm-none-eabi cmake ninja-build - name: Configure run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPEDebug - name: Build run: cmake --build build - name: Upload artifact uses: actions/upload-artifactv3 with: name: firmware path: build/*.elf这样每次push代码CI都会自动编译编译失败会发通知。对于团队协作来说这比每个人本地用Keil编译要可靠得多。6.5 调试技巧用条件断点和数据观察点Cortex-Debug支持条件断点和数据观察点。条件断点可以在满足特定条件时才停下来比如if (counter 100) { // 在这里设条件断点条件写 counter 100 }数据观察点可以在某个变量被读写时停下来对于排查内存越界、野指针特别有用。在VSCode的调试面板里右键变量选择“Break on Value Change”或者“Break on Value Read”。6.6 性能分析用SWO的ITM做时间测量SWO除了输出printf还可以用来做时间测量。在代码里插入ITM-PORT[1].u8 1; // 开始 // 被测代码 ITM-PORT[1].u8 0; // 结束然后在Cortex-Debug的SWO面板里可以看到时间戳。这个方法比用GPIO翻转加示波器要方便而且不占用额外的引脚。7. 一些个人体会和后续可扩展的方向这套环境我用了大概半年多最大的感受是“一次配置长期受益”。刚开始折腾的时候确实花了不少时间但配置好之后写代码、编译、调试、版本管理的体验比Keil好太多。尤其是代码补全和Git集成写代码的效率提升很明显。有一个建议如果你刚开始学STM32可以先从Keil或者STM32CubeIDE入手把基本的外设操作和调试方法学会了再考虑换到VSCode。因为VSCode这套环境涉及的工具链比较多如果对编译、链接、调试的基本原理不熟悉排查问题会比较吃力。后续还可以扩展的方向一是把单元测试集成进来用Unity或者CMocka对纯逻辑代码做测试二是把静态分析工具比如cppcheck加到构建流程里三是用J-Link替代ST-LinkJ-Link的调试体验更好但价格也更高。这些等有实际需求的时候再折腾也不迟。
返回列表