☰
WSL2下用usbipd-win实现STM32烧录的完整指南
2026/10/16 1:11:29 网站建设 项目流程

写这篇的时候,满脑子都是那句“我板子呢”。真不是段子,是某天晚上我在WSL里敲完代码,兴冲冲准备编译烧录,结果在终端里翻了半天,发现根本找不到开发板。当时的完整流程是这样的:代码在Linux环境里编译得顺顺当当,但烧录要用的工具链、驱动全在Windows那头,两边切换折腾了二十多分钟,板子还莫名其妙从系统里“消失”了。那一刻我才意识到,WSL用起来虽爽,但USB设备的访问问题从没真正被解决过,尤其是遇到STM32这种要靠调试器烧录的板子,问题就更明显了。

这篇文章是我自己的解决方案复盘,也是从“在WSL里烧录STM32”这个具体需求出发的完整实操记录。整个过程会涉及WSL1和WSL2的机制差异、usbipd-win的用法、ST-Link工具的选型与配置、常见权限问题的排查思路,还包括我踩过的不少坑。无论你是刚接触WSL的嵌入式初学者,还是被设备访问问题折磨过一阵子的老手,这篇内容都能帮你节约大量排查时间。

1. 为什么非要在WSL里烧录板子

先搞清楚动机。很多人问:你直接在Windows下烧录不好吗,非得折腾WSL?这个问题我认真想过,答案是我没得选,或者说,方便到没道理不用。

1.1 开发环境割裂的现实

做嵌入式C++开发,尤其是用CMake加GCC工具链这条路,最舒服的环境基本都在Linux侧。编译速度快、脚本习惯顺手、容器的隔离也干净,我平时的编辑、编译、静态检查几乎全在WSL里完成。但STM32的烧录工具,比如ST-Link的驱动、官方烧录软件,其官方支持主要面向Windows。这就形成了“编译在Linux、烧录在Windows”的割裂状态。

我后来试过不少办法:把生成的固件复制到Windows共享目录,再开一个PowerShell窗口烧录;或者干脆用VS Code的远程开发插件,通过Remote-WSL把开发和烧录都串起来。这些办法都能用,但每次都涉及文件导出、窗口切换和工具链切换,一旦需要反复调整代码、验证功能,这套流程就被放大成严重的效率瓶颈。

1.2 几种绕开问题的土办法

在遇到usbipd-win之前,我周围的开发者解决这个问题的思路大致有几类。

第一类是在Windows下直接用STM32CubeProgrammer烧录,烧完再回WSL继续开发。缺点很明显:切换成本高,而且没法很好地集成到自动化脚本里。

第二类是用OpenOCD在物理Linux机器上烧录,WSL里编译完再通过SSH拷贝到那台机器。这个方案绕开了Windows的驱动问题,但引入了一台额外机器,网络传输和硬件连接都成倍增加复杂度。

第三类是直接装双系统,彻底告别WSL。这个方案最彻底,但对于工作环境主要在Windows下的开发者来说,代价太大,很多日常任务、办公软件还是得回到Windows侧。

这些办法本质都是在避开“WSL无法访问USB设备”这个核心矛盾。

1.3 最终方案选型:usbipd-win设备共享

真正解决这个矛盾的工具是usbipd-win。它通过USB/IP协议,把Windows下识别到的USB设备“共享”给WSL2使用。你在Windows插上ST-Link,在WSL2里就能看到一个对应的USB设备,可以像在物理Linux机器上一样直接操作。

这套方案最大的优势是保持开发环境单一切换:代码在WSL里编译,烧录也在WSL里执行,调试器通过usbipd-win穿透到Linux环境。Windows这边只需要做一次设备绑定,剩下的事情全在WSL侧完成。

我最开始也担心这玩意儿不稳定,实际测下来,无论是普通的烧录还是OpenOCD连接GDB调试,延迟都低到感知不到烧录过程是通过网络协议转发。唯一需要注意的是WSL2本身是轻量虚拟机,设备挂载后如果WSL实例重启,设备的连接也要重新建立。

