☰
WSL2下STM32烧录实战:usbipd配置、ST-Link直通与OpenOCD调试
2026/10/20 20:39:10 网站建设 项目流程

在 WSL 里折腾 STM32 烧录,最让我绷不住的一个瞬间就是:终端里st-info --probe安静地执行完毕,没有任何输出;OpenOCD 则用一行红色报错告诉我"没有找到 ST-Link"。我低头看了一眼桌面,开发板电源灯亮着,ST-Link 也插在上面,USB 线换了三根,Windows 下一切正常,可 WSL 里就是像板子根本没插。这个场景反复出现两三次之后,我决定把整条链路彻底搞明白。这篇文章就是完整记录:怎么让 WSL 真正看到 STM32 开发板,怎么把工具链装好,怎么把固件写进 Flash,以及如果再遇到"我板子呢"这句话,应该按什么顺序一层一层排查。

这属于我"基于 STM32 的嵌入式 C++ 编程之旅"系列里的实操篇。前面几篇内容多为编译、链接脚本、内存布局,核心环境都在 WSL2 里。真正到了把代码烧到板子上这一步,才发现前几篇省下来的时间,在这一篇全都得还回去——因为 WSL2 默认和宿主机之间的 USB 设备是隔离的。

1. 为什么放着好好的 Windows 不用,非要在 WSL 里烧板子

1.1 开发环境的分裂:代码在 Linux,板子插在 Windows

我先交代一下背景:我这边的 STM32 项目,所有源码、构建脚本、静态检查、单元测试都放在 WSL2 里跑。原因不算复杂——Linux 侧处理文件、跑 CMake、Ninja、diff、sed、awk 这些工具确实顺手,构建过程和 CI 环境也更接近。而桌面仍然是 Windows,因为电路图、串口调试助手、抓波形之类的工具在 Windows 下更顺手。既然代码环境已经 Linux 化,烧录工具自然也希望能在 WSL 里直接调用,否则每次编译完还得把固件拷贝到 Windows 侧再另开软件去烧,流程一下子就断开了。

问题就出在这里:WSL2 共享文件系统、共享网络,唯独默认不共享 USB 设备。你从 WSL 里执行lsusb,看到的只是虚拟机自带的几个虚拟 USB 设备,插在主机上的 ST-Link 完全不在列表里。而 STM32 的烧录,本质就是通过 ST-Link 这个 USB 调试器去访问目标芯片的调试接口,因此 USB 设备访问权限就成了绕不开的头一道坎。

1.2 "我板子呢"现场还原:第一次在 WSL 烧录的翻车过程

第一次踩坑的场景是这样的:我先在 Windows 上确认了 ST-Link 驱动正常、设备管理器没有感叹号,板子也能通过 IDE 烧录。然后切到 WSL,编译出.bin,执行:

st-info --probe

没有任何反应。再试 OpenOCD:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

等待片刻,输出Error: No STLink found。这时候我第一反应是检查接线、供电、BOOT 引脚,全部正常。搞了半天才反应过来:问题根本不是"板子坏了"或者"接线松了",而是 WSL 里根本没有这块板子的 USB 设备节点,它当然连不上。

"我板子呢"这五个字,就是从这种场景里来的。它并不是说你真的丢了板子,而是你在 Linux 侧完全感知不到硬件的存在。要解决这个问题,核心是打通 USB 设备从 Windows 到 WSL2 的通道。

提示:WSL1 和 WSL2 在架构上有本质区别,WSL1 没有虚拟化边界,也不支持这套 USB 直通方案。动手前先用wsl -l -v确认发行版跑在 2 上,否则后续所有操作都会落空。

2. 让 WSL 真正"摸到"USB 设备:usbipd 与 udev 的完整配置

2.1 Windows 侧准备:安装 usbipd-win,确认 ST-Link 的 BusID

