新闻详情

新闻详情

首页 / 资讯中心 / 详情

从Keil迁移到VSCode+CMake+GCC:STM32开发环境搭建实战

发布时间:2026/9/28 17:32:32来源:尧图网络
从Keil迁移到VSCode+CMake+GCC:STM32开发环境搭建实战
1. 为什么我要从 Keil 搬到 VSCode 这套组合用了六年 Keil MDK从 STM32F103 到 F407 再到 H7 系列我几乎把它的每一个角落都摸透了。但去年接手一个多平台协作的项目之后我彻底动了换环境的念头。原因很直接Keil 的编辑器体验停留在十年前代码补全慢半拍多文件跳转经常卡顿而且它的工程文件是私有格式团队里用 macOS 和 Linux 的同事根本打不开。每次合并代码.uvprojx文件都要冲突一次手动解决冲突的时间比写业务代码还长。后来我把整个工具链换成了VSCode CMake MinGW arm-none-eabi-gcc这套组合。换完之后最大的感受是工程结构终于变成了纯文本谁都能看懂谁都能改Git 合并再也不用提心吊胆。编辑器响应速度、代码补全、全局搜索这些日常操作体验提升了一个档次。更重要的是这套工具链是跨平台的Windows、macOS、Linux 上用的是同一套 CMake 脚本团队协作的摩擦直接降到了零。不过我得先说清楚这套方案不是没有代价的。Keil 帮你把编译器、链接脚本、启动文件、调试配置全部打包好了你点一下按钮就能编译下载。换成 VSCode 这套之后这些配置全部要你自己写。第一次搭环境的时候我踩了至少七八个坑从 MinGW 和 arm-none-eabi-gcc 搞混到 CMake 找不到编译器再到链接脚本路径写错导致程序跑飞每一个坑都花了不少时间排查。所以这篇内容我会把整个搭建过程拆开讲重点放在那些容易出错的地方让你少走弯路。这篇文章适合两类人一是已经会用 Keil 但想换到更现代开发环境的嵌入式工程师二是刚接触 STM32想从一开始就用一套更通用、更接近实际工程实践的工具链来学习的新手。如果你完全没写过单片机代码建议先了解一下 GPIO、时钟、中断这些基础概念再来看不然配置过程中的一些术语可能会让你困惑。2. 工具链选型的底层逻辑为什么是这四个东西2.1 MinGW 和 arm-none-eabi-gcc 到底有什么区别这是我在搭建过程中遇到的第一个、也是最容易搞混的问题。很多人看到教程里说安装 MinGW就以为 MinGW 是用来编译 STM32 代码的。这个理解是错的。MinGW 的全称是 Minimalist GNU for Windows它提供的是x86/x64 架构的 Windows 本地编译工具链。也就是说你用 MinGW 里的 gcc 编译出来的程序是跑在 Windows 上的.exe文件。它的作用是在你的电脑上提供一个类 Unix 的编译环境让 CMake 能够正常工作同时也能编译一些辅助工具。而真正用来编译 STM32 代码的是arm-none-eabi-gcc。这是一个交叉编译器它运行在你的 Windows 电脑上但生成的机器码是给 ARM Cortex-M 内核用的。none表示它不针对特定的操作系统eabi表示它遵循 ARM 嵌入式应用二进制接口。所以完整的编译链路是这样的CMake 读取CMakeLists.txt调用 arm-none-eabi-gcc 把 C 源文件编译成 ARM 目标文件再调用 arm-none-eabi-ld 链接成.elf最后用 arm-none-eabi-objcopy 转成.hex或.bin。MinGW 在这个过程中扮演的是基础设施的角色提供 CMake 运行所需的环境和 make 工具。注意如果你只装了 MinGW 没装 arm-none-eabi-gccCMake 配置阶段可能不会报错但编译时会提示找不到编译器或者更隐蔽地——用 MinGW 的 gcc 去编译 ARM 代码生成一堆 x86 指令烧进去直接跑飞。2.2 CMake 在嵌入式项目里到底解决了什么问题很多人觉得 CMake 是给大型 C 项目用的单片机项目就那么几个文件用 Makefile 就够了。我一开始也这么想直到我的项目从 5 个源文件涨到了 40 多个还引入了 FreeRTOS、FatFS、USB 库之后手写 Makefile 变成了一场噩梦。CMake 的核心价值在于用声明式的方式描述工程结构。你不需要告诉它先编译 A 再编译 B你只需要告诉它这个可执行文件由哪些源文件组成需要包含哪些头文件路径需要链接哪些库。剩下的依赖关系、编译顺序、增量编译CMake 会自动处理。更重要的是CMake 支持工具链文件toolchain file。你可以把所有跟交叉编译相关的配置——编译器路径、编译选项、链接选项——全部写在一个.cmake文件里。这样你的主CMakeLists.txt就是纯逻辑描述跟具体平台无关。换一个芯片只需要换工具链文件和链接脚本工程结构完全不用动。2.3 VSCode 的角色它不只是编辑器VSCode 在这套方案里承担了三个角色代码编辑器、调试前端、任务调度器。作为编辑器它的 IntelliSense 引擎通过读取compile_commands.jsonCMake 可以自动生成来提供精准的代码补全和跳转。这个文件记录了每个源文件编译时用的所有参数包括头文件搜索路径和宏定义。所以只要 CMake 配置正确VSCode 的补全就是准确的不会出现找不到头文件的红色波浪线。作为调试前端VSCode 通过 Cortex-Debug 插件调用 OpenOCD 或 J-Link GDB Server把 GDB 的调试信息可视化。你可以设断点、看变量、单步执行体验跟 Keil 的调试器差不多但界面更清爽。作为任务调度器你可以把编译、烧录、清理这些操作配置成 task用快捷键一键触发。配合 CMake Tools 插件甚至可以在底栏直接切换 Debug/Release 配置、选择目标芯片。2.4 这套组合的代价与收益对比对比项Keil MDKVSCode CMake GCC初始搭建时间安装即用10 分钟首次配置约 1-2 小时编辑器体验一般补全慢优秀补全快且准跨平台仅 WindowsWindows/macOS/Linux工程文件格式私有 XML纯文本 CMakeGit 协作冲突频繁几乎无冲突编译器优化ARMCC收费GCC免费开源调试体验好好需额外配置社区支持商业支持开源社区资料丰富代码体积略小略大可优化从表格能看出来这套方案的主要成本在初始搭建阶段。一旦搭好后续的开发效率提升是持续的。而且因为所有配置都是文本文件你可以把它做成模板下一个项目直接复制粘贴五分钟就能开工。3. 环境搭建的完整操作链路3.1 安装顺序很关键先装什么后装什么我踩过的第一个坑就是安装顺序搞错了。第一次搭的时候我先装了 VSCode然后装 CMake最后装 MinGW结果 CMake 一直找不到 make 工具。后来才明白MinGW 必须最先装并且要把它的 bin 目录加到系统 PATH 里这样后续安装的 CMake 才能自动检测到可用的构建工具。正确的安装顺序是MinGW提供 gcc、g、make、mingw32-makeCMake提供 cmake 命令行工具arm-none-eabi-gcc提供 ARM 交叉编译器OpenOCD提供调试和烧录服务VSCode编辑器最后装装完再配置插件每一步装完之后都要打开一个新的命令行窗口验证。注意是新的窗口因为 PATH 环境变量的修改不会影响已经打开的终端。验证命令如下# 验证 MinGW gcc --version mingw32-make --version # 验证 CMake cmake --version # 验证 ARM 交叉编译器 arm-none-eabi-gcc --version # 验证 OpenOCD openocd --version如果任何一个命令提示不是内部或外部命令说明 PATH 没配好。回到系统环境变量设置里检查确保每个工具的bin目录都加进去了。3.2 MinGW 安装中最容易出错的 PATH 配置MinGW 的安装本身很简单下载安装器选好组件点下一步就行。但 PATH 配置这一步我见过太多人出错。问题通常出在两个地方一是加错了目录二是加了多个版本的 MinGW 导致冲突。正确的做法是只把 MinGW 安装目录下的bin文件夹加到 PATH 的最前面。比如你装在C:\mingw64那就加C:\mingw64\bin。不要加C:\mingw64本身也不要加C:\mingw64\lib。如果你之前装过其他版本的 MinGW比如 Dev-C 自带的、CodeBlocks 自带的一定要把它们从 PATH 里删掉。多个版本的 gcc 混在一起会出现编译能过但链接报错的诡异问题。我遇到过最离谱的一次是编译出来的程序在开发机上跑得好好的换一台电脑就崩溃排查了半天才发现是链接了错误版本的运行库。提示在命令行里执行where gcc可以列出所有在 PATH 中找到的 gcc。如果输出了多个路径说明有冲突需要清理。3.3 arm-none-eabi-gcc 的版本选择与验证arm-none-eabi-gcc 的版本选择有个原则不要盲目追新也不要用过老的版本。太新的版本可能对某些老芯片的支持有变化太老的版本又缺少一些现代 C 标准的支持。我目前用的是 10.3 版本对 STM32 全系列支持都很稳定。下载的时候注意选对文件名Windows 平台要选gcc-arm-none-eabi-10.3-2021.10-win32.exe这种带win32的。虽然你的系统是 64 位的但这个工具链本身是 32 位程序在 64 位 Windows 上运行完全没问题。安装过程中有一个选项要特别注意Add path to environment variable。一定要勾上。如果忘了勾就手动把安装目录下的bin文件夹加到 PATH 里。验证的时候除了arm-none-eabi-gcc --version还要确认它真的能编译 ARM 代码。可以写一个最简单的测试文件// test.c int main(void) { volatile int a 1; volatile int b 2; return a b; }然后用arm-none-eabi-gcc -c test.c -o test.o编译。如果生成了test.o再用arm-none-eabi-objdump -d test.o反汇编看到的是 ARM 指令而不是 x86 指令就说明工具链没问题。3.4 CMake 安装后的第一个验证生成一个最小工程CMake 安装完之后不要急着去配 STM32 工程。先用一个最简单的 C 程序验证 CMake 本身能不能正常工作。创建一个文件夹里面放两个文件# CMakeLists.txt cmake_minimum_required(VERSION 3.20) project(HelloCMake C) add_executable(hello main.c)// main.c #include stdio.h int main(void) { printf(Hello CMake\n); return 0; }然后在文件夹里打开命令行执行mkdir build cd build cmake -G MinGW Makefiles .. mingw32-make如果一切正常会在build目录下生成hello.exe。运行它输出Hello CMake。这一步的意义在于确认 CMake 能找到 MinGW 的编译器确认 make 工具能正常工作。如果这一步就失败了那后面配 STM32 工程肯定也跑不通先在这里把问题解决掉。常见的错误是CMake Error: Could not create named generator MinGW Makefiles这通常是因为 MinGW 的bin目录没有加到 PATH或者加错了位置。另一个常见错误是cmake : 无法将cmake项识别为 cmdlet...这是 PowerShell 的报错说明 CMake 没装好或者 PATH 没生效。4. STM32 工程结构的搭建与 CMakeLists 编写4.1 从零组织一个可维护的工程目录Keil 的工程结构是扁平的所有文件堆在一个列表里文件多了之后很难管理。用 CMake 的时候我建议从一开始就采用分层目录结构project/ ├── CMakeLists.txt # 顶层 CMake 脚本 ├── cmake/ │ ├── toolchain.cmake # 交叉编译工具链配置 │ └── stm32f103.cmake # 芯片相关的编译选项 ├── src/ │ ├── main.c │ ├── system_stm32f1xx.c │ └── stm32f1xx_it.c ├── drivers/ │ ├── CMSIS/ # CMSIS 核心文件 │ └── STM32F1xx_HAL_Driver/ # HAL 库 ├── startup/ │ └── startup_stm32f103xb.s # 启动文件 ├── linker/ │ └── STM32F103XB_FLASH.ld # 链接脚本 └── build/ # 编译输出目录不纳入版本控制这个结构的好处是芯片相关的文件启动文件、链接脚本放在独立目录换芯片的时候只需要替换这几个文件驱动库独立存放升级 HAL 库不会影响业务代码build目录完全由 CMake 生成可以随时删掉重新来。4.2 toolchain.cmake 里必须写对的几个变量工具链文件是整套配置的核心。它告诉 CMake不要用你默认的 gcc用我指定的交叉编译器。# cmake/toolchain.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_CXX_COMPILER ${TOOLCHAIN_PREFIX}g) set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}size) # 避免 CMake 尝试编译可执行文件来检测编译器 set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)这里有几个关键点CMAKE_SYSTEM_NAME设为Generic表示这是一个裸机系统没有操作系统。如果不设这个CMake 会默认按宿主系统Windows来配置导致链接时找不到main之外的系统库。CMAKE_TRY_COMPILE_TARGET_TYPE设为STATIC_LIBRARY是一个很关键的技巧。CMake 在配置阶段会尝试编译一个测试程序来验证编译器是否可用。对于交叉编译器来说这个测试程序链接时会失败因为找不到链接脚本和启动文件。设为STATIC_LIBRARY后CMake 只编译不链接就能顺利通过检测。4.3 链接脚本和启动文件的路径处理链接脚本.ld文件和启动文件.s文件的路径处理是另一个容易出错的地方。CMake 里路径可以用绝对路径也可以用相对路径但我强烈建议用相对于顶层 CMakeLists.txt 的相对路径这样整个工程可以随意移动不会因为路径变化而失效。在顶层CMakeLists.txt里这样写# 链接脚本 set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/linker/STM32F103XB_FLASH.ld) # 启动文件 set(STARTUP_FILE ${CMAKE_SOURCE_DIR}/startup/startup_stm32f103xb.s) # 链接选项 target_link_options(${PROJECT_NAME} PRIVATE -T${LINKER_SCRIPT} -Wl,-Map${PROJECT_NAME}.map -Wl,--gc-sections --specsnano.specs --specsnosys.specs )--specsnano.specs表示使用 newlib-nano这是一个精简版的 C 运行库适合单片机这种资源受限的环境。--specsnosys.specs表示不使用系统调用因为裸机环境没有操作系统_write、_sbrk这些系统调用需要自己实现或者用空实现。-Wl,--gc-sections让链接器丢弃没有被引用的代码段可以显著减小最终固件的大小。配合编译选项-ffunction-sections -fdata-sections使用效果更好。4.4 编译选项的取舍优化等级与调试信息编译选项直接影响代码的性能和可调试性。我在实际项目中总结了一套比较平衡的配置target_compile_options(${PROJECT_NAME} PRIVATE -mcpucortex-m3 -mthumb -Wall -Wextra -Og # Debug 配置用 -Og -g3 # 最详细的调试信息 -ffunction-sections -fdata-sections )-Og是 GCC 专门为调试优化的等级它在保持代码可调试性的同时做了一些基本的优化。比-O0生成的代码小又不像-O2那样会把变量优化掉导致断点看不清楚。-g3生成最详细的调试信息包括宏定义。这样在 GDB 里可以用macro expand查看宏展开调试的时候很方便。Release 配置则换成-O2或-Os优化体积去掉-g加上-DNDEBUG关闭断言。注意不要用-O3。在嵌入式环境里-O3的激进优化有时会导致时序问题特别是涉及硬件寄存器操作的时候。我遇到过-O3把一段延时循环优化没了的情况排查了很久才发现是优化等级的问题。5. VSCode 配置与调试链路打通5.1 必装插件清单与配置要点VSCode 本身只是一个编辑器所有功能都靠插件。这套方案需要装的插件不多但每一个都很关键C/CMicrosoft 出品提供 IntelliSense、代码跳转、错误提示CMake提供 CMakeLists.txt 的语法高亮和补全CMake Tools提供 CMake 的配置、构建、调试集成Cortex-Debug提供 ARM Cortex-M 的调试支持ARM Assembly提供汇编文件的语法高亮装完插件之后最关键的一步是让 C/C 插件找到compile_commands.json。CMake 在配置时加上-DCMAKE_EXPORT_COMPILE_COMMANDSON就会在 build 目录生成这个文件。然后在 VSCode 的settings.json里加上{ C_Cpp.default.compileCommands: ${workspaceFolder}/build/compile_commands.json, cmake.buildDirectory: ${workspaceFolder}/build, cmake.generator: MinGW Makefiles }这样 C/C 插件就知道每个源文件的编译参数补全和跳转就准确了。如果没有这个文件插件会用自己的默认规则去猜头文件路径经常猜错导致满屏红色波浪线。5.2 launch.json 与 OpenOCD 的对接细节调试配置是整套方案里最复杂的部分。你需要一个launch.json告诉 VSCode 怎么启动调试会话。{ version: 0.2.0, configurations: [ { name: STM32 Debug, type: cortex-debug, request: launch, servertype: openocd, cwd: ${workspaceFolder}, executable: ${workspaceFolder}/build/${workspaceFolderBasename}.elf, device: STM32F103C8, configFiles: [ interface/stlink.cfg, target/stm32f1x.cfg ], svdFile: ${workspaceFolder}/STM32F103.svd, runToEntryPoint: main, preLaunchTask: CMake Build } ] }几个关键字段的解释executable指向编译生成的.elf文件这是调试器读取符号信息的来源。路径必须写对否则调试器启动后会提示找不到符号。configFiles指定 OpenOCD 的配置文件。interface/stlink.cfg是调试器的接口配置如果你用的是 J-Link就换成interface/jlink.cfg。target/stm32f1x.cfg是目标芯片的配置F4 系列换成target/stm32f4x.cfg。svdFile指向芯片的 SVD 文件这个文件描述了所有外设寄存器的地址和位定义。有了它调试的时候可以在 VSCode 里直接查看外设寄存器的值不用手动去算地址。SVD 文件可以从芯片厂商的官网或者开源仓库下载。preLaunchTask指定调试前自动执行的任务通常是编译。这样你按 F5 的时候它会先编译再启动调试不用手动切换。5.3 烧录任务的配置与一键下载除了调试日常开发中更常用的是烧录。可以配置一个 task 来实现一键下载{ version: 2.0.0, tasks: [ { label: Flash, type: shell, command: openocd, args: [ -f, interface/stlink.cfg, -f, target/stm32f1x.cfg, -c, program ${workspaceFolder}/build/${workspaceFolderBasename}.elf verify reset exit ], problemMatcher: [] } ] }配置好之后按CtrlShiftB就能触发烧录。verify表示烧录后校验reset表示烧录完复位芯片exit表示烧录完关闭 OpenOCD。这三个参数建议都加上能避免很多烧录了但没跑起来的问题。5.4 调试时变量看不到的常见原因调试过程中最常见的问题是断点停了但变量显示optimized out或者值不对。这通常是三个原因造成的第一编译优化等级太高。-O2会把很多局部变量优化到寄存器里GDB 读不到。解决办法是调试时用-Og或-O0。第二变量被声明为volatile但编译器还是优化了。这种情况一般不会发生但如果发生了检查一下是不是在中断和主循环之间共享的变量忘了加volatile。第三调试信息不完整。确保编译时加了-g3链接时没有 strip 掉调试信息。有些工程的 Release 配置会自动 strip调试的时候要切回 Debug 配置。6. 那些让我熬夜排查的坑与修复方案6.1 CMake 报错找不到编译器PATH 与工具链文件的双重检查这个错误我遇到过三次每次原因都不一样。第一次是 PATH 没配好。arm-none-eabi-gcc装了但bin目录没加到系统 PATH 里。CMake 配置的时候报No CMAKE_C_COMPILER could be found。解决办法就是把工具链的bin目录加到 PATH然后重启 VSCode。注意是重启 VSCode不是重新打开终端。VSCode 启动时会读取一次环境变量之后在它内部打开的终端虽然能读到新的 PATH但 CMake Tools 插件用的还是启动时的旧环境。第二次是工具链文件路径写错了。CMAKE_TOOLCHAIN_FILE指向了一个不存在的文件CMake 静默忽略了这个参数然后用默认的编译器去配置自然找不到。解决办法是在CMakePresets.json或者命令行里明确指定工具链文件的绝对路径配置成功后检查CMakeCache.txt里的CMAKE_C_COMPILER变量确认指向的是arm-none-eabi-gcc而不是gcc。第三次最隐蔽工具链文件里CMAKE_C_COMPILER写的是arm-none-eabi-gcc但系统 PATH 里同时存在 MinGW 的gcc。CMake 在检测编译器时先找到了 MinGW 的gcc因为名字匹配上了都是 gcc就没去用工具链文件里指定的。解决办法是在工具链文件里写绝对路径set(CMAKE_C_COMPILER C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin/arm-none-eabi-gcc.exe)6.2 链接时报 undefined reference to _sbrk 的根因这个错误几乎每个从 Keil 转到 GCC 的人都会遇到。原因是 newlib 的malloc和printf需要底层系统调用支持而裸机环境没有操作系统_sbrk、_write、_read、_close这些函数没有实现。最简单的解决办法是加上--specsnosys.specs链接选项它提供了一套空的系统调用实现。但这样printf就没有输出了因为_write是空的。如果你需要printf输出到串口就得自己实现_write#include sys/stat.h #include unistd.h int _write(int file, char *ptr, int len) { (void)file; for (int i 0; i len; i) { // 假设 USART1 已经初始化 while (!(USART1-SR USART_SR_TXE)); USART1-DR ptr[i] 0xFF; } return len; }_sbrk是给malloc用的如果你不用动态内存分配可以用--specsnosys.specs里的空实现。如果要用malloc就得自己管理堆空间extern char _end; extern char _estack; static char *heap_end _end; void *_sbrk(int incr) { char *prev heap_end; if (heap_end incr _estack) { return (void *)-1; } heap_end incr; return prev; }注意_end和_estack是在链接脚本里定义的符号分别表示数据段的结束和栈顶。用之前要确认链接脚本里有这两个符号。6.3 程序烧进去不跑中断向量表和启动模式排查程序烧录成功但芯片没反应这是最让人头疼的问题。我总结了一个排查顺序第一步确认启动模式。STM32 的 BOOT0 和 BOOT1 引脚决定了从哪里启动。如果 BOOT0 接高电平芯片会从系统存储器启动而不是从 Flash 启动。检查一下板子上的跳线帽确保 BOOT0 接地。第二步确认中断向量表的位置。链接脚本里.isr_vector段必须放在 Flash 的最前面地址0x08000000。如果链接脚本写错了向量表被放到了别的位置芯片上电后取不到复位向量就不会执行你的代码。第三步确认启动文件里的Reset_Handler正确调用了SystemInit和__libc_init_array。SystemInit配置时钟__libc_init_array初始化 C 运行库包括全局变量和静态变量的初始化。如果少了__libc_init_array全局变量的初始值可能不对。第四步用调试器单步执行。在Reset_Handler的第一条指令设断点看能不能停下来。如果能停说明向量表没问题继续往下单步看在哪一步跑飞。如果不能停说明向量表或者启动模式有问题。6.4 浮点运算异常与编译选项的隐藏关联STM32F4 系列有硬件浮点单元FPU但默认情况下 GCC 不会启用它。如果你写了浮点运算代码编译器会用软件模拟速度慢不说还可能因为链接了错误的库导致异常。启用硬件浮点需要两个条件编译选项和链接选项都要加对应的参数。# 对于 Cortex-M4F target_compile_options(${PROJECT_NAME} PRIVATE -mfpufpv4-sp-d16 -mfloat-abihard ) target_link_options(${PROJECT_NAME} PRIVATE -mfpufpv4-sp-d16 -mfloat-abihard )-mfloat-abihard表示使用硬件浮点调用约定函数参数通过浮点寄存器传递。如果只加了编译选项没加链接选项链接时会报错因为编译器生成的浮点调用和链接库的调用约定不一致。还有一个坑如果你用了 FreeRTOS任务切换时需要保存浮点寄存器。FreeRTOS 的移植层需要配置configENABLE_FPU为 1否则任务切换后浮点寄存器的值会丢失导致浮点运算结果随机出错。7. 从 Keil 工程迁移到 CMake 的实操建议7.1 源文件与头文件路径的批量整理Keil 工程里的文件路径是相对于.uvprojx文件的迁移到 CMake 的时候需要重新组织。我的做法是先把所有源文件按功能分类放到src、drivers、middlewares这几个目录下然后在 CMakeLists.txt 里用file(GLOB_RECURSE)批量收集。file(GLOB_RECURSE SOURCES ${CMAKE_SOURCE_DIR}/src/*.c ${CMAKE_SOURCE_DIR}/drivers/STM32F1xx_HAL_Driver/Src/*.c ) file(GLOB_RECURSE HEADERS ${CMAKE_SOURCE_DIR}/src/*.h ${CMAKE_SOURCE_DIR}/drivers/STM32F1xx_HAL_Driver/Inc/*.h )不过file(GLOB_RECURSE)有个缺点新增文件后 CMake 不会自动重新配置需要手动触发一次cmake ..。如果你经常增删文件建议显式列出所有源文件或者用CONFIGURE_DEPENDS选项让 CMake 每次构建时检查文件变化。7.2 宏定义与编译选项的对应关系Keil 工程里的宏定义在Options for Target的C/C选项卡里迁移到 CMake 的时候要逐个对应过来。常见的几个Keil 里的宏CMake 里的写法作用USE_HAL_DRIVERtarget_compile_definitions(... USE_HAL_DRIVER)启用 HAL 库STM32F103xBtarget_compile_definitions(... STM32F103xB)指定芯片型号DEBUGtarget_compile_definitions(... DEBUG)启用调试代码__FPU_PRESENTtarget_compile_definitions(... __FPU_PRESENT1)启用 FPU这些宏定义直接影响头文件里的条件编译。如果漏了STM32F103xBstm32f1xx.h就不知道要包含哪个型号的寄存器定义编译会报错。7.3 迁移后的验证清单迁移完成后不要急着写新功能先做一轮完整的验证编译能通过没有 warning至少没有新增 warning生成的.elf文件大小跟 Keil 版本接近差异在 10% 以内算正常烧录后 LED 闪烁程序能正常运行串口输出正常printf能打印中断能正常触发用定时器中断测试调试器能设断点、看变量、单步执行浮点运算结果正确如果有用到这七项都通过了说明迁移基本成功。之后就是逐步把业务代码从 Keil 工程搬过来每搬一个模块就验证一次不要一次性全搬完再测出了问题很难定位。8. 日常开发中的效率技巧与个人体会8.1 用 CMake Presets 简化配置命令每次配置都要敲一长串cmake -G MinGW Makefiles -DCMAKE_TOOLCHAIN_FILE... -DCMAKE_BUILD_TYPEDebug ..很烦。CMake 3.19 之后引入了 Presets 功能可以把这些配置写在一个 JSON 文件里{ version: 3, configurePresets: [ { name: debug, generator: MinGW Makefiles, binaryDir: ${sourceDir}/build/debug, cacheVariables: { CMAKE_TOOLCHAIN_FILE: ${sourceDir}/cmake/toolchain.cmake, CMAKE_BUILD_TYPE: Debug, CMAKE_EXPORT_COMPILE_COMMANDS: ON } }, { name: release, inherits: debug, binaryDir: ${sourceDir}/build/release, cacheVariables: { CMAKE_BUILD_TYPE: Release } } ] }配置好之后只需要cmake --preset debug和cmake --build --preset debug两条命令就能完成配置和构建。VSCode 的 CMake Tools 插件也能识别 Presets在底栏直接切换。8.2 编译速度优化ccache 与并行构建项目大了之后全量编译可能要几分钟。两个优化手段第一用ccache。它缓存每次编译的结果第二次编译同样的文件时直接读缓存速度能快好几倍。MinGW 环境下可以装一个 ccache 的 Windows 版本然后在工具链文件里配置find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) endif()第二并行构建。mingw32-make -j8可以用 8 个线程并行编译。在 VSCode 的 CMake Tools 配置里加上cmake.parallelJobs: 8或者在命令行里加-j参数。8.3 版本控制中应该忽略哪些文件用 Git 管理工程的时候build目录、.vscode目录里的某些文件不应该提交。我的.gitignore是这样的build/ .vscode/ipch/ .vscode/*.log *.o *.elf *.bin *.hex *.map compile_commands.jsonbuild目录完全由 CMake 生成不需要提交。compile_commands.json也是生成的而且路径跟本机相关提交了反而会导致别人那边补全出错。.vscode目录里的settings.json和launch.json可以提交因为它们是团队共享的配置但ipch和日志文件不用提交。8.4 我在这套环境里最真实的感受用了大半年之后我最大的感受是前期投入的时间完全值得。虽然第一次搭建花了一两个小时中间还踩了不少坑但之后每个新项目只需要复制模板、改一下芯片型号和链接脚本五分钟就能开工。而且因为工程文件是纯文本我可以清楚地看到每一处配置出了问题也知道去哪里找。另一个意外的收获是这套环境让我对编译链接的过程理解更深了。以前用 Keil 的时候编译烧录就是一个按钮我从来不去想背后发生了什么。换成 GCC 之后因为要自己写链接脚本、自己处理系统调用反而把很多以前模糊的概念搞清楚了。比如中断向量表是怎么被链接器放到 Flash 开头的全局变量的初始值是怎么从 Flash 拷贝到 RAM 的堆和栈是怎么在链接脚本里划分的。这些知识在排查一些诡异 bug 的时候特别有用。当然这套方案也不是没有缺点。GCC 编译出来的代码体积确实比 ARMCC 大一些在 Flash 只有 64KB 的 F103C8 上有时候需要开-Os优化才能塞进去。另外GCC 的某些优化行为跟 ARMCC 不一样从 Keil 迁移过来的代码偶尔会出现时序问题需要重新调整延时循环。但总的来说如果你追求的是现代化的开发体验、跨平台协作、以及对工程结构的完全掌控这套方案是目前最合适的选择。Keil 依然是一个优秀的商业工具但它的封闭性和老旧的编辑器体验在今天的开发环境下已经越来越难以接受了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AI 超级智能体全栈项目阶段七:Spring AI 集成 MCP 全攻略:从客户端配置到服务端开发实战(含图片搜索服务案例) 2026/9/28 18:19:19

