从Keil5迁移到VSCode+GCC:GD32开发环境搭建与实战指南

发布时间:2026/9/23 8:14:04
从Keil5迁移到VSCode+GCC:GD32开发环境搭建与实战指南
1. 为什么我要从 Keil5 迁移到 VSCode GCC1.1 一个用了五年 Keil5 的老用户为什么突然想换我用 Keil5 开发 GD32 差不多有五年时间了。说实话Keil5 对 GD32 的支持确实成熟官方固件库直接带工程模板双击打开就能编译下载新手入门几乎零门槛。但用得越久那种“被绑住”的感觉就越明显。最直接的痛点是编辑器体验。Keil5 的代码编辑器停留在十几年前的水平——没有智能补全、没有函数跳转、没有 Git 集成、没有多光标编辑代码格式化更是想都别想。我平时写 Python 和前端的时候用 VSCode回到 Keil5 写 C 代码那种割裂感就像从智能手机换回功能机。其次是版本管理和团队协作Keil5 的工程文件是二进制格式的.uvprojxGit diff 出来一堆乱码多人协作时合并冲突基本靠吼。再就是跨平台问题我的主力开发机是 Windows但 CI 服务器跑的是 LinuxKeil5 根本没法在 Linux 上跑自动化构建无从谈起。还有一个很现实的问题Keil5 是商业软件虽然网上有各种“注册”方式但公司合规审查越来越严用正版授权的话一套下来价格不便宜。而 GCC 工具链是开源的VSCode 也是免费的整套方案零成本还不用担心版权问题。所以我就动了念头能不能用 VSCode GCC 搭一套 GD32 的开发环境既保留 Keil5 的调试烧录能力又能享受现代编辑器的开发体验折腾了大概两周踩了不少坑最终跑通了。这篇文章就把整个过程完整记录下来包括工具选型、环境搭建、工程配置、调试烧录、常见问题排查希望能帮到有同样想法的朋友。1.2 这套方案到底适合谁不适合谁先说清楚适用人群免得你花时间看完发现不适合自己。适合的人有一定 C 语言基础、用过 Keil5 或 IAR 开发 GD32/STM32 的嵌入式工程师想用现代编辑器提升开发效率的人需要在 Linux 或 macOS 上开发 GD32 的人想搭建自动化构建流水线的人学生或者个人开发者不想花钱买商业 IDE 授权的人。不太适合的人完全零基础、连 GPIO 点灯都没做过的新手——建议先用 Keil5 把 GD32 的基本开发流程跑通再来折腾这套环境项目工期特别紧、没时间折腾工具链的人——Keil5 开箱即用的优势在这种场景下还是很明显的用 GD32 特殊外设且官方只提供 Keil 工程示例的——虽然可以移植但工作量不小。我个人的建议是如果你已经用 Keil5 做过至少一个完整的 GD32 项目对启动文件、链接脚本、中断向量表这些概念有基本认知那这套方案完全值得一试。如果你还在“照着教程点灯”的阶段先别折腾把基础打牢再说。1.3 整体方案架构长什么样在动手之前先把整体架构理清楚这样后面每一步你都知道自己在干什么。整套方案由四部分组成编辑器层用 VSCode负责代码编写、补全、跳转、Git 管理工具链层用 ARM GNU Toolchain负责编译、链接、生成可执行文件构建系统层用 Make 或 CMake负责组织源文件、管理编译规则调试烧录层用 OpenOCD GDB负责连接调试器、下载程序、单步调试。这四层的关系可以这样理解VSCode 是“驾驶舱”你在这里操作GCC 是“发动机”真正干活的是它Make/CMake 是“传动系统”把发动机的动力传到轮子上OpenOCD GDB 是“方向盘和刹车”控制程序往哪跑、什么时候停。和 Keil5 相比Keil5 把这四层全部打包在一起了你只需要点按钮。而这套方案是“组装式”的每一层都可以替换——比如你可以用 IAR 的编译器替换 GCC可以用 J-Link 替换 OpenOCD灵活性高很多但代价是配置工作要自己做。理解了架构接下来就可以动手了。2. 工具选型与安装每一步都告诉你为什么2.1 ARM GNU Toolchain 的版本选择与安装编译 GD32 需要 ARM 架构的交叉编译工具链。这里有个坑要先说网上很多教程让你装gcc-arm-none-eabi这个包在 Ubuntu 的 apt 源里确实有但版本往往很老比如 9.3.0 或者更早而且有些发行版仓库里的包不完整缺少newlib或者gdb。我自己在 Ubuntu 上就遇到过apt install gcc-arm-none-eabi之后编译报错说找不到libc_nano.a的情况。我的建议是直接从 ARM 官方下载独立工具链。搜索“ARM GNU Toolchain Downloads”找到arm-gnu-toolchain-13.x.relx-x86_64-arm-none-eabi这个包版本号会更新选最新的稳定版即可。Windows 用户下载.exe安装包Linux 用户下载.tar.xz压缩包。安装时有个关键选项一定要勾选“Add path to environment variable”Windows或者手动把bin目录加到PATHLinux/macOS。这一步决定了你后面能不能在命令行直接敲arm-none-eabi-gcc。安装完成后验证一下arm-none-eabi-gcc --version arm-none-eabi-gdb --version arm-none-eabi-objcopy --version三个命令都能输出版本号说明工具链装好了。如果提示“command not found”检查 PATH 配置。Linux 下可以在~/.bashrc里加一行export PATH$PATH:/opt/arm-gnu-toolchain/bin然后source ~/.bashrc生效。注意如果你之前装过系统仓库里的gcc-arm-none-eabi建议先卸载避免 PATH 里两个版本打架。我遇到过gcc --version显示 13.x 但实际编译用的是 9.3.0 的情况就是 PATH 顺序问题。2.2 VSCode 及核心插件配置VSCode 从官网下载安装就行这一步没什么好说的。重点说插件。必装插件C/CMicrosoft 出品提供智能补全、跳转、错误提示。这是核心插件必装。Cortex-Debug专门用于 ARM Cortex-M 调试配合 OpenOCD 使用。EIDEEmbedded IDE这个插件是国人开发的专门针对嵌入式开发支持 GD32、STM32 等芯片的工程管理可以导入 Keil 工程非常方便。推荐插件GitLensGit 增强看每行代码是谁改的。Better Comments注释高亮区分 TODO、FIXME 等。Hex Editor查看二进制文件。装完 C/C 插件后需要配置c_cpp_properties.json让 IntelliSense 知道你的头文件路径和宏定义。这个文件在.vscode目录下内容大概长这样{ configurations: [ { name: GD32, includePath: [ ${workspaceFolder}/**, ${workspaceFolder}/Firmware/GD32F10x_standard_peripheral/Include, ${workspaceFolder}/Firmware/CMSIS/GD/GD32F10x/Include ], defines: [ GD32F10X_HD, USE_STDPERIPH_DRIVER ], compilerPath: /opt/arm-gnu-toolchain/bin/arm-none-eabi-gcc, cStandard: c11, cppStandard: c17, intelliSenseMode: gcc-arm } ], version: 4 }defines里的宏定义要和你的 Makefile 里保持一致否则 IntelliSense 会报一堆“未定义”错误但实际编译是过的——这种“假报错”很烦人配置对了就没了。2.3 OpenOCD 与调试器驱动OpenOCD 是连接 GDB 和硬件调试器的桥梁。GD32 常用的调试器有 J-Link、ST-Link、DAPLink 等。我用的是 J-Link OB板载也试过 ST-Link V2都能用。OpenOCD 的安装Windows 用户可以从 xPack 项目下载预编译包Linux 用户apt install openocd即可但版本可能较老建议从源码编译或者用 xPack 的包。安装后验证openocd --version然后需要确认你的调试器对应的配置文件。OpenOCD 的scripts目录下有interface和target两个子目录。J-Link 对应interface/jlink.cfgST-Link 对应interface/stlink.cfg。GD32 的 target 配置OpenOCD 官方没有直接提供但可以用 STM32 的配置替代——GD32F103 和 STM32F103 在调试接口上基本兼容用target/stm32f1x.cfg就行。GD32F4xx 系列用target/stm32f4x.cfg。注意GD32 某些型号的 Flash 容量和 STM32 不完全一致用 STM32 的配置可能会遇到“Flash 写入失败”或者“校验错误”。如果遇到这种情况需要自己写一个 target 配置文件指定正确的 Flash 起始地址和大小。这个后面在调试章节会详细说。2.4 Make 还是 CMake构建系统怎么选构建系统有两个选择Make 和 CMake。Make更简单直接一个Makefile搞定所有事情适合小型项目。缺点是跨平台性差Windows 上需要装 MinGW 或者 MSYS2 才能用。CMake更现代跨平台性好适合中大型项目。缺点是学习曲线陡一点配置文件写起来比 Makefile 啰嗦。我的建议是如果你只是想把 Keil 工程迁移过来用 Make 就够了简单直接。如果你打算长期维护这个项目或者需要跨平台构建用 CMake。这篇文章我以 Make 为主来讲解因为大部分从 Keil 迁移过来的项目规模不大Make 足够用。CMake 的配置我会在最后简单提一下思路。Windows 用户如果不想装 MSYS2可以用 EIDE 插件自带的构建功能它内部封装了 Make不需要你手动装。但如果你想在命令行构建还是建议装一个 MSYS2里面带make和rm等常用工具。3. 从零搭建 GD32 工程完整实操流程3.1 工程目录结构设计一个好的目录结构能让后续维护省很多事。我推荐的目录结构如下gd32_project/ ├── .vscode/ # VSCode 配置 │ ├── c_cpp_properties.json │ ├── launch.json │ └── tasks.json ├── Firmware/ # 官方固件库 │ ├── CMSIS/ │ └── GD32F10x_standard_peripheral/ ├── User/ # 用户代码 │ ├── main.c │ ├── gd32f10x_it.c │ └── ... ├── Startup/ # 启动文件 │ └── startup_gd32f10x_hd.s ├── Linker/ # 链接脚本 │ └── gd32f10x_hd.ld ├── Build/ # 编译输出gitignore ├── Makefile └── .gitignore这个结构把官方库、用户代码、启动文件、链接脚本分开清晰明了。Build目录放编译产物加到.gitignore里不纳入版本管理。从 Keil 工程迁移时把 Keil 工程里的Startup文件夹、CMSIS文件夹、Library文件夹标准外设库复制过来用户代码放到User目录。Keil 的.s启动文件 GCC 不能直接用需要换成 GCC 版本的启动文件——这个后面详细说。3.2 启动文件与链接脚本的适配这是从 Keil 迁移到 GCC 最容易出问题的地方。启动文件Keil 用的启动文件是 ARM 汇编器格式.sGCC 用的是 GNU 汇编器格式.S注意大写。两者语法不同不能混用。解决办法是从 GD32 官方固件库或者网上找 GCC 版本的启动文件。GD32 官方固件库的Template文件夹里通常有gcc子目录里面就是 GCC 版本的启动文件。如果找不到可以拿 STM32 对应型号的 GCC 启动文件改——把中断向量表里的中断名称改成 GD32 的即可。GD32F103 和 STM32F103 的中断向量表基本一致改起来不麻烦。链接脚本Keil 用的是分散加载文件.sctGCC 用的是链接脚本.ld。链接脚本定义了 Flash 和 RAM 的起始地址、大小以及各个段.text、.data、.bss的存放位置。一个典型的 GD32F103 链接脚本长这样ENTRY(Reset_Handler) MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K RAM (rwx) : ORIGIN 0x20000000, LENGTH 64K } SECTIONS { .text : { . ALIGN(4); *(.isr_vector) *(.text) *(.text*) *(.rodata) *(.rodata*) . ALIGN(4); _etext .; } FLASH .data : AT(_etext) { . ALIGN(4); _sdata .; *(.data) *(.data*) . ALIGN(4); _edata .; } RAM .bss : { . ALIGN(4); _sbss .; *(.bss) *(.bss*) *(COMMON) . ALIGN(4); _ebss .; } RAM }关键参数说明ORIGIN 0x08000000是 Flash 起始地址LENGTH 512K是 Flash 大小。GD32F103 不同型号的 Flash 大小不同比如 C8T6 是 64KRCT6 是 256KZET6 是 512K。这个参数必须和你的芯片型号匹配写大了会导致程序跑飞写小了会浪费空间。RAM 的ORIGIN 0x20000000LENGTH 64K。GD32F103 的 RAM 一般是 20K 到 96K 不等同样要按实际型号填。注意如果你用的是 GD32F4xx 系列Flash 起始地址是0x08000000但 RAM 起始地址是0x20000000大小从 128K 到 256K 不等。GD32F4xx 还有 CCM RAM核心耦合内存地址在0x10000000如果需要用到要在链接脚本里单独定义。3.3 Makefile 编写从编译到生成 hex/binMakefile 是整套方案的核心它定义了怎么编译、怎么链接、怎么生成最终文件。我写一个通用的 GD32 Makefile你直接抄改就行。# 工具链 PREFIX arm-none-eabi- CC $(PREFIX)gcc AS $(PREFIX)gcc -x assembler-with-cpp CP $(PREFIX)objcopy SZ $(PREFIX)size # 目标芯片 TARGET gd32f103 # 宏定义 DEFS -DGD32F10X_HD -DUSE_STDPERIPH_DRIVER # 头文件路径 INCLUDES -IFirmware/CMSIS \ -IFirmware/CMSIS/GD/GD32F10x/Include \ -IFirmware/GD32F10x_standard_peripheral/Include \ -IUser # 源文件 C_SOURCES $(wildcard User/*.c) \ $(wildcard Firmware/GD32F10x_standard_peripheral/Source/*.c) \ $(wildcard Firmware/CMSIS/GD/GD32F10x/Source/*.c) ASM_SOURCES Startup/startup_gd32f10x_hd.s # 编译选项 OPT -Og -g3 MCU -mcpucortex-m3 -mthumb CFLAGS $(MCU) $(DEFS) $(INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections LDFLAGS $(MCU) -TLinker/gd32f10x_hd.ld -Wl,--gc-sections -Wl,-MapBuild/$(TARGET).map # 输出文件 BUILD_DIR Build ELF $(BUILD_DIR)/$(TARGET).elf HEX $(BUILD_DIR)/$(TARGET).hex BIN $(BUILD_DIR)/$(TARGET).bin # 目标 all: $(ELF) $(HEX) $(BIN) $(ELF): $(C_SOURCES) $(ASM_SOURCES) mkdir -p $(BUILD_DIR) $(CC) $(CFLAGS) $(C_SOURCES) $(ASM_SOURCES) $(LDFLAGS) -o $ $(SZ) $ $(HEX): $(ELF) $(CP) -O ihex $ $ $(BIN): $(ELF) $(CP) -O binary -S $ $ clean: rm -rf $(BUILD_DIR) flash: $(ELF) openocd -f interface/jlink.cfg -f target/stm32f1x.cfg \ -c program $(ELF) verify reset exit .PHONY: all clean flash几个关键点解释一下-mcpucortex-m3 -mthumb指定了 CPU 架构和指令集。GD32F103 是 Cortex-M3 内核GD32F4xx 是 Cortex-M4要改成-mcpucortex-m4。-mthumb表示用 Thumb 指令集ARM Cortex-M 只支持 Thumb。-Og -g3是调试优化级别-Og在保持调试体验的同时做少量优化-g3生成最详细的调试信息。发布版本可以改成-O2 -g0。-fdata-sections -ffunction-sections配合-Wl,--gc-sections可以移除未使用的代码和数据减小固件体积。这个优化很有效我实测能减小 10% 到 20% 的体积。-Wl,-MapBuild/$(TARGET).map生成 map 文件里面详细列出了每个函数和变量占用的地址和大小排查内存问题时非常有用。flash目标用 OpenOCD 一键烧录后面调试章节会详细说。3.4 VSCode 调试配置launch.json 和 tasks.jsonVSCode 的调试配置放在.vscode/launch.json里。配合 Cortex-Debug 插件配置如下{ version: 0.2.0, configurations: [ { name: GD32 Debug (OpenOCD), type: cortex-debug, request: launch, servertype: openocd, cwd: ${workspaceFolder}, executable: Build/gd32f103.elf, device: GD32F103, configFiles: [ interface/jlink.cfg, target/stm32f1x.cfg ], svdFile: Firmware/CMSIS/GD/GD32F10x/SVD/GD32F103.svd, runToEntryPoint: main, preLaunchTask: build } ] }svdFile指定 SVD 文件这个文件描述了芯片的所有外设寄存器配置后可以在调试时查看外设寄存器的值非常方便。GD32 的 SVD 文件可以从官方固件库或者网上找。preLaunchTask指定调试前执行的构建任务对应tasks.json里的build任务{ version: 2.0.0, tasks: [ { label: build, type: shell, command: make, args: [all], group: { kind: build, isDefault: true }, problemMatcher: [$gcc] }, { label: flash, type: shell, command: make, args: [flash], problemMatcher: [] } ] }配置好后按 F5 就能自动编译、下载、进入调试。打断点、单步、查看变量、查看寄存器体验和 Keil5 一样但编辑器体验好太多了。4. 调试与烧录OpenOCD 实战4.1 OpenOCD 配置文件详解OpenOCD 的配置文件分两部分interface 配置和 target 配置。interface 配置指定调试器类型。J-Link 用interface/jlink.cfgST-Link 用interface/stlink.cfgDAPLink 用interface/cmsis-dap.cfg。如果你用的是 J-Link还可以在配置文件里指定速度adapter speed 40004000 表示 4MHz速度越快烧录越快但太长或质量差的杜邦线可能导致通信不稳定。我一般用 1000 到 4000 之间实测 2000 比较稳。target 配置指定芯片类型。前面说过GD32 可以用 STM32 的配置替代。但如果你遇到 Flash 写入问题可以自己写一个 target 配置set CHIPNAME gd32f103 source [find target/stm32f1x.cfg]这样就是基于 STM32F1x 的配置但芯片名改成 GD32F103。大部分情况下直接用target/stm32f1x.cfg就行。4.2 一键烧录脚本与批量生产思路调试的时候用 VSCode 的 F5 就够了但批量生产或者 CI 环境需要命令行烧录。前面 Makefile 里的flash目标就是干这个的make flash这条命令会调用 OpenOCD执行program命令烧录 ELF 文件然后verify校验最后reset复位芯片exit退出。如果是批量生产可以把烧录命令写成一个脚本配合流水线使用。比如#!/bin/bash for i in $(seq 1 10); do echo 烧录第 $i 块板子... openocd -f interface/jlink.cfg -f target/stm32f1x.cfg \ -c program Build/gd32f103.elf verify reset exit if [ $? -ne 0 ]; then echo 第 $i 块板子烧录失败 exit 1 fi read -p 请更换板子按回车继续... done这个脚本会循环烧录 10 块板子每烧一块提示更换。实际生产中可以用夹具和自动切换器但思路是一样的。4.3 调试技巧断点、watch、寄存器查看VSCode Cortex-Debug 的调试体验和 Keil5 相当甚至更好。几个常用功能断点在行号左边点一下就能打断点支持条件断点右键断点 - 编辑断点 - 输入条件。比如i 100当i等于 100 时才停下来排查循环问题很方便。Watch 窗口可以添加变量表达式实时查看值。支持结构体展开查看外设寄存器的位域。寄存器查看配置了 SVD 文件后可以在“XPERIPHERALS”面板里查看所有外设寄存器的值比 Keil5 的寄存器窗口更直观。内存查看在调试控制台输入x/16x 0x20000000可以查看内存内容排查数组越界、栈溢出等问题时很有用。反汇编调试时右键 - “打开反汇编视图”可以看汇编代码排查编译器优化导致的诡异问题时很有帮助。实操心得调试优化过的代码时单步可能会“跳来跳去”这是因为编译器做了指令重排。建议调试阶段用-Og或-O0发布时再用-O2。如果必须在-O2下调试可以给关键函数加__attribute__((optimize(O0)))强制不优化。5. 常见问题与排查技巧实录5.1 编译报错找不到头文件、未定义引用问题现象编译时报fatal error: gd32f10x.h: No such file or directory。原因头文件路径没配全。GD32 的头文件分布在多个目录CMSIS目录下有内核头文件Include目录下有外设头文件用户代码目录下有自己的头文件。解决检查 Makefile 里的INCLUDES变量确保所有头文件目录都加进去了。可以用find . -name *.h列出所有头文件确认路径。问题现象链接时报undefined reference to xxx。原因源文件没加到编译列表里或者库文件没链接。解决检查C_SOURCES变量确保所有.c文件都包含进去了。如果用了数学库比如sin、cos需要在LDFLAGS里加-lm。5.2 烧录失败Flash 写入错误、校验失败问题现象OpenOCD 报Flash write failed或者Verify failed。原因一链接脚本里的 Flash 大小和实际芯片不匹配。比如芯片实际是 256K链接脚本写了 512K程序可能被链接到超出实际 Flash 范围的地址。解决查芯片数据手册确认 Flash 大小修改链接脚本里的LENGTH。原因二芯片被读保护了。GD32 有读保护功能开启后无法通过调试器读取或写入 Flash。解决用 GD32 的官方工具或者 OpenOCD 解除读保护。OpenOCD 命令gd32f1x unlock 0或者用 ST-Link Utility 之类的工具解除保护。注意解除保护会擦除整个 Flash程序会丢失。原因三调试器速度太快通信不稳定。解决在 interface 配置里降低adapter speed比如从 4000 降到 1000。5.3 程序跑飞启动文件、中断向量表问题问题现象程序烧录成功但一上电就跑飞或者进不了main函数。原因一启动文件用错了。Keil 的启动文件和 GCC 的不兼容如果误用了 Keil 的.s文件链接会报错或者生成的向量表不对。解决确认用的是 GCC 版本的启动文件.S大写后缀检查启动文件里的中断向量表是否和芯片匹配。原因二链接脚本里的ENTRY符号不对。GCC 需要知道入口点是Reset_Handler。解决检查链接脚本第一行是不是ENTRY(Reset_Handler)以及启动文件里是否定义了Reset_Handler标号。原因三中断向量表偏移不对。如果程序从非默认地址启动比如 Bootloader 跳转需要设置SCB-VTOR寄存器。解决在main函数开头加SCB-VTOR 0x08000000;地址根据实际程序起始地址填。5.4 调试连接不上OpenOCD 报错排查问题现象OpenOCD 启动时报Error: open failed或者No device found。排查步骤检查调试器驱动是否装好。Windows 设备管理器里看有没有识别到 J-Link 或 ST-Link。检查接线。SWD 接口需要接SWCLK、SWDIO、GND有些调试器还需要接VCC参考电压。检查 interface 配置是否选对。J-Link 用jlink.cfgST-Link 用stlink.cfg别搞混。检查芯片是否在运行。如果芯片进入了低功耗模式或者死循环调试器可能连不上。可以按住复位键启动 OpenOCD 后再松开。检查是否有其他程序占用了调试器。比如 Keil5 或者 ST-Link Utility 还开着会独占调试器。5.5 常见问题速查表问题现象可能原因排查方法解决方案找不到头文件头文件路径未配置检查 Makefile 的 INCLUDES补全路径未定义引用源文件未加入编译检查 C_SOURCES添加源文件或链接库Flash 写入失败Flash 大小不匹配查数据手册修改链接脚本 LENGTH校验失败芯片读保护尝试解锁解除读保护程序跑飞启动文件错误检查 .S 文件换 GCC 版启动文件进不了 main入口点错误检查 ENTRY改为 Reset_Handler调试连不上驱动/接线问题查设备管理器重装驱动/检查接线调试器被占用其他程序开着关闭 Keil 等关闭冲突程序单步跳来跳去编译器优化检查优化级别改用 -Og 或 -O0固件太大未启用 gc-sections检查 LDFLAGS加 --gc-sections避坑技巧遇到问题时先看 OpenOCD 和 GCC 的完整输出不要只看最后一行错误。很多问题的根因在前面几行。比如“Flash 写入失败”的根因可能是“无法识别芯片”而“无法识别芯片”的根因可能是“调试器速度太快”。逐层排查别跳步。6. 进阶优化与工程化实践6.1 用 CMake 替代 Make 的迁移思路Make 适合小项目但项目大了之后Makefile 会变得很难维护。CMake 的优势在于跨平台、模块化、支持多目标、和 IDE 集成好。用 CMake 重写构建系统的思路cmake_minimum_required(VERSION 3.20) project(gd32_project C ASM) set(CMAKE_C_STANDARD 11) 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) # 编译选项 add_compile_options( -mcpucortex-m3 -mthumb -Og -g3 -Wall -fdata-sections -ffunction-sections ) add_compile_definitions(GD32F10X_HD USE_STDPERIPH_DRIVER) include_directories( Firmware/CMSIS Firmware/CMSIS/GD/GD32F10x/Include Firmware/GD32F10x_standard_peripheral/Include User ) # 源文件 file(GLOB_RECURSE SOURCES User/*.c Firmware/GD32F10x_standard_peripheral/Source/*.c Firmware/CMSIS/GD/GD32F10x/Source/*.c Startup/*.S ) add_executable(${PROJECT_NAME}.elf ${SOURCES}) # 链接选项 target_link_options(${PROJECT_NAME}.elf PRIVATE -T${CMAKE_SOURCE_DIR}/Linker/gd32f10x_hd.ld -Wl,--gc-sections -Wl,-Map${PROJECT_NAME}.map ) # 生成 hex 和 bin add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD COMMAND ${CMAKE_OBJCOPY} -O ihex $ TARGET_FILE:${PROJECT_NAME}.elf ${PROJECT_NAME}.hex COMMAND ${CMAKE_OBJCOPY} -O binary -S $ TARGET_FILE:${PROJECT_NAME}.elf ${PROJECT_NAME}.bin )CMake 的配置比 Makefile 啰嗦但结构更清晰扩展性更好。如果你的项目有多个目标比如 Bootloader AppCMake 管理起来会轻松很多。6.2 集成 Git 与 CI 自动化构建用 VSCode GCC 的一大优势就是可以方便地集成 Git 和 CI。Git 配置.gitignore文件要排除编译产物和 IDE 配置Build/ .vscode/ *.o *.elf *.hex *.bin *.mapCI 配置以 GitHub Actions 为例每次 push 自动编译name: Build GD32 Firmware on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Install ARM Toolchain run: | sudo apt-get update sudo apt-get install -y gcc-arm-none-eabi make - name: Build run: make all - name: Upload Artifacts uses: actions/upload-artifactv3 with: name: firmware path: Build/*.hex这样每次提交代码CI 会自动编译编译失败会发通知编译成功会生成 hex 文件供下载。团队协作时非常有用能避免“在我电脑上能编译”的问题。6.3 代码格式化与静态检查嵌入式 C 代码也可以享受现代工具链的便利。代码格式化用clang-format配置文件.clang-formatBasedOnStyle: Google IndentWidth: 4 ColumnLimit: 100 AllowShortFunctionsOnASingleLine: NoneVSCode 装Clang-Format插件保存时自动格式化。静态检查用cppcheck检查代码中的潜在问题cppcheck --enableall --inconclusive --stdc11 User/可以集成到 CI 里每次提交自动检查。单元测试嵌入式代码的单元测试比较麻烦但可以用Unity或者CMock框架在 PC 上编译测试逻辑代码不依赖硬件的部分提高代码质量。6.4 性能优化编译选项与代码体积控制GD32 的 Flash 和 RAM 资源有限优化很重要。编译选项优化-O2或-Os-O2优化速度-Os优化体积。一般用-Os因为 Flash 通常比速度更紧张。-flto链接时优化可以跨文件优化进一步减小体积。但调试时会麻烦一些。-ffunction-sections -fdata-sections-Wl,--gc-sections移除未使用的代码和数据。代码体积分析用arm-none-eabi-size查看各段大小arm-none-eabi-size Build/gd32f103.elf输出text data bss dec hex filename 24576 1024 2048 27648 6c00 Build/gd32f103.elftext是代码段data是已初始化数据bss是未初始化数据。text data是 Flash 占用data bss是 RAM 占用。map 文件分析Build/gd32f103.map文件里详细列出了每个函数和变量的大小按大小排序可以找出占用空间大的模块针对性优化。实操心得我遇到过一个项目 Flash 快满了用-Os和--gc-sections之后省了 15% 的空间。另外printf函数很占空间可能占几 K如果只是调试用可以在发布版本里用宏定义关掉或者用轻量级的printf替代实现。7. 我踩过的坑与最终建议7.1 那些让我熬夜的坑第一个坑启动文件用错。刚开始迁移的时候我直接把 Keil 工程里的.s文件复制过来编译报了一堆语法错误。折腾了半天才意识到 GCC 和 Keil 的汇编语法不一样。后来从 GD32 官方固件库的Template/gcc目录里找到了 GCC 版本的启动文件问题解决。第二个坑链接脚本 Flash 大小写错。我用的是 GD32F103RCT6Flash 是 256K但链接脚本里写的是 512K。程序编译链接都没问题但烧录后跑飞。查了两天才发现是链接脚本的问题——程序被链接到了超出实际 Flash 范围的地址烧录时被截断了。第三个坑OpenOCD 速度太快。J-Link 默认速度是 4000kHz我的杜邦线比较长通信不稳定烧录经常失败。后来把速度降到 1000kHz问题解决。这个坑很隐蔽因为错误信息是“Flash 写入失败”看起来像是 Flash 问题实际上是通信问题。第四个坑IntelliSense 假报错。VSCode 的 C/C 插件配置不对时会报一堆“未定义标识符”错误但实际编译是过的。这是因为 IntelliSense 不知道你的宏定义和头文件路径。配置好c_cpp_properties.json后问题解决。第五个坑调试优化过的代码。用-O2编译的代码单步调试时行号跳来跳去变量值也看不准。后来调试阶段改用-Og发布时才用-O2体验好很多。7.2 什么情况下我还是会用 Keil5虽然这套方案很香但有些场景我还是会用 Keil5紧急项目工期紧没时间折腾工具链Keil5 开箱即用。特殊芯片某些 GD32 的特殊型号官方只提供了 Keil 工程示例移植到 GCC 工作量太大。团队协作如果团队里其他人都在用 Keil5你一个人用 VSCode GCC工程文件不兼容协作会很麻烦。调试复杂外设Keil5 的外设寄存器查看器在某些场景下比 VSCode 更方便特别是没有 SVD 文件的芯片。所以我的建议是两套环境都留着。日常开发用 VSCode GCC享受现代编辑器的便利遇到特殊情况切回 Keil5保证项目进度。工具是为人服务的没必要非此即彼。7.3 给后来者的几条实用建议建议一先跑通再优化。不要一上来就追求完美的工程结构、最优的编译选项。先用最简单的 Makefile 把程序编译烧录跑通然后再逐步优化。我见过太多人卡在“完美配置”上结果项目一直没跑起来。建议二版本控制很重要。从第一天就用 Git 管理代码每次改动都提交。工具链配置、链接脚本、Makefile 这些都要纳入版本管理。出问题时可以回滚对比不同版本找原因。建议三保留 Keil 工程作为参考。迁移过程中Keil 工程不要删。遇到问题时对比 Keil 工程的配置比如 Flash 地址、中断向量表、编译选项能快速定位问题。建议四多查官方文档。ARM GNU Toolchain 的官方文档、OpenOCD 的官方文档、GD32 的固件库手册这些是第一手资料。网上的教程可能过时或者有误官方文档最可靠。建议五加入社区。VSCode GCC 开发 GD32 的社区虽然不如 Keil 大但活跃度在上升。遇到问题可以在相关论坛或者群里提问通常能得到热心解答。最后再分享一个小技巧如果你觉得 OpenOCD 配置太麻烦可以试试 EIDE 插件。它把 OpenOCD 的配置封装成了图形界面选芯片型号、选调试器、点烧录全程不用写配置文件。对于不想折腾命令行的朋友这是个不错的折中方案。但如果你想深入理解底层原理还是建议手动配置一遍 OpenOCD知其然也知其所以然。这套环境我用了大半年从最初的磕磕绊绊到现在行云流水中间踩的坑都写在这篇文章里了。希望它能帮你少走一些弯路。工具链的迁移不是一蹴而就的给自己一点耐心跑通第一个工程之后后面的路就顺了。