WSL2 访问 USB 设备,目前最成熟的办法是用一个叫 usbipd-win 的 Windows 服务。它的作用是把宿主机上的 USB 设备通过 USB/IP 协议分享给 WSL2 内核。安装用管理员身份打开 PowerShell,执行:

winget install usbipd

装完之后先不要急着进 WSL,先在 Windows 侧确认设备状态。执行:

usbipd list

输出大概长这样:

BUSID VID:PID DEVICE STATE 3-4 0483:3748 ST-LINK/V2 Not shared

这条输出里有三个关键信息:BUSID 是设备在 USB 总线上的编号,3-4这样的格式;VID:PID 里0483:3748常见的 ST-Link/V2 标识;STATE 当前是Not shared,意思是设备还完全由 Windows 持有。不同芯片、不同调试器版本的 PID 可能不一样,比如 ST-Link/V3 就不是这个值,所以实际以usbipd list的显示为准。

这里面有个特别容易犯的错:BusID 不是固定不变的,换一个 USB 口就会变,甚至同一台机器冷启动后的分配也可能不同。所以后面做脚本或者写备忘的时候,宁可多一步自动查询,也不要硬编码。

2.2 WSL 侧准备:先启动虚拟机,再检查内核与基础工具

执行 attach 之前,最好先让 WSL2 跑起来。很多人在新款 usbipd 上遇到的第一个报错就是The WSL 2 virtual machine is not running,说白了就是 WSL 还没启动,设备没法挂进去。可以先执行一条简单命令把虚拟机拉起来:

wsl -e true

然后再进入发行版,确认基础工具在。lsusb如果没装,先补上:

sudo apt update && sudo apt install -y usbutils

这个时候执行lsusb,你大概率还是看不到 ST-Link 的,这是正常的,因为设备还在 Windows 侧。另外,确认 WSL 内核版本也不多余,执行:

wsl --version

内核太老会缺一些 USB-over-IP 相关的支持,如果后面 attach 出问题,第一步就是wsl --update把内核和 WSL 组件更新到较新的版本。

2.3 绑定与挂载:bind 和 attach 是两个不同动作

usbipd 的使用里,bind和attach是最容易搞混的两个操作,它们干的完全不是一回事。

bind是告诉 Windows:"这个设备接下来允许被共享出去",它会修改设备在 Windows 侧的驱动绑定关系。这条命令需要管理员权限:

usbipd bind --busid 3-4

执行完再看usbipd list,STATE 会变成Shared。bind是一次性的,之后重启 Windows 它依然是 Shared 状态,不需要重复执行。

attach是把当前已经允许共享的设备动态挂载到正在运行的 WSL2 里。这个动作每次 WSL 重启之后都要重新做:

usbipd attach --wsl --busid 3-4

执行完再回到 WSL 里运行lsusb,应该能看到这样一行:

Bus 001 Device 002: ID 0483:3748 ST-LINK/V2

看到这行,才意味着设备真正"进了"WSL。注意,attach 是临时状态,WSL 关机、Windows 重启、设备拔插之后,设备会重新回到 Windows 侧。这个"不长久"的脾气是日常使用里最大的麻烦,后面我会专门讲怎么用脚本应对。

2.4 最后一公里:udev 规则与设备访问权限

lsusb能看到设备,不代表当前用户就一定能读写设备节点。第一次搞定 attach 之后,我紧接着就遇到了 OpenOCD 报Error: open failed,问题就出在/dev/bus/usb下面设备节点的权限上。

Linux 桌面发行版一般会通过 udev 规则自动给调试器设备放权限,但 WSL2 默认环境下对 passthrough 设备不会自动做这个动作。解决办法是自己补一条规则,在 WSL 内创建/etc/udev/rules.d/49-stlink.rules,内容如下:

SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3748", MODE="0666", GROUP="dialout"

然后重新加载规则:

sudo udevadm control --reload-rules sudo udevadm trigger