AI 超级智能体全栈项目阶段七:Spring AI 集成 MCP 全攻略:从客户端配置到服务端开发实战(含图片搜索服务案例)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI Agent框架探秘:拆解 OpenHands 的 Memory 模块与配置实践 2026/9/28 18:19:19

AI Agent框架探秘:拆解 OpenHands 的 Memory 模块与配置实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
别再吹通用型AI Agent了!真实业务都是Workflow:用TaoToken统一Key跑通Prompt chaining 2026/9/28 18:19:19

别再吹通用型AI Agent了!真实业务都是Workflow:用TaoToken统一Key跑通Prompt chaining

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
基于云端OpenClaw的情绪互动机器人系统-Milk-V Duo S + 机器人 端开发(5):用 TaoToken 统一 Key 打通 MQTT 与 HTTP 轮询配置 2026/9/28 18:19:18

基于云端OpenClaw的情绪互动机器人系统-Milk-V Duo S + 机器人 端开发(5):用 TaoToken 统一 Key 打通 MQTT 与 HTTP 轮询配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Claude vs Codex:2026编程5大场景对决,TaoToken统一Key接入实测 2026/9/28 18:19:18

Claude vs Codex:2026编程5大场景对决,TaoToken统一Key接入实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
UltraEdit 配置 TaoToken:settings.json 骨架与 API 通道验证 2026/9/28 18:19:12

UltraEdit 配置 TaoToken:settings.json 骨架与 API 通道验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