2. 先把工具链捋清楚:编译、烧录各司其职

在动手配置之前,建议先把工具链的职责分清楚。很多人烧录失败,是因为把“编译”和“烧录”这两个环节的工具混在了一起。

2.1 编译链与烧录链的分工

STM32开发通常涉及两个独立的链条。

编译链解决的是“把源代码变成机器码”:C++源码经过交叉编译器(比如arm-none-eabi-gcc)编译出目标文件,再通过链接脚本和CMake构建系统,最终生成烧录用的bin或hex文件。这个过程完全在WSL里运行,不依赖任何USB设备。

烧录链解决的是“把机器码写进芯片”:烧录工具通过调试器(如ST-Link)连接芯片的SWD或JTAG接口,把bin文件写入Flash。这个过程需要访问硬件设备,是整个WSL方案的核心难点。

搞清楚这个分工后,问题就变得很清晰:需要解决的不是编译,而是如何在WSL里访问到那个USB调试器。

2.2 三款烧录工具怎么选

在WSL环境下,我实际用过三款烧录工具,各有取舍。

第一款是st-flash,它自带ST-Link的Linux驱动,安装方便,命令行简单,适合快速烧录整个固件文件。缺点是调试功能有限,不太适合需要单步调试的场景。

第二款是OpenOCD,老牌的片上调试工具,功能非常全面,支持烧录、调试、RTT输出、Flash解锁等。缺点是配置复杂,需要写目标板配置文件。

第三款是STM32CubeProgrammer,它也有Linux版本,可以命令行烧录,但对WSL设备挂载的兼容性不如前两者稳定,我在某些芯片型号上遇到过连接失败的问题,后面会讲到具体表现。

我的建议是:日常快速烧录用st-flash,需要调试或做复杂Flash操作时用OpenOCD。后面我的配置也以这两个为主。

2.3 WSL1与WSL2的底层差异为什么有影响

这个点很容易被忽略,但在做USB设备共享时最关键。WSL1不是虚拟机,它是Windows内核兼容层上的Linux调用翻译,所以旧版本WSL1甚至可以通过Win32 API访问Windows侧的设备文件,看起来“好像能直接访问串口”。但WSL2是真正的轻量虚拟机,拥有自己的Linux内核,与宿主机Windows之间通过虚拟网络交换数据,所以WSL2默认根本看不到Windows侧插入的USB设备。

如果你还在用WSL1,部分操作确实能“歪打正着”跑通,但环境不够纯净。而WSL2才是真正适合嵌入式开发的Linux环境,也正因为它的虚拟化特性,才需要引入usbipd这类工具来桥接设备。我整个方案都建立在WSL2基础上,这也是官方推荐的做法。

3. 实操:把ST-Link设备弄进WSL2

这章是整个流程的具体步骤,按顺序操作基本上就能成功。我会把关键的输出、命令和注意事项都写清楚。

3.1 在Windows侧安装usbipd-win

这个工具本质上是一个Windows服务,把USB设备转换为IP资源,供WSL2挂载。

我用的是winget安装方式,管理员权限打开PowerShell执行:

winget install usbipd

安装完用管理员的PowerShell启动服务:

usbipd service start

这一步不需要重启WSL,但建议把WSL关闭再打开一次,确保环境干净。

安装完成后可以先用这个命令查看当前连接的所有USB设备:

usbipd list

输出会列出设备的BUSID、VID/PID和名称。STM32开发板通常通过ST-Link调试器连接,设备名会带有“ST-Link”字样,VID是0483,比如:

BUSID VID:PID DEVICE STATE 2-3 0483:3748 STMicroelectronics ST-Link Debug Not shared

注意看STATE字段,刚插入的时候显示的是“Not shared”。

3.2 绑定与挂载ST-Link设备