不过这里有个前提:WSL 里的 systemd 和 udev 得正常运行。如果你的 WSL 版本较旧、还没启用 systemd,规则可能根本不会生效。最省心的办法是在/etc/wsl.conf里加上 systemd 支持:

[boot] systemd=true

改完执行wsl --shutdown重启 WSL,再重新 attach,然后再验证权限。这一步做完,烧录环境的基础就算真正打好了。

3. 烧录工具的选择与安装:stlink-tools 与 OpenOCD

3.1 两套工具,两条路线

设备通道打通之后,接下来是工具。STM32 烧录的主流工具是两套:stlink-tools和OpenOCD。

stlink-tools是针对 ST-Link 调试器的专用工具集,核心命令是st-info、st-flash、st-trace,使用门槛最低,安装后直接敲命令就能烧录。OpenOCD是通用开源调试器,覆盖面广,能烧录、能跑 GDB 调试、能执行各种自定义脚本,适合更复杂的场景。

我的分工很明确:快速验证固件就烧录用st-flash;涉及调试、读寄存器、检查 Flash 内容或者做产测脚本时用 OpenOCD。如果你刚开始接触,完全可以从简单的st-link-tools入手,遇到需要调试时再上 OpenOCD。

能力stlink-toolsOpenOCD
安装方式apt install stlink-toolsapt install openocd
支持的调试器ST-Link 为主多种调试器
烧录 bin支持支持
烧录 elf/hex支持 bin 为主更灵活
GDB 调试不直接提供内置 GDB 服务
高级操作有限脚本化、可定制

3.2 apt 源里的版本可能不够新,先别急着追求最新

Ubuntu/Debian 默认软件源里的stlink-tools和OpenOCD版本,通常落后于芯片生态的更新速度。比如 STM32G0、H7 这类比较新的芯片,或者某些新出的调试器,老版本 OpenOCD 可能识别不到,现象是"调试器能识别,但连接目标芯片失败"。遇到这种问题,先别怀疑硬件,先看版本:

openocd --version st-info --probe

如果确定要换新版本,我个人的建议是:先用 apt 装一个版本跑通全流程,卡在芯片支持问题上再考虑去获取更新的构建;不要一开始就给自己增加编译源码的复杂度。很多人第一步就跑去从源码编 OpenOCD,结果环境和依赖纠缠了半天,反而把核心流程耽误了。

3.3 WSL 侧没有"驱动"这个概念

WSL 下烧录,很多人的下一个疑问是:"我是不是要在 WSL 里装 ST-Link 的驱动?"答案是:不用。

Windows 侧装驱动,是因为 Windows 需要厂商提供的 USB 驱动来识别 ST-Link 设备。而 WSL 侧,设备通过 USB/IP 协议进入 Linux 内核之后,内核会把它识别成一个普通的 USB bulk 设备,并不需要额外安装什么驱动。你缺的只是设备节点权限和烧录工具本身。验证内核到底认没认到这个设备,可以执行:

sudo lsusb -v -d 0483:3748

如果能读出一大段描述符信息,说明内核已经识别了,问题基本只剩权限和工具版本。

4. 第一次真正烧录:st-flash 与 OpenOCD 的命令行流程

4.1 最直接的烧录:st-flash 三步走

我先假设你的构建系统已经把固件编译出来了。如果用的是 CMake,产物通常在build/目录下。在开始写之前,先确认 ST-Link 和目标芯片状态:

st-info --probe

如果输出类似:

Found 1 stlink programmers serial: ... openocd: interface/stlink.cfg flash: 524288 (0x80000) pagesize: 2048 ...

说明通道正常、芯片也连上了。然后就可以烧录:

st-flash write build/firmware.bin 0x08000000

0x08000000是 STM32 内部 Flash 的起始地址。大多数裸机工程不需要改这个地址;但如果你写了 BootLoader,或者通过链接脚本把代码放在了别的偏移位置,就必须显式指定为链接脚本里定义的地址。烧完之后可以接着执行:

