前阵子帮一个朋友调一块板子,他电脑上 Keil 莫名其妙弹出许可证过期,又赶上公司网络管控严格,装个新包折腾了快俩小时。我在旁边看着,顺手把自己平时用的 VSCode + OpenOCD 那套环境跑起来,从编译、下载到断点调试,十分钟把问题定位了。朋友当时就愣了:还能这么玩?
这不是什么新潮技术,STM32 开发从来就不只有 Keil 一条路。用 VSCode 当编辑器,ARM GCC 负责编译,OpenOCD 负责烧录和调试,Cortex-Debug 插件提供可视化调试界面,这套组合完全能替代 Keil 的日常开发流程,而且编辑体验、代码检索、Git 集成都甩开 Keil 一截。更关键的是,它不挑调试器,ST-Link、DAP-Link、J-Link 都能用,也能很方便地适配 GD32 这样的国产替代芯片。这篇文章就把这套环境的搭建过程、底层逻辑和移植 GD32 的细节一次讲透。
1. 先想清楚:这套组合到底替代了什么
1.1 Keil 用得越久,越想要一个更顺手的编辑器
Keil 在嵌入式圈子里扎根多年,绝大多数 STM32 教程、例程、毕业设计都是基于它写的。它把编辑、编译、烧录、调试集成在一个窗口里,学习成本低,对新手友好。但用的时间长了,痛点会越来越明显。
首先是编码体验。现代软件开发里,代码补全、跳转定义、全局搜索、重构、Git 对比这些能力几乎是刚需,而 Keil 的编辑器还停留在十年前的水平。尤其是工程大一点,源文件上百个的时候,在 Keil 里找一个函数定义,跳来跳去真的很费劲。第二个痛点是许可证。MDK 的商业授权不便宜,社区版又有代码体积限制,很多开发者不得不去找一些灰色手段,这不仅不体面,还容易遇到软件报错。第三个问题是跨平台和命令行支持。Keil 只跑在 Windows 上,想接入 Jenkins 自动化编译、想在 Linux 或 macOS 上开发,基本没戏。
还有一个经常被忽略的点:Keil 的编译信息对新手不友好。报错提示格式混乱,定位不够直观;工程文件是二进制私有格式,跟 Git 配合不好,同事之间合并工程很容易出怪问题。
1.2 替代方案的完整工具链长什么样
我要推荐的这套替代方案,弥补的正是上述这些短板。整套工具链由四个部分组成,各司其职:
- VSCode:负责代码编辑、文件管理、Git 操作、终端命令执行;
- ARM GNU 工具链:负责把 C 代码编译成 ARM 机器码,生成 .elf 和 .hex 文件;
- OpenOCD:负责通过调试器(ST-Link 等)连接芯片,执行烧录、复位、读写内存等底层操作;
- Cortex-Debug 插件:把 OpenOCD 的能力包装成 VSCode 里的调试会话,支持断点、单步、变量查看、寄存器查看等可视化调试功能。
这套工具链全部开源免费,没有代码体积限制,跨平台可用,而且每一部分都能被其他工具替换。比如不想用 VSCode,可以换 CLion 甚至 Vim;不想用 OpenOCD,可以换 pyOCD、J-Link GDB Server;不想用 ARM GCC,可以换 Clang。整个流程是组合式的,灵活性远远高于 Keil 这样的“全家桶”。
一句话总结:用 VSCode + OpenOCD 做 STM32 开发,本质上是把过去分散在 Keil 里的各个功能拆开,换成每个环节更好用的专业工具。
2. 环境搭建:从零把工具链准备齐
2.1 下载并安装四个基础工具
这套环境搭建并不复杂,但每一步都要做对,否则后面的调试会到处踩坑。
先装 VSCode。直接去 VSCode 官网下载安装包,Windows 下安装时建议勾选“添加到 PATH”以及“通过 Code 打开操作”,后面在终端里输 code 命令会很方便。Linux 下可以直接用系统包管理器安装,比如 Ubuntu 下 sudo apt install code。
然后是 ARM GCC 工具链。这个工具链的名字很长,叫 arm-none-eabi-gcc,意思是“目标架构 ARM、无操作系统、嵌入式应用二进制接口”。到 ARM 官方开发者网站下载最新版,Windows 选择 exe 安装包,Linux 选择对应压缩包解压到 /opt 里。安装完后把 bin 目录加入 PATH,Windows 用户在系统环境变量里加,Linux 用户编辑 ~/.bashrc 加 export PATH=$PATH:/opt/gcc-arm-none-eabi/bin。
接着是 OpenOCD。这里特别说一下,原版 OpenOCD 在 Windows 上需要自己编译,比较麻烦。我推荐使用 xpack 打包好的版本,到 xpack 的 GitHub Releases 页面下载对应平台的压缩包,解压后同样加入 PATH。xpack 版本开箱即用,ST-Link、DAP-Link 的驱动都集成好了,非常适合开发环境使用。
最后,如果你用的是 ST-Link 调试器,Windows 下需要安装 ST 官方的 ST-Link 驱动,确保设备管理器里能看到 “STM32 STLink dongle” 之类的设备。DAP-Link 通常是免驱的,Win10/Win11 能直接识别。
2.2 VSCode 里要装的插件,缺一不可
工具链装完后,打开 VSCode,在扩展商店里搜这几个插件并安装:
- C/C++:微软官方插件,提供代码补全、语法高亮、跳转定义、调试支持;
- Cortex-Debug:核心调试插件,负责连接 OpenOCD 并呈现调试界面,是可视化调试的关键;
- EIDE:嵌入式 IDE 插件,用来创建和管理 GCC 工程,对 Keil 用户尤其友好;
- Cortex-Debug:Device Support Pack 扩展,它提供设备相关文件支持,配合 Cortex-Debug 使用能识别更多芯片型号。
EIDE 这个插件值得单独说。它并不是必须的,因为也可以用 Makefile 或 CMake 手动管理工程。但对从 Keil 迁移过来的开发者来说,EIDE 的学习成本最低:它可以导入 Keil 工程,也可以新建一个 GCC 工程然后手动添加源码、头文件路径、宏定义、链接脚本,界面是图形化的,不需要去理解复杂的 make 语法。用它,你可以在不懂 Makefile 的情况下完成编译。
2.3 用一条命令验证工具链
安装配置完成后,打开一个新的终端,依次执行下面三条命令,查看版本信息是否正常输出:
code --version arm-none-eabi-gcc --version openocd --version如果 arm-none-eabi-gcc 提示“不是内部或外部命令”,说明 PATH 没配置好,需要检查环境变量;如果 OpenOCD 提示缺少 DLL,尝试把 OpenOCD 目录下的 libusb 相关动态库路径也加到 PATH 里。这三条命令能通过,环境就算是齐了。
3. 编译工程:把 HAL/标准库工程跑起来
3.1 从 Keil 工程迁移到 VSCode 的两种思路
环境准备好之后,面临的实际问题是怎么把现有的 STM32 工程在 VSCode 里编译起来。这里有两条路,根据你的情况选。
第一种思路:使用 EIDE 导入工程。在 VSCode 里按 Ctrl+Shift+P,输入 EIDE,选择“Import Keil Project”,然后定位到你的 .uvprojx 文件。EIDE 会尝试解析 Keil 工程里的源文件、头文件路径和宏定义,自动生成一个 EIDE 工程。这个功能对中小型工程识别率很高,但对于那些引用了大量相对路径、条件编译或者自制 SDK 的复杂工程,解析可能不完整,需要手动检查源文件列表。
第二种思路:手动创建工程结构。在 EIDE 里新建一个“Empty Project”或者“GCC Project”,然后把源码文件夹整个拖进工程的 Source 目录,手动添加 Include Paths 和宏定义。这种方式稍微麻烦一点,但最可控,尤其适合理解工程全貌的场景。
我个人的习惯是:新项目直接用 EIDE 手动建工程;老工程用导入功能做初筛,然后逐项核对源文件和宏。这里提醒一句,迁移后第一件事是确认芯片型号是否正确,这直接影响编译参数和链接脚本。
3.2 编译配置与链接脚本,这几项必须搞对
在 EIDE 里配置编译项时,有几个关键选项无论如何不能错。
首先是 CPU 类型。在 EIDE 的“编译配置”中可以设置 ARM CPU 类型,对应你的具体芯片。Cortex-M3 对应 --cpu Cortex-M3,Cortex-M4 对应 --cpu Cortex-M4,如果带 FPU(比如 STM32F407),还要在编译选项里加上 -mfpu=fpv4-sp-d16 -mfloat-abi=hard。CPU 选错了,轻则启动异常,重则程序根本跑不起来,这是新手最容易忽略的地方。
其次是链接脚本。链接脚本决定了代码段、数据段的起始地址和存储布局。在 EIDE 里,链接脚本通常是一个 .icf 或 .ld 文件。对 STM32 来说,最简单的方法是使用 STM32CubeMX 生成一个 Makefile 工程,然后直接复制里面的 .ld 文件到你的工程里。这个 .ld 文件是根据 CubeMX 里选择的芯片自动生成的路径映射,省去手写 Flash 和 RAM 地址的麻烦。
如果你是从 Keil 迁移过来的,Keil 用的是分散加载文件(.sct),跟 GCC 的链接脚本语法完全不同,不能直接套用。所以迁移到 VSCode 后,建议直接用 CubeMX 生成对应的 GCC 工程,再把它作为基础骨架,再往里面填你自己的业务代码。这个思路能帮你绕开链接脚本、启动文件、系统时钟初始化这些容易出错的底层层面的问题。“用 CubeMX 生成骨架”是我在实际迁移中比较推荐的做法。
3.3 命令行编译与问题定位
EIDE 提供了一键编译按钮,但真正好用的地方在于,它生成的工程底层是 Makefile,你完全可以在项目目录下打开终端,执行 make 命令来编译。这样既能看到完整编译过程,也能在出错时更方便地 grep 日志。
编译报错是每天都要面对的事。在 VSCode 里遇到报错,直接 Ctrl+点击错误信息里的文件路径,就能跳到出错的代码行;或者双击“问题”面板里的错误条目。这一点体验比 Keil 好太多。
我踩过的一个坑是:从 Keil 迁移过来的工程,编译时频繁报 undefined reference。原因往往是某些 .c 文件里定义了函数,但源文件没有加进工程。Keil 工程里文件是按组展示的,手动迁移时容易漏掉全部文件。遇到这种报错,先检查 EIDE 的源文件列表,看看是不是有漏网的 source 文件没有添加。
4. OpenOCD 配置与可视化调试
4.1 OpenOCD 的工作逻辑先搞明白
很多新手卡在 OpenOCD 配置上,是因为没搞清楚它的工作方式。OpenOCD 本身是一个“桥接程序”,它的一端通过 USB 连接 ST-Link 等调试器,另一端通过 GDB 协议对外提供服务。调试器软件(如 Cortex-Debug)下发调试指令,OpenOCD 把这些指令翻译成调试器能懂的协议,再由调试器控制芯片。
所以 OpenOCD 的启动需要两类配置文件:interface 文件描述调试器类型,target 文件描述芯片类型。这两类文件用 -f 参数指定,OpenOCD 会依次加载。比如用 ST-Link 调试 STM32F103,启动命令就是:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg命令执行后,OpenOCD 会启动一个 3333 端口的 GDB 服务,并打印出 “Info : Listening on port 3333 for gdb connections” 这样的信息。这时 OpenOCD 就进入等待状态,等待调试器软件连上来。
4.2 配置调试器和目标芯片
在给 Cortex-Debug 写配置之前,先手动跑一次 OpenOCD,确认它能正常连上芯片。因为如果 OpenOCD 命令行都报错,说明配置或硬件有问题,这时候不用去研究调试插件,先把 OpenOCD 这条链路搞通。
连接 ST-Link 到 STM32F103 开发板,执行:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg正常情况下,日志会显示识别出 STM32F1 系列的 IDCODE,并且显示类似 “Info : STM32F10x_128: 128 kB flash, 20 kB sram” 的信息。如果显示 “Error: init mode failed (unable to connect to the target)”,那就需要检查接线、供电和复位引脚了。
针对不同调试器和芯片,配置文件的选择可以参考下面这张表:
| 调试器/目标 | interface 配置 | 适用芯片示例 |
|---|---|---|
| ST-Link 调试 STM32F1 | interface/stlink.cfg | STM32F103C8T6 |
| ST-Link 调试 STM32F4 | interface/stlink.cfg | STM32F407VET6 |
| DAP-Link 调试 STM32F7 | interface/cmsis-dap.cfg | STM32F767ZIT6 |
| FTDI 转 SWD 调试 STM32L4 | interface/ftdi/olimex-arm-usb-tiny-h.cfg | STM32L432KC |
如果看不到对应的 target 文件,可以先在 openocd 安装目录的 tcl/target 文件夹里搜一下,也可以根据芯片系列找相近的文件。target/stm32f1x.cfg 通常覆盖了 STM32F101、STM32F102、STM32F103、STM32F105、STM32F107 全系列,配置里通过 -work-area-virt 等参数区分不同型号。
4.3 配置 Cortex-Debug 的 launch.json
OpenOCD 能在命令行跑通之后,剩下的就是让调试器软件把 OpenOCD“拉起来”。这一步在 VSCode 里通过 .vscode/launch.json 配置完成。
按 Ctrl+Shift+P,输入 Debug: Open Configurations,选择 Cortex-Debug,然后填入类似下面的配置:
{ "name": "STM32 Debug", "cwd": "${workspaceFolder}", "executable": "./build/firmware.elf", "request": "launch", "type": "cortex-debug", "servertype": "openocd", "interface": "swd", "configFiles": [ "interface/stlink.cfg", "target/stm32f1x.cfg" ], "searchDir": [], "runToEntryPoint": "main", "svdFile": "${workspaceFolder}/STM32F103.svd", "gdbPath": "arm-none-eabi-gdb", "preLaunchTask": "build" }先看几个关键字段:executable 指向编译生成的 ELF 文件,注意不是 .hex 也不是 .bin,ELF 里包含了调试符号,缺失它就没法设置断点和查看变量。configFiles 就是 OpenOCD 的配置文件列表,跟命令行里 -f 参数后面的内容一致。svdFile 指向芯片的 SVD 外设描述文件,这个文件来自 ARM CMSIS 标准,描述了芯片全部寄存器的名称和地址,调试时能实时看到外设寄存器的具体值。如果没有这个文件,调试功能会缺一大块。
关于 gdbPath,在 Windows 上有时配置为 arm-none-eabi-gdb 就能找到,但保险起见,写全路径。preLaunchTask 是可选的,它会在调试前自动执行编译任务,省去每次手动编译的步骤。你也可以不配,自己先按 Ctrl+Shift+B 编译再启动调试。
4.4 可视化调试能看什么,实际体验如何
配置好后,按下 F5 启动调试,程序会自动停在 main 函数入口。左侧调试面板会展示调用堆栈、局部变量、全局变量、断点列表,甚至 ARM 内核寄存器 r0-r15、xPSR、msp、psp 等都能直接看到。这在排查启动代码、rtos 任务切换问题时非常有用。
外设寄存器视图是个亮点。因为刚才配置里加了 svdFile,调试时可以展开一个 Peripherals 下拉菜单,点击 USART1,就能看到它的 BRR、CR1、SR 等寄存器实时变化。当你在代码里执行一条串口发送语句,外设视图里 USART1_DR 寄存器的值会立刻更新。这种可视化的直观程度,其实已经超过了 Keil 的寄存器窗口。
Cortex-Debug 还支持 RTOS 视图。如果工程里用的是 FreeRTOS,并且用 CubeMX 生成的 Makefile 工程已经带上了 FreeRTOS 源码,Cortex-Debug 可以自动检测并列出所有任务、状态、信号量。调试多任务程序时,可以直观地看到每个任务停在哪个函数、哪个状态,这对分析死锁和优先级翻转问题非常有价值。我调试一个跑 FreeRTOS 的网口项目时,正是靠着任务列表一眼发现一个高优先级任务在死循环占着 CPU 不放。
还有一个小功能:调试控制台里可以直接执行表达式求值。在 WATCH 窗口里添加一个表达式,比如(uint32_t)0x4001380C,就能直接读 USART1->SR 的值。这个写法和 GDB 原生命令完全一致,灵活性很高。
5. GD32 移植实战技巧
5.1 GD32 与 STM32 的差异,不是简单换个标号
国产芯片 GD32 这些年用的人越来越多,它和 STM32 的兼容性吸引了很多人做替代。但“兼容”不等于“完全一样”,在实际移植时,有几个硬件层面的差异必须了解。
最核心的差异是内核和外设的微调。GD32F103 系列虽然也是 Cortex-M3 内核,但主频可以跑到 108MHz,而 STM32F103 通常只能跑到 72MHz。这意味着 GD32 的 Flash 等待周期、系统时钟树配置跟 STM32 不同,直接拿 ST 的 SystemInit 代码用,可能会在提高主频时出现运行异常。
GPIO 的复用模式定义也有所不同。比如在 STM32 的 HAL 库里,GPIO_Mode 是一个枚举值,而 GD32 的固件库把每个引脚的复用功能定义成了单独的宏,类似 GPIO_AF_0、GPIO_AF_1。所以直接用 ST 的库代码编译到 GD32,可能在引脚配置函数上直接报错。
此外,GD32 某些系列的 USB、ADC、Flash 控制器逻辑和 ST 不是完全一致的,特别是最近更新的 GD32F4 系列,它针对高性能场景做了不少改动,不是简单的寄存器兼容。
5.2 编译链路里的移植改动
软件层面最直接的做法是使用 GD32 官方固件库,而不是 ST 的 SPL 或者 HAL。GD32 官方提供了和外设库(Firmware Library),里面包含了针对不同系列的头文件和源文件,比如 GD32F10x Firmware Library 就是为 F1 系列准备的。
在建工程时,把 GD32 固件库里的源码全部加入 EIDE 工程的 Source 目录,然后把头文件路径指到 Firmware 和 CMSIS 文件夹。这时你会注意到,GD32 的库文件命名和 ST 不一样,比如 stm32f10x.h 变成了 gd32f10x.h,核心寄存器结构体也从 GPIO_TypeDef 变成了 gpio_typedef,全部小写。这个差异对从 ST 移植过来的代码影响很大,因为 C 语言是区分大小写的,如果你原来是 GPIOA->CRH,在 GD32 里就要改成 GPIOA->CRH(这部分恰好一样),但类型名必须全改成小写或小写下划线风格。
另一个关键是宏定义。GD32 固件库在编译时需要定义一些全局宏,比如 GD32F10X_HD(高密度)、GD32F10X_CL(互联型)等,这些宏在 EIDE 的编译配置里要手动加上,否则编译会报很多未定义错误。
启动文件和链接脚本也需要换成对应型号的。GD32 固件的支持包里一般会提供 GCC 使用的启动文件和链接脚本。启动文件命名类似 startup_gd32f103_hd.s,里面的中断向量表名称也改成了 gd32 风格。
5.3 OpenOCD 侧的 GD32 适配
OpenOCD 对 GD32 的支持在不断完善。较新版本的 OpenOCD 里,tcl/target 目录下已经可以直接找到 gd32f1x.cfg、gd32f3x0.cfg 等文件。如果你用的 xpack 版本更新不够频繁,找不到对应的 cfg 文件,也可以直接把 stm32f1x.cfg 复制一份,改名为 gd32f1x.cfg,然后修改其中的芯片 ID 和 Flash 参数。
这里给一个我实际用过的配置,针对 GD32F103C8T6,用 ST-Link 连接:
openocd -f interface/stlink.cfg -f target/gd32f1x.cfg如果 target 目录下确实没有 gd32f1x.cfg,可以手动创建,内容参照:
- set CHIPNAME gd32f103
- set WORKAREASIZE 0x4000
- flash bank 用 stm32f1x,因为 GD32F103 的 Flash 编程协议在 1KB/2KB 页面模式下和 STM32 是兼容的
之所以 OpenOCD 没有把 GD32F103 识别为 STM32F103,是因为 GD32 的调试 IDCODE 和 ST 系列有差异。OpenOCD 在连接时会对 IDCODE 做校验,匹配不上就报错。所以最简单的做法就是用新版本 OpenOCD 里的 gd32 专用配置,或者手动修改 IDCODE 字段。
有一点要格外注意:连接 GD32 后,OpenOCD 可能报 "Unknown flash device! Device ID = 0x7f0f0201" 之类错误。这时候不要慌,它说明 OpenOCD 成功识别出了芯片内核,但 Flash 的 ID 不在它的已知列表里。你需要在 target 配置里手动加上 flash device 的参数,或者在 OpenOCD 启动后用 flash probe 命令手动检测一次。我遇到过几个老版本 OpenOCD,手动补上 ID 后就正常刷进去了。
5.4 GD32 烧录和 DFU 注意事项,以及 ITCM 的小知识
GD32 的烧录方式有几个坑值得单独列出来。最典型的是用 J-Link 烧谒 GD32 时报 “Cannot connect to target”,这通常是因为 J-Link 的新版本软件无法识别 GD32 的 ID。解决办法是更新 J-Link 软件到支持 GD32 的版本,或者在 J-Link Commander 里手动指定设备类型为 GD32F103。如果你手里的是国产兼容调试器,建议直接用 ST-Link 或者 DAP-Link,配合 OpenOCD 是最省心的组合。
另一个常见场景是 GD32 的 DFU 下载。GD32 内置了 USB DFU bootloader,芯片上电时如果检测到 BOOT0 拉高,就会进入 DFU 模式,电脑上会出现一个 DFU 设备。Windows 下需要手动安装 GD32 DFU 驱动,如果没有正确安装,设备管理器里显示为未知设备,烧录工具无法识别。驱动装好后,可以用 DFU 工具或者 OpenOCD 的 dfu 接口把固件直接写进 Flash,这种方式不需要调试器,对量产和维护非常有帮助。
关于 GD32 ITCM,我在一个 F407 系列项目上用过这个特性。GD32F407 这类高端芯片内部带有一个紧耦合内存(ITCM),它和 CPU 之间有一条独立的高速总线,CPU 从 ITCM 取指可以零等待访问,适合放对实时性要求极高的中断服务程序和关键算法。在工程里启用 ITCM 的方法是修改链接脚本,把 ITCM 区域划分出来,然后把指定的函数、中断向量表放到该区域。用 GCC 的话,链接脚本里加入:
ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K然后把需要加速的函数用attribute((section(".itcm"))) 指定到该段。这里注意,ITCM 和 Flash 的地址空间可能重叠,必须在系统初始化时把 ITCM 对应的 RAM 使能,并且保证用户程序不使用冲突的地址。
6. 常见问题排查速查
6.1 OpenOCD 连接不上芯片,先别怀疑软件
OpenOCD 启动时报 “unable to find a matching CMSIS-DAP device” 或者 “init mode failed (unable to connect to the target)”,多数情况下不是软件问题,而是硬件链路问题。先检查 SWD 的四根线:SWDIO、SWCLK、GND,以及 VCC 是否接到了调试器的 TARGET VCC 引脚上。很多新手只接三根线,导致调试器无法确认目标电压,连接自然失败。
另一个很容易忽略的点是目标板供电。ST-Link V2 上的 3.3V 输出能力有限,只能给低功耗板子供电,如果板子上有其他模块,比如无线模块、屏幕,最好外接电源,否则调试过程中会频繁掉线。
有时候 Reset 引脚影响也很大。某些开发板的复位电容设计得过大,导致调试器复位芯片时信号不稳定,OpenOCD 就会报 “JTAG-DP STICKY ERROR”。这种情况下可以尝试把配置文件的 reset_config 改成 srst_only,或者手动在板子的复位电容上并联一个较小的电容。我调试一块国产开发板时,就是因为复位电容 100nF 偏大,换成了 10nF 后一切正常。
6.2 报错说找不到调试器,多半是驱动或 IDCODE
Windows 下 OpenOCD 连接 ST-Link 时,出现 “Error: libusb_open() failed” 或者 “No ST-Link detected”,先打开设备管理器,看是否识别出 ST-Link。如果设备管理器里是黄色的感叹号,说明驱动没装对。网上下载 ST-Link 官方驱动,安装时注意选择 64 位版本。
如果驱动正常,但还是报 IDCODE 不匹配,那就有可能是芯片本身的问题。比如你拿 STM32F4 的配置去连 STM32F1,OpenOCD 会提示 “Cannot identify target as a STM32F1 family”。解决办法就是检查 target 配置文件是否与芯片型号匹配。
还有一种情况是芯片被读保护了。之前调试过程中开了 RDP 保护,此时 OpenOCD 连接时能读到 IDCODE,但无法读取内存,也无法擦除。通常会报 “Target not available” 或者 “Error: flash write failed”。这时候需要执行:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \ -c "init; halt; stm32f1x unlock 0; resume; reset; exit"执行 unlock 命令会擦除整个 Flash 并解除读保护,注意这会清掉芯片里所有数据。
6.3 烧录不进去、下载后程序不跑
烧录速度快,但程序不启动,这是另一类常见问题。首先看复位方式,OpenOCD 默认使用硬件复位,如果你的电路板复位电路太弱,程序可能没有正确复位。在 target 配置里可以明确设置:
reset_config srst_nogate或者尝试用软件复位。把 launch.json 里 configFiles 中的 openocd 参数加一行 -c "reset_config srst_only",再试。
程序不跑的另一个原因是寄存器配置错位。以前我用 GCC 编译 GD32 时,因为启动文件里中断向量表用的是 __Vectors,而 GD32 某些固件库的启动文件里变成 gd32_irq_handler,二者名字不匹配,导致程序启动后直接 HardFault。遇到这种问题,先不要急着加代码,用调试器看 PC 停在哪个地址,最快捷的排查方式是单步看启动文件的执行。
6.4 调试时能否看到外设寄存器,取决于 SVD 文件
很多同学配置好 launch.json 后能正常跑断点,但左侧“外设”面板是空的。这不是调试器坏了,而是没有加载 SVD 文件。SVD 文件是一个描述芯片寄存器的 XML 文件,需要单独下载。在 ST 的官方 SDK 里一般会附带,或者去 posborne/cmsis-svd 仓库下载对应型号的 .svd 文件,然后在 launch.json 里指定:
"svdFile": "${workspaceFolder}/STM32F103.svd"加载后重新启动调试,外设面板里就会密密麻麻地列出 GPIO、USART、TIM、RCC 等寄存器。这可以说是可视化调试真正让人“上瘾”的功能。你可以实时比较寄存器值和代码逻辑,排查起来效率特别高。
6.5 关于调试速度和接线:稳定压倒一切
SWD 的调试速度可以配置,但并不是越快越好。Cortex-Debug 的 launch.json 里支持通过 openocd 的 adapter speed 参数设置,比如在 configFiles 后面加一行:
"openocdArgs": "-c \"adapter speed 1000\""这里的 1000 是 kHz,意思是 SWD 时钟设为 1MHz。如果线材太长、接触不良,高速时会频繁报错,比如 “SWD DIO Pane with unexpected clock” 这类乱码。我建议默认用 1000 到 2000,稳定优先。如果布线环境好,再用 4000 也可以。
另外提醒一个关于 VM 的坑:如果你在虚拟机里跑 VSCode,USB 直通给虚拟机时,OpenOCD 访问 ST-Link 可能不稳定。解法是关闭虚拟机的 USB 自动连接功能,或者直接在本机运行。这个坑我连续踩了两天,最后拆掉虚拟机一切正常。
我个人在实际项目里的使用习惯是:LaTeX 级别的长代码编辑和搜索在 VSCode 里完成,真正的调试还是以 Cortex-Debug 的可视化界面为主,OpenOCD 的命令行日志作为辅助。用久了你会发现,从 Keil 迁到 VSCode + OpenOCD 排除了许可证和编辑器的烦恼,剩下的就是纯粹的代码和硬件逻辑。一开始会有一段适应期,但等你把外设寄存器视图、RTOS 任务视图都用顺手之后,再回头用 Keil 反而觉得浑身不舒服。这套环境我用了快两年,从 STM32F1 到 GD32F407,调试体验稳稳的,值得花一个下午把它搭起来。