绑定这一步相当于告诉Windows:允许把这个USB设备共享给WSL。在管理员PowerShell里执行:

usbipd bind --busid 2-3

绑定后,如果板子拔掉重新插入,状态可能变成“Attached”或保持“Not shared”,但绑定状态已经保存到系统配置里,下次插入无需重复bind。

接下来将设备附加到WSL2:

usbipd attach --wsl --busid 2-3

attach执行后,设备状态会变成“Attached”。此时切到WSL2终端,执行:

lsusb

你应该能看到类似这样的输出:

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

看到这一条,说明设备已经成功穿透到WSL2,USB这条路通了。

注意:attach命令是在管理员PowerShell里执行的,如果当前用户路径下找不到usbipd,可能需要手动把安装目录加入PATH,或者直接用完整路径。另外attach成功后,WSL侧的lsusb要等一两秒才能看到设备,不必着急。

3.3 Linux侧的权限与udev规则

设备虽然出现了,但普通用户无权访问。直接运行st-flash会报类似这样的错误:

Failed to connect to target

原因在于设备文件的权限归属。默认情况下,/dev/bus/usb/002/003的属主是root,普通用户没有读写权限。

最简单的方式是直接用sudo运行烧录工具,但这治标不治本,每次都要输密码不说,还可能因为环境变量问题导致工具找不到配置文件。

正确的做法是配置udev规则。在WSL2里创建规则文件:

sudo nano /etc/udev/rules.d/99-stlink.rules

内容如下:

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

这里0483是ST-Link的厂商ID,3748是ST-Link V2的Product ID。不同版本可能有差异,可以用lsusb查看实际值。保存后执行:

sudo udevadm control --reload-rules sudo udevadm trigger

重新插入设备后,权限就自动变成普通用户可访问。

3.4 安装烧录工具并验证

WSL2里安装st-flash很简单,我用的是源码编译方式,因为Ubuntu仓库里的版本可能偏老:

git clone https://github.com/stlink-org/stlink.git cd stlink cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build sudo cmake --install build

安装完成后,可以在任意目录执行:

st-flash version

如果输出类似:

st-flash 1.8.0

说明安装成功。也可以直接跑一次探测,验证能否正常识别芯片:

st-flash probe

这个命令不需要烧录任何数据,只探测当前连接的芯片。

3.5 一键烧录脚本

为了把整个流程固化下来,我在项目根目录写了一个Makefile片段,把编译和烧录用脚本集成到一起。

flash: build/stm32_app.bin st-flash write build/stm32_app.bin 0x08000000

这样每次改完代码,直接执行:

make flash

编译和烧录一条命令搞定。我还在脚本里加了超时后自动重新挂载usbipd的处理,防止板子断电重连后设备没跟上。

4. 烧录过程中的坑与排查实录

这部分是最有价值的,基本是几次实战踩下来的记录。每一条都是我亲眼见过的报错和最终解决办法。

4.1 设备挂载成功但烧录工具不认设备

有一次lsusb能看到ST-Link,但st-flash probe一直提示找不到设备。排查了很久,发现是WSL2中新开的终端没有继承usbipd attach的环境信息。usbipd把设备附加到WSL2实例后,你必须在同一个WSL会话里访问设备。

解决办法很简单:关掉当前所有WSL窗口,重新打开一个新的WSL终端,然后再执行lsusb。如果还不行,就执行:

sudo usbipd detach --busid 2-3

在PowerShell侧重新attach,再开新终端。

重要提示:attach后设备从Windows侧消失,这是正常现象,不要误以为板子坏了。等你用完在PowerShell里执行usbipd detach --busid 2-3,设备才会重新出现在Windows设备管理器里。

4.2 权限问题汇总

权限导致的烧录失败是最常见的。除了udev规则不生效外,还有一个容易忽略的点:usbipd attach时如果以普通用户运行,WSL侧的设备文件可能还是root属性,此时即使有udev规则也不会自动刷新。