st-flash reset

让板子复位运行新固件。st-flash的烧录速度不快,但小体积固件基本是秒级完成。要注意不同版本的 stlink-tools 在命令行参数上略有差异,建议执行st-flash --help先过一眼。

4.2 用 OpenOCD 烧录,顺便为 GDB 调试打底

OpenOCD 的烧录方式更通用一点,先指定调试器接口和目标芯片配置文件。ST-Link/V2 的接口配置通常是interface/stlink.cfg,目标芯片按型号选,比如 F1 用target/stm32f1x.cfg、F4 用target/stm32f4x.cfg。一条完整的烧录命令:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ -c "program build/firmware.elf verify reset exit"

这条命令会把 ELF 文件直接写入 Flash,做一次校验,然后复位运行并退出。这里用 ELF 格式有个好处:OpenOCD 能自动解析段信息和地址,不需要你手动指定烧录地址。如果想进入调试会话,去掉最后面的exit:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

然后另开一个终端,用arm-none-eabi-gdb连接本机的localhost:3333,就能打断点、看寄存器、单步调试。当然,GDB 的使用是另一篇的话题,这里先把烧录链路跑通更重要。

4.3 把烧录动作固化进构建系统

命令行一条条敲多了,你很快就会想:"编译完能不能直接一条命令连烧录一起搞定?"当然可以。以 CMake 为例,在CMakeLists.txt里增加一个自定义 target:

add_custom_target(flash COMMAND st-flash write ${CMAKE_BINARY_DIR}/firmware.bin 0x08000000 DEPENDS firmware.bin )

之后只需要:

cmake --build build --target flash

编译加烧录就一起完成了。如果用的是 OpenOCD,把COMMAND换成对应的 openocd 命令串即可。这种方式比每次手动敲终端命令更不容易出错,尤其是当你同时维护多个板子、多个芯片型号的时候,把芯片类型和烧录命令固化在构建文件里,简直是一劳永逸。

5. "我板子呢?"完整排查链路:从 Windows 到 WSL 逐层定位

5.1 第一层:先从 Windows 侧设备和驱动找起

遇到"板子不见了",我的排查顺序永远是先看 Windows 侧,不要在 WSL 里乱试。打开设备管理器,看 ST-Link 设备是否存在、有没有黄色感叹号;如果 Windows 这边都不认识设备,后面全是白搭。

确认硬件正常之后,再跑一次:

usbipd list

这里有两个容易忽略的点。第一,如果你之前为别的设备(比如某个 USB 转串口工具)做过 bind,它可能把当前 USB 控制器占住,导致 ST-Link attach 不进去,这种情况会有Another session has already attached之类的提示。第二,某些 Windows 后台软件会独占 USB 设备,同样会让 passthrough 失效。所以排查时先确认设备在 Windows 侧是"空闲可共享"状态。

5.2 第二层:WSL 内的设备节点与权限检查

Windows 侧正常之后,切回 WSL 执行:

lsusb

如果看不到设备,直接跳到 5.3;如果看到了,再检查设备节点权限:

ls -l /dev/bus/usb/001/002

正常情况应该看到类似crw-rw-r--甚至crw-rw-rw-的权限。如果是crw-------且当前用户不是 root,那 OpenOCD 和 st-flash 都会访问失败。用id确认自己是属于dialout组,或者把 udev 规则里的 GROUP 改成你所在的组。WSL 下改完组之后,有些版本要求重开终端才会刷新,遇到权限没生效就先重开终端再试一次。

5.3 bind 和 attach 环节最常见的报错对照

usbipd 的报错信息在不同版本里措辞略有差异,但排查思路是一致的,我把最常遇到的几种整理成了一张表:

