STM32开发环境搭建:VSCode+OpenOCD+ST-Link调试烧录全攻略

STM32开发环境搭建:VSCode+OpenOCD+ST-Link调试烧录全攻略 很多人在 CubeIDE 里点了半天生成代码转头却在为另一个问题发愁Eclipse 界面卡补全慢格式化随手一按就把整个文件弄乱。我去年开始把 STM32 的开发工作流切到 VSCode CubeIDE 生成工程 OpenOCD 调试 ST-Link 烧录踩了不少坑也把从no stm32 target found到flash timeout这一类报错的排查逻辑基本捋顺了。这篇文章就是把这套环境从零到能跑、能改、能调试、能量产烧录的过程完整记录一遍给想在 VSCode 里写 STM32 的人一条能直接照抄的路。1. 为什么我把调试环境从 CubeIDE 主界面搬到了 VSCode1.1 卡顿只是表象真正的问题是编辑体验和工程视图很多人觉得 CubeIDE 卡是因为电脑配置不行实际上真正让人难受的是 Eclipse 的工程管理和编辑器交互。STM32CubeIDE 本质上就是在 Eclipse 上套了一层 ST 的插件工程目录里塞满了.cproject、.project、Debug 目录下的中间文件代码索引稍微大一点就明显感到补全迟钝。更麻烦的是如果你同时开了 Git 做版本管理Eclipse 默认对.metadata这类目录的过滤行为需要额外设置否则提交的时候一不留神就把一堆 IDE 缓存文件带上去了。但这里我要先把话说清楚我并不是建议你把 CubeIDE 删掉。CubeIDE 和 CubeMX 最大的价值是生成外设初始化代码图形化配置时钟树、引脚、外设参数这一环效率极高靠手写寄存器或者手工维护启动文件完全是自找麻烦。1.2 分工方式CubeIDE 生成初始化VSCode 负责写和调我现在的实际工作流是这样用 CubeMX 或者 CubeIDE 建工程把时钟、GPIO、UART、SPI、I2C、定时器这些都配好生成代码。用 VSCode 打开生成后的工程目录写业务逻辑、改代码、看 Git 差异。在 VSCode 里配置编译任务直接调用 arm-none-eabi-gcc 和 make 编译。调试用 Cortex-Debug 插件拉起 OpenOCD通过 ST-Link 连目标板断点、单步、看变量全在 VSCode 里完成。烧录用 OpenOCD 或者 STM32CubeProgrammer 的命令行工具脚本化批量处理。这套组合的好处是CubeIDE 干的还是它最擅长的活而我每天盯着最多的编辑器变成了 VSCode。VSCode 的补全、跳转、格式化、Git 集成、远程开发这些体验比 Eclipse 好一个量级而且启动快开再多窗口也不心疼。1.3 什么样的开发者适合这套组合如果你属于下面几类人这套工作流会特别舒服从 Keil 转过来的老 MCU 工程师受够了 Keil 的编辑器想用现代编辑器但不想放弃成熟的 HAL 库开发模式。做毕设或者 DIY 项目的学生需要快速从 CubeMX 生成工程同时希望代码留在 GitHub 上方便管理。做产品开发的工程师需要对多个板卡做脚本化烧录、自动化测试VSCode 的终端和任务系统比 IDE 里点按钮高效得多。当然如果你就是喜欢 CubeIDE 里一键编译、一键下载的闭环那也没必要折腾。这套方案更适合愿意花半小时配置环境、换取长期开发体验提升的人。2. 开工前先把工具链关系理顺CubeIDE、OpenOCD、ST-Link 各管哪一段2.1 数据流通路从源码到目标板要过几道关卡先理清工具链是怎么串起来的不然配置的时候很容易糊涂。一条最简单的数据通路是这样的CubeMX/CubeIDE负责生成 STM32 的启动代码、外设初始化代码和链接脚本同时可以生成 Makefile 或者存储工程配置。arm-none-eabi-gcc是交叉编译器把 C 代码编译成 ARM Cortex-M 的机器码。make读取 Makefile 执行编译、链接生成.elf文件。OpenOCD通过 ST-Link 这个硬件调试器访问目标芯片的 SWD 接口完成烧录和调试会话管理。Cortex-DebugVSCode 插件做的是把 GDB 和 OpenOCD 连起来你在编辑器里点“开始调试”它负责启动 OpenOCD、连接远端 GDB Server、加载.elf符号文件、控制断点。所以 OpenOCD 在这里扮演的是“翻译官”的角色它把 GDB 的调试指令转换成 SWD 协议的命令送到 ST-Link再进芯片内部。这个链路只要有一个环节不对就会出现各种让人抓狂的报错。2.2 软件清单和版本选择我实际使用的软件清单如下版本选择上我的原则是“能用新的就别用旧的”但 OpenOCD 除外后面会解释。软件作用备注VSCode编辑器官网下载最新版即可STM32CubeMX 或 STM32CubeIDE生成初始化代码CubeIDE 自带 CubeMX 功能arm-none-eabi-gcc交叉编译器可以装 Task 官方工具链也可以用 CubeIDE 自带的OpenOCD调试与烧录桥接0.11/0.12 版本稳定性较好ST-Link 驱动让 Windows 识别 ST-Link官方 STSW-LINK009STM32CubeProgrammer解锁、批量烧录、即时调试命令行工具非常强大Cortex-Debug 插件VSCode 里的调试前端必须安装这里有个很容易被忽略的问题ST-Link 驱动和固件是两回事。驱动装好以后设备管理器里能看到 ST-Link 调试口和虚拟串口。很多人遇到“stm32 virtual com port 叹号”问题其实是驱动没有正确安装或者系统装了两版驱动冲突。解决方法是打开设备管理器找到带黄色叹号的设备右键更新驱动手动指向你下载的 STSW-LINK009 驱动目录。如果更新完还报错那就把原来的 ST-Link 相关驱动全部卸载重装。另外如果 ST-Link 固件太老OpenOCD 连接的时候可能表现为“能识别到 USB但连接不上芯片”。这时用 STM32CubeProgrammer 里的 Firmware Upgrade 工具升级一下 ST-Link 固件问题通常就消失了。2.3 为什么我不建议直接拷贝网上的 openocd.cfg网上有很多现成的openocd.cfg是别人针对自己的开发板写的直接拿来用大概率会遇到“板子和配置文件对不上”的尴尬。比如你的芯片是 STM32F103C8用的 target 文件是stm32f1x.cfg别人的是 STM32F407target 文件是stm32f4x.cfg。还有人的板子用的是 J-Linkinterface 配置写的jlink.cfg你手里是 ST-Link那当然连不上。OpenOCD 的官方脚本目录在安装目录下的share/openocd/scripts里面有interface和target两个大目录。正确的做法是分别找到适配你调试器的 interface 文件和适配你芯片型号的 target 文件然后组合起来。这也是我下面要讲的重点。3. 两条构建路径CubeMX 生成 Makefile 与 STM32CubeCLT 官方扩展3.1 路径一CubeMX 生成 Makefile 工程推荐新手第一步是在 CubeMX 里新建工程选择你的芯片型号。在 Project Manager 选项卡里找到 Toolchain/IDE 下拉框选择Makefile。这个选项是让 CubeMX 生成一套可以直接用 make 编译的工程结构里面已经有完整链接脚本和编译依赖。生成完以后用 VSCode 打开工程文件夹。VSCode 里需要装几个插件我个人觉得最低配置是C/C微软官方那款Cortex-DebugSerial Monitor或者Serial Plotter看串口输出用如果不想老点鼠标可以装Tasks Shell Input之类的辅助插件然后用CtrlShiftB打开任务配置创建一个编译任务。最简单的方式是在.vscode/tasks.json里写{ version: 2.0.0, tasks: [ { label: Build, type: shell, command: make, args: [-j], group: { kind: build, isDefault: true }, options: { cwd: ${workspaceFolder} } } ] }前提是make和arm-none-eabi-gcc已经在系统 PATH 里。我用的是 STM32CubeCLT 自带的工具链路径加进去以后命令行里直接执行make就能编译。不需要把 CubeIDE 也加进 PATH因为 CubeIDE 内部集成的工具链路径里带着版本号每次 CubeIDE 升级路径都会变维护起来太麻烦。编译成功以后工程目录下会生成.elf、.bin、.hex文件其中.elf包含了调试信息Cortex-Debug 需要用到它。3.2 路径二STM32CubeCLT ST 官方 VSCode 扩展如果你觉得手动配 tasks.json 还是麻烦2023 年底往后有了更省事的方案ST 官方推出了 STM32CubeCLT这不是一个 IDE而是一套命令行工具集合里面打包了 arm-none-eabi-gcc、OpenOCD、STM32CubeProgrammer CLI 和 GDB。它的出现就是为了配合 VSCode 插件使用的。在 VSCode 扩展市场搜STM32 VS Code Extension找到 STMicroelectronics 发布的那款装上。这个扩展可以读取 CubeMX 生成的.ioc文件直接在 VSCode 里打开外设配置界面也能自动识别工具链路径一键编译、一键烧录。如果你是从零开始的新手这条路径比我手动配置更平滑因为它把编译任务和调试任务都预先接好了。我个人一开始用的是手动 make 的方式后来项目多了以后也装了官方扩展。两者的区别在于手动方式能让你理解整个编译链路的每一环适合排查问题扩展方式胜在开箱即用适合不想看那些“无聊细节”的人。3.3 编译任务配置与 USER CODE 保护区域不管用哪种路径有一个铁律必须遵守CubeMX 重新生成代码时你自己的业务代码必须写在USER CODE BEGIN和USER CODE END注释之间。比如/* USER CODE BEGIN 0 */ void my_custom_init(void) { // 你的初始化代码 } /* USER CODE END 0 */CubeMX 的代码生成器遇到这两个注释之间的内容会原样保留一旦你写在区域外面重新生成代码的时候直接被覆盖找都找不回来。这是切到 VSCode 开发后最容易犯的错误因为没有了 CubeIDE 图形界面里的代码生成提示很多人改文件的时候根本意识不到哪些区域是“保留区”。4. Cortex-Debug 与 OpenOCD 联调launch.json 里每个字段都不白给4.1 launch.json 实例与字段解释调试配置是整套环境里最核心也最容易出问题的一环。创建.vscode/launch.json写入下面的配置这个配置我在 STM32F103、F407、F429 上都实测过{ version: 0.2.0, configurations: [ { name: Debug STM32, type: cortex-debug, request: launch, cwd: ${workspaceFolder}, executable: ./build/test.elf, servertype: openocd, serverpath: C:/ST/STM32CubeCLT/OpenOCD/bin/openocd.exe, configFiles: [ interface/stlink.cfg, target/stm32f1x.cfg ], interface: swd, device: STM32F103C8, svdFile: ./STM32F103.svd, runToEntryPoint: main, gdbPath: C:/ST/STM32CubeCLT/GNU-tools-for-STM32/bin/arm-none-eabi-gdb.exe } ] }逐条解释一下关键字段executable指向编译生成的.elf文件。注意这里路径要和 Makefile 输出目录一致CubeMX 生成的 Makefile 默认输出到build/目录。servertype写openocdCortex-Debug 才会用 OpenOCD 作为 GDB Server。serverpath是 OpenOCD 可执行文件的绝对路径。如果 OpenOCD 已经加入系统 PATH也可以省略这个字段但我建议显式写出来避免以后装了别的工具改了 PATH 导致找不到。configFiles数组指定 OpenOCD 要加载的脚本。第一个是接口配置第二个是目标芯片配置。svdFile是可选字段强烈建议加上。SVD 文件描述了芯片所有寄存器的地址和位域定义加载后调试时可以直接查看外设寄存器的值省去手动打开参考手册的时间。STM32 各系列的 SVD 文件在 CubeIDE 安装目录里能找到也可以从芯片厂商的官方包中获取。runToEntryPoint填main调试启动后会先自动停在 main 函数入口不需要手动设置断点。4.2 OpenOCD 需要哪两个配置文件为什么要分两个文件因为 interface 和生产环境里的“适配器硬件”相关target 和具体的芯片型号相关。interface/stlink.cfg是 ST 官方根据调试器类型定义的脚本里面包含了 ST-Link 驱动初始化和传输模式选择。target/stm32f1x.cfg是针对 STM32F1 系列芯片的脚本里面包含内核类型、片内 Flash 大小、RAM 起始地址、复位配置等。这种分离方式的好处是灵活。你今天用 ST-Link明天临时换一个 CMSIS-DAP 调试器只需要把stlink.cfg换成cmsis-dap.cfgtarget 文件不用动。这也是为什么我强烈不建议把两个文件整合成一个私有配置那是给自己埋雷。OpenOCD 启动时后面的 log 一般会打印这些信息Info : STLINK V2J38S7 Info : Target voltage: 3.3V Info : clock speed 4000 kHz Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints看到这几行基本说明连接成功了。如果卡在某一步就是在对应的环节出了问题。4.3 SVD 文件、断点与运行控制的小技巧调试时我习惯在 VSCode 的监视窗口添加几个关键寄存器比如RCC-CR、GPIOA-ODR。有 SVD 文件后这些寄存器会显示成可读的位域列表方便查状态。断点方面除了普通断点STM32 的调试单元还支持硬件观察点。在变量变化时触发中断的场景很有用比如怀疑某个全局变量被未知代码修改右键变量选择“Add to Watch”再右键设硬件观察点芯片会在变量被写入时停下。这个功能在实时系统里排 bug 特别高效。5. OpenOCD 高频报错排查target not found、debug authentication、flash timeout、写保护锁死5.1 先从整体上理解OpenOCD 连接不上时在抱怨什么这一节我把网络热词里那些高频报错一次性捋清楚。很多人一看到Error: no stm32 target found! if your product embeds debug authentication就直接懵了其实这个报错信息拆开来理解就好no stm32 target found表示 OpenOCD 和 ST-Link 能正常通信但 SWD 总线上没有扫描到预期的 STM32 内核。后半句提到的 debug authentication 是针对较新芯片比如 STM32H5、U5、L5、C0 等的保护机制提示你如果用到了调试认证功能需要用对应的解锁脚本。对于绝大多数 F1/F4 用户重点排查的是前半句也就是“为什么 SWD 总线上找不到内核”。5.2 no stm32 target found / debug authentication接线、复位、RDP 的排查链路我的排查顺序固定是这样的从硬件到软件每步都有对应的现象检查 SWD 接线和参考电压。SWDIO、SWCLK、GND 三根线是底线ST-Link 的 3.3V 输出如果想给目标板供电确认板子没有另外接电源避免两个电源打架。OpenOCD log 里会打印Target voltage如果显示 0V 或者明显偏低先处理电源问题。确认目标板是否处于低功耗模式。如果之前烧录的程序在跑低功耗SWD 引脚可能被配置成模拟输入或者直接关闭调试器自然扫描不到内核。这时候按住目标板的复位键在 OpenOCD 启动的瞬间松开让内核从复位状态开始被抓到。STM32CubeProgrammer 里对应的选项叫Under reset连接。查看 Option Bytes 里的读保护等级。如果芯片被设置了 RDP Level 1OpenOCD 连接时也会失败。用 STM32CubeProgrammer 看看当前保护级别如果显示读保护开启按 5.4 的方法降级。降低 SWD 频率。默认 OpenOCD 可能跑 4MHz如果线比较长或者接触不良4MHz 下时序容错太差。在配置文件里加一句adapter speed 1000单位 kHz再试一次。更新 ST-Link 固件。老版本固件连接新款芯片时会出现莫名失败。关于 debug authentication 这段如果你的芯片确实属于带 TrustZone 的新系列OpenOCD 提示需要 debug authentication script 时意味着芯片的调试口被 Secure 模式锁住了。这个不是一个纯软件能绕过的保护需要 ST 提供的认证机制配合。普通开发板一般不会遇到遇到的话检查是不是别人动过 Option Bytes。5.3 flash timeout频率、供电、写保护三分天下flash timeout. reset target and try it again是烧录阶段的高频报错。它的本质是 OpenOCD 写入 Flash 时目标芯片没有在预期时间内完成写操作。常见原因有三类SWD 频率过高。烧录和调试不同Flash 写入对时序更敏感。如果你把频率调到 4MHz以上在线长不佳的板子上容易触发 timeout。把adapter speed降到 1000~2000 基本能解决。目标板供电不稳。Flash 写入瞬间电流变化大如果 ST-Link 的 3.3V 输出能力有限电压跌落会导致写入失败。给目标板外接独立电源共地再试。Flash 区域被写保护。代码里可能调用过 Flash 写保护接口把某个扇区保护起来了。这时 OpenOCD 写那个扇区就会报 timeout 或者 verify error。处理方法是先用 STM32CubeProgrammer 把 WRP 清除再重新烧录。还有一个容易被遗漏的坑目标板的外部晶振没起振。很多 STM32 开发板默认使用外部晶振如果晶振焊接不良或者负载电容不对芯片可能在复位后没有正常时钟导致 Flash 控制器反应慢。这种情况连接可以成功但写入经常超时。换回内部 HSI 时钟试试如果问题消失就是晶振电路的问题。5.4 芯片锁死自救流程CubeProgrammer 解锁与全片擦除芯片被读保护锁死是最让人恐慌的场景尤其是辛辛苦苦写的代码突然连不上调试器。我的自救流程如下实测对绝大多数 RDP Level 1 的情况有效打开 STM32CubeProgrammer右侧选择 ST-LINK接口选 SWD勾选Under reset。点 Connect。如果连接失败再次尝试时按住目标板复位键不放点 Connect然后松开复位键。连接成功后在左侧菜单中选择Option Bytes页面找到Read Out Protection把级别从 Level 1 改为 Level 0界面里通常是一个下拉框选AA也就是 Level 0。点 Apply。工具会弹窗警告说降级会擦除整个 Flash确认。等待操作完成断开重新连接Flash 已经被清空芯片恢复正常。注意RDP Level 2 是永久保护没有降级通道一旦设置成 Level 2芯片直接变成“砖头”只能换新。所以量产前务必检查代码里不要误操作 Option Bytes。5.5 一张表收尾现象、原因、处理对照现象可能原因处理方式no stm32 target foundSWD 接线错误、低功耗模式、RDP 保护、频率过高检查接线、under reset 连接、RDP 降级、降低频率debug authentication提示新系列芯片启用了 TrustZone 或调试认证保护用 STM32CubeProgrammer 检查 Option Bytes必要时全片擦除flash timeout频率过高、供电不足、写保护、外部晶振异常降频到 1MHz、外接电源、清除 WRP、改用内部时钟设备管理器虚拟串口叹号ST-Link 驱动未正确安装卸载重装 STSW-LINK009 驱动写保护锁死代码调用了 Flash 写保护CubeProgrammer 清除 WRP 并重新烧录6. 进阶与量产序列号烧录、串口重映射、CAN BUSOFF 恢复、刹车输入6.1 批量烧录用 ST-Link 序列号区分多台设备开发阶段一块板子一个 ST-Link问题不大。但在产线上工位可能同时插多个 ST-LinkOpenOCD 不指定序列号时会默认选择第一个识别到的烧错板子是早晚的事。解决办法是在命令里指定 ST-Link 的序列号。先通过 STM32CubeProgrammer CLI 查看已连接的 ST-LinkSTM32_Programmer_CLI -c portSWD它会列出每个 ST-Link 的序列号。然后写一个批量烧录脚本每个 ST-Link 对应一个板卡#!/bin/bash SERIALS( 066FFF374747464867411745 066FFF3547474648674512A3 ) for sn in ${SERIALS[]}; do STM32_Programmer_CLI -c portSWD sn$sn modeHOTPLUG \ -w app.hex -v -rst doneOpenOCD 也支持在配置里指定序列号在openocd.cfg中加一行adapter serial 066FFF374747464867411745这种方式在自动化测试架上很实用多个工位同时烧录互不干扰。6.2 CubeMX 里给 USART1 做引脚重映射串口重映射是 CubeMX 里一个很基础但很多人第一次找不到入口的功能。以 STM32F103 的 USART1 为例默认引脚是 PA9TX、PA10RX但实际工程里这两个引脚经常被其他外设占用需要重映射到 PB6、PB7。操作步骤在 CubeMX 的 Pinout 视图里先启用 USART1选择 Asynchronous 模式。默认会自动分配 PA9/PA10。右键 PA9在功能列表里把引脚功能改掉释放 PA9 和 PA10。在右侧芯片图上查找 PB6 和 PB7左键点击在 GPIO 功能下拉列表里选择USART1_TX和USART1_RX或者直接选择USART1选项不同版本界面略有差异。如果 PB6/PB7 已经被 I2C1 或其他外设占用会提示冲突需要先释放相应引脚。重新生成代码CubeMX 会自动在MX_GPIO_Init里加上重映射相关的 AFIO 配置。代码生成后在main.c里调MX_GPIO_Init()之后可以确认一下GPIOA和GPIOB的初始化代码中有关 USART1 引脚的部分是否切换到了 PB6/PB7。需要注意USART1 在 F1 系列上的重映射由 AFIO 控制CubeMX 会自动处理不需要手写寄存器。但在老工程里从 PA9 改到 PB6如果你习惯在main.c的用户代码区手写了HAL_UART_Transmit相关调用引脚变了但外设句柄不变代码层面基本不会报错容易让人忽略硬件上的变化调试时务必确认示波器或者串口助手的接线确实接到了新的引脚上。6.3 CAN BUSOFF 恢复勾选项与手动复位CAN 总线出问题的时候节点会进入 BUSOFF 状态表现就是整个节点从总线上“消失”了不再参与收发。搜索stm32 cube busoff 恢复的同学多半是遇到了发送错误计数超过 255bxCAN 自动进入离线状态。STM32 的 bxCAN 提供了两种处理方式第一种是硬件自动恢复。在 CubeMX 的 CAN 参数配置里有一个Auto Bus-Off Management选项把它置为 Enable。对应的 HAL 结构体成员是AutoBusOff这样总线恢复是硬件自动完成的不需要软件干预。大多数应用场景用这个就够。第二种是软件手动恢复。如果你需要更精细的控制可以在 CAN 错误中断回调里检测 BUSOFF 标志然后做一次 CAN 外设重初始化void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_BOF)) { // 进入初始化模式 SET_BIT(hcan-Instance-MCR, CAN_MCR_INRQ); while (!(hcan-Instance-MSR CAN_MSR_INAK)); // 退出初始化模式重新进入正常模式 CLEAR_BIT(hcan-Instance-MCR, CAN_MCR_INRQ); while (hcan-Instance-MSR CAN_MSR_INAK); } }这段代码的核心逻辑是清掉 INITRQ 位让控制器从初始化模式回到正常模式。注意在这个恢复过程中节点会重新参与总线仲裁如果总线上还存在硬件故障很快又会进入 BUSOFF所以软件恢复只是权宜之计根本解决还是要查总线物理层。6.4 刹车输入、I2S 数播这类热词背后的真实场景搜索词里出现stm32 刹车大概率是电机控制场景。STM32 的高级定时器 TIM1/TIM8 有一个专门的Break Input功能外部电平变化可以立刻把 PWM 输出强制拉到安全状态不需要 CPU 干预。这个功能在电机驱动板上通常接的是过流保护信号或者急停按钮信号反应速度是微秒级的比中断处理还快。CubeMX 里配置 TIM1 的 Break and Dead-Time 选项使能 Break 功能选择极性高电平有效还是低电平有效然后调用HAL_TIMEx_ConfigBreakDeadTime初始化。触发后TIM1 的所有 PWM 输出引脚会被硬件强制置为安全电平同时 MOE 位清零。这个特性做产品非常有用但如果你只是在学习 PWM 输出暂时用不到知道有这个东西就行。至于stm32 数播 iis 设置属于音频方向。I2S 传输音频数据给外部 DAC 时除了常见的 I2S 标准格式配置最坑的是 MCLK 时钟输出。要输出主时钟给 DAC 芯片需要在 CubeMX 的 I2S 高级设置里把 Master Clock Output 打开同时在时钟树里正确配置 PLLI2S否则 DAC 要么没声音要么声音变调。调试这类问题建议先用逻辑分析仪看 I2S 的三根线SCK、WS、SD有没有数据再逐级查 MCLK 和时钟源。这些进阶点都有一个共同逻辑绝大部分问题不是你代码逻辑不行而是工具链和芯片配置的某个细节没对上。把 VSCode CubeIDE OpenOCD ST-Link 这条链路跑通之后你在工程里遇到的报错会集中在“芯片行为”和“硬件设计”两个层面反而更容易排查。我自己把这套环境前后迭代过三个版本从最早的纯命令行 GDB到后来用 Cortex-Debug 插件再到接入 STM32CubeCLT 官方工具链最大的教训就是不要一上来就照抄网上的配置文件。花半小时理解interface和target的区别、理解 OpenOCD 的报错信息、理解 RDP 保护机制之后遇到任何板子都能淡定排查。如果你刚好卡在某一步按上面的排查链路走一遍大部分问题十分钟内能定位。