我排查这类问题时的顺序是:

  1. 先确认设备在lsusb中的路径,比如/dev/bus/usb/002/003。
  2. 检查该文件的权限:ls -l /dev/bus/usb/002/003。
  3. 如果属主是root且权限是660,说明udev规则没生效,执行sudo chmod 666 /dev/bus/usb/002/003,或者重新插拔设备。

后来我干脆写了个小脚本,在每次attach后自动设置权限,避免手工重复操作。

4.3 烧录失败与芯片写保护

如果出现这类经典提示:

stlink flash write error: Unknown error

或者OpenOCD报:

Error: target not halted

原因大多是芯片内部Flash已经写保护,或者是芯片处于低功耗模式无法进入调试状态。遇到这种情况,先尝试用OpenOCD解锁:

openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "init" -c "halt" -c "stm32f1x unlock 0"

解锁后重新烧录。如果是新板子,烧录一般不会有问题,这种问题多出在重复使用过的芯片上。

4.4 USB掉线与高速烧录失败

我还遇到过烧录到一半USB断开的情况。换成短而粗的USB线解决了,数据线质量差导致供电不稳的问题非常隐蔽。另外ST-Link驱动能力有限,如果板子上还有其他外设耗电,建议外接供电,别只靠ST-Link供电。

高速烧录模式下偶尔会失败,如果出现校验错误,可以在st-flash后面加参数降低速度:

st-flash --reset --connect-under-reset write app.bin 0x08000000

或者切换到低速模式,一般能解决大部分校验不稳定问题。

4.5 常见问题速查表

整理了一份基于实践经验的速查表,方便遇到问题时快速定位。

现象可能原因解决办法
lsusb看不到设备未attach成功管理员PowerShell执行usbipd attach,再开新的WSL终端
烧录工具提示无权限udev规则未生效sudo udevadm reload + trigger,或手动chmod 666
每次WSL重启需要重新attach设备没有自动重连写脚本在make flash时自动检测并attach
烧录到一半设备断开USB线材质量差/供电不足换线、外接供电、降低烧录速度
提示target not halted芯片写保护或进入低功耗用OpenOCD解锁,acyclic reset连接
attach后Windows找不到设备设备被attach到WSL正常现象,detach后恢复

4.6 一些独家心得

最后分享几条平时没人提但很实用的经验。

第一条是尽量不要用WSL官方文档里的挂载方式直接挂载USB驱动器,那和usbipd的USB设备共享是两码事。很多人被这个名字绕晕,把U盘挂载当成了调试器共享,白白折腾半天。

第二条是如果你有多块不同型号的板子,比如F1系列和F4系列,建议在Makefile里把机型作为参数传进来。我自己就干过把F4固件烧到F1芯片上的蠢事,校验过了才知道完蛋,结果要重新擦除再烧。

第三条是ST-Link设备的VID/PID不是固定不动的,新版ST-Link可能是374B或374E,不同批量生产的设备也可能出现不同PID。批量烧录或者换了新板子时,优先用lsusb确认一下实际ID再写udev规则,不要盲目照抄网上的配置。

写在最后的一点体会

折腾完这套环境之后,我最直观的感受是整个嵌入式的开发流变得顺滑了很多。每次改完代码,make完直接烧录,不用再跨窗口搬运文件,也不用担心驱动匹配的问题。最初为了“在WSL里烧录STM32”花掉的几个晚上,换来的是之后每次编译、烧录、调试都高度连贯的体验,这个账怎么算都值得。

最后再分享一个小技巧:usbipd的attach操作其实可以写到PowerShell的启动脚本里,或者做成一个开机自动运行的计划任务。只要板子插着,WSL第一次启动时设备就已经在Linux侧等着了。配合我在Makefile里做的自动检测,基本可以做到插上板子敲个make flash就完事。愿你也能尽早摆脱“我板子呢”的困扰,把时间花在更有价值的事情上。

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

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

立即咨询