报错现象原因处理方法
The WSL 2 virtual machine is not runningWSL 虚拟机没启动先执行wsl -e true启动,再 attach
Another session has already attached设备已经被当前或另一个 WSL 会话占用执行usbipd detach --busid <BUSID>解绑,或重启 WSL
access deniedbind 或 attach 权限不足用管理员身份运行 PowerShell / 终端
No USB device with busid X was foundBusID 已经变了重新usbipd list查最新 BusID

这套组合拳打下来,能覆盖绝大多数 passthrough 失败的情况。如果还不行,就回到最笨但有效的方法:wsl --shutdown把 WSL 完全停掉,再重新启动、重新 attach。状态类问题,很多时候重置状态比查原因更快。

5.4 烧录工具的报错定位:是环境问题还是硬件问题

设备进了 WSL,权限也对了,可烧录工具仍然报错,这时候要区分两类问题:

报错大概率原因处理方式
st-info --probe无输出设备没进 WSL,或权限不足先用lsusb确认设备,再查节点权限
OpenOCDNo STLink foundpassthrough 失败重新 bind / attach
OpenOCDopen failed设备节点权限不够补 udev 规则,或临时用 sudo 验证
Failed to connect to target目标芯片供电、接线、复位异常检查 ST-Link 到目标板的 SWD 接线和电源

最后一种报错尤其容易让人误以为是 WSL 环境问题,其实它是板级硬件问题。把"WSL 里看不见设备"和"WSL 能看见设备但连不上芯片"分开看,排查效率会高很多。前者的焦点在主机环境,后者的焦点在板子本身。

6. 稳定使用一段时间后,我最想分享的几点建议

6.1 写个脚本对抗"热插拔失忆"

WSL 下的 USB passthrough,最让人烦躁的就是设备状态不持久。Windows 重启、WSL 重启、设备拔插,都可能让绑定关系失效。别靠记忆,直接在项目里放一个attach-stlink.sh:

#!/usr/bin/env bash set -euo pipefail BUSID="${1:-}" if [[ -z "$BUSID" ]]; then usbipd list echo "Usage: $0 <busid>" exit 1 fi usbipd attach --wsl --busid "$BUSID"

每次重启后,先跑usbipd list查一下最新的 BusID,然后执行脚本传参挂载。也可以直接在 Windows 侧写一个 PowerShell 函数,把 bind 和 attach 组合在一起调用。习惯了这套之后你会发现,日常遇到的"板子呢",绝大多数只是忘了 attach,而不是第一次没配置好。

6.2 固定 USB 口,减少无关变量

我给 ST-Link 专门固定了一个主板背板的原生 USB 口,不插前置面板,也不经过 Hub。这不是洁癖。第一,UsbID 和 BusID 的稳定性会好很多,不会因为换口导致配置失效;第二,背板原生口的供电质量通常比前置口稳定。ST-Link/V2 有时候会从 USB 取电给目标板供电,如果碰上目标板功耗稍高,供电不稳就会出现各种奇怪的烧录失败,报错模棱两可,非常消磨耐心。

6.3 什么时候我不推荐用 WSL 烧录

WSL2 + usbipd 这套方案,对低频烧录完全够用。但如果你每天要烧录几十次,或者需要长时间挂着 GDB 调试,我的建议是换原生 Linux,或者干脆回到 Windows 侧烧录。WSL 方案最大的价值,是把"编译环境彻底 Linux 化"和"不想装双系统"两个诉求撮合在一台机器里;但多一层 usbip 转发,就多一层故障面。赶进度的时候设备突然消失,那种心情经历过的人都懂。

所以在我的工作流里,WSL 主要负责编译、构建和日常快速烧录验证;真正高强度调试时,我会把整个工程放到原生 Linux 环境,或者退而在 Windows 侧用现成 IDE 烧录。手段不重要,思路清晰才重要。最后再分享一个小技巧:不管报错多诡异,先记录当时的lsusb输出、usbipd 状态和工具版本,再动配置。烧录环境的问题,大部分是"状态类问题",状态对了,一切自然就通了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询