news 2026/10/20 1:06:00

WSL2 USB/IP透传:STM32开发板OpenOCD烧录完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WSL2 USB/IP透传:STM32开发板OpenOCD烧录完整指南

"P" 盘里编译好的固件在 WSL 下静静躺着,开发板插在 USB 口上,Windows 的设备管理器里能清楚看到那个设备,但你在 WSL 里敲lsusb却什么都搜不到。这时候冒出那句“我板子呢?”太正常了。这期要解决的问题,就是让 WSL 能真正“摸到”STM32 开发板,并且完成烧录这条链路。你可以在 WSL 里写代码、编译、调试,再把固件直接烧进去,不用切回 Windows 侧用图形界面工具倒一手。

前面几期我们讲了嵌入式 C++ 的工程构建、Makefile 组织、链接脚本这些东西,都是在 WSL 里跑编译工具链完成的,到烧录这一环很多人会卡住。因为 WSL2 本质上是一个轻量虚拟机,USB 设备默认不会直接出现在里面。但这并不代表无解,社区里早有一套成熟方案,核心是 USB/IP 协议转发。搞清楚这条链路之后,烧录其实就是一条 openocd 命令的事。

1. 先搞清楚:为什么 WSL 里“找不到板子”

1.1 WSL1 与 WSL2 的本质差异

WSL1 走的是系统调用翻译层,Windows 和 Linux 的进程能互相调用,对硬件的访问方式也比较特殊。串口设备在 WSL1 里可以勉强通过 /dev/ttyS* 映射到 Windows 的 COM 口,但 USB 设备整体转发这件事它做得很别扭。

WSL2 则完全不同,它跑在一个真正的轻量虚拟机里,有自己的 Linux 内核。好处是几乎完整的 Linux 兼容性,OpenOCD、GCC、GDB 这些工具跑起来和原生 Linux 没区别。坏处也随之而来:虚拟机默认没有 USB 控制器直通能力,你在 Windows 宿主机上插的开发板,虚拟机内部根本看不到。

这就是“我板子呢”这个问题的根源。不是你的开发板坏了,也不是 WSL 配置错了,而是 WSL2 和 USB 设备之间存在一层隔阂——虚拟化边界。

1.2 解决思路:把 USB 设备“借”进 WSL 里

要打通这层隔阂,最早有人用 VirtualBox/VMware 里的 USB 直通功能,但那是在完整虚拟机里。WSL2 虽然是虚拟机,但它不提供常规的图形化 USB 过滤配置界面,所以需要另一条路。

现在社区里普遍采用的方案是usbipd-win。它的原理是 Windows 侧装一个 USB/IP 服务端,把物理 USB 设备通过 USB/IP 协议共享出去;WSL2 内核里内置了 USB/IP 客户端支持,可以像网络设备一样把远程 USB 设备挂载进虚拟机。听起来很绕,但其实就是“网络传 USB”。它在 Windows 侧把 USB 设备的控制权转交给 WSL,WSL 里随后出现对应的设备节点。

后面我会把这套东西从安装到烧录完整走一遍。不过先说结论:这个方案在 Win10 21H2 及以上、配合新版本 WSL 内核,实测用起来很稳。

2. 烧录前的工具清单与选型

2.1 烧录流程到底需要什么

先把烧录这件事拆开看。你要把一个编译好的固件写进 STM32 芯片,至少需要三样东西:

  1. 编译产物,也就是.elf/.bin/.hex文件,前面几期的工程构建流程已经生成了。
  2. 调试器硬件,也就是常见的 ST-Link,或者开发板上板载的 DAP-Link/CMSIS-DAP 调试器。它是 USB 设备,负责把 PC 的指令翻译成 SWD 或 JTAG 时序信号,进而操作芯片内部寄存器、写入 Flash。
  3. 烧录软件,它负责电脑和调试器之间的通信,并且把固件文件解析成 Flash 编程操作。

这三者缺一不可。很多人以为烧录就是“把文件拖进去”,但实际上中间还有一个协议转换的过程。如果烧录软件不支持你的调试器型号,或者调试器固件版本太旧,都会导致失败。

2.2 工具选型:为什么这次选 OpenOCD

Windows 侧有不少成熟的烧录工具,图形化界面点几个按钮就能完成烧录。但在 WSL 里,图形化工具基本帮不上忙,更麻烦的是它也破坏了命令行工作流的连贯性。既然前面的编译、构建都跑在 WSL 里,烧录这一步也应该在 WSL 里用命令行完成,这样才能形成一条完整的自动化链路。

OpenOCD 是当前最合适的答案。它是一个开源的调试与烧录工具,支持大量不同的调试器硬件和芯片型号。它的优势体现在三个方面:

  • 跨平台,在 Linux 下原生编译运行,WSL 里直接 apt 安装即可。
  • 命令行接口友好,能嵌入 Makefile、CI 脚本,一次配置之后反复使用。
  • 不只是烧录,它还提供 GDB Server 功能,后续给 STM32 做断点调试也是同一套工具。

有人可能会问,芯片原厂的命令行工具不行吗?其实也可以,但通常需要单独下载 Linux 版本并处理许可证问题,而且不一定愿意为此在工程里引入额外私有工具。OpenOCD 一个东西覆盖了烧录和调试,我觉得对嵌入式 C++ 开发流程来说是更清爽的选择。

3. USB 设备透传:把开发板接入 WSL

3.1 安装 usbipd-win

在 Windows 侧打开管理员权限的 PowerShell 或终端,执行:

winget install usbipd

如果没有用 winget,也可以从 usbipd-win 项目的发布页下载 MSI 安装包。安装完成后,需要确认两件事:

  1. WSL 内核版本较新。在 WSL 里执行wsl --version,如果内核版本比较旧,先去 Windows 设置里执行一次wsl --update。
  2. Windows 版本建议在 Win10 21H2 或 Win11 以上,低版本系统对 USB/IP 的支持不完整。

有个很容易忽略的点:安装完 usbipd 之后,需要重新打开一个管理员终端让它生效。不要图省事沿用旧终端窗口,否则后面执行usbipd命令会报“无法识别”。

3.2 绑定并附加 USB 设备

先把开发板插到电脑 USB 口,然后打开 Windows 侧管理员 PowerShell,执行:

usbipd list

输出结果里会列出当前所有 USB 设备,每一行有 BUSID、设备名称和状态。找到你的调试器,比如 ST-Link 或者 DAP-Link,记下它对应的 BUSID。注意这里要找的是调试器的设备,不是串口设备,两者有时会同时出现。

然后执行绑定:

usbipd bind --busid=<你的BUSID>

bind 这一步的作用是把设备从 Windows 的默认设备管理逻辑中释放出来,允许它通过 USB/IP 协议共享。bind 之后设备在 Windows 设备管理器里会变化,这是正常的,不用慌。

接下来把它附加到 WSL 里:

usbipd attach --wsl --busid=<你的BUSID>

执行完这条命令后,WSL 里马上执行lsusb应该就能看到你的调试器了。如果lsusb里出现了类似 ID 0483:374b 的设备,说明 ST-Link 已成功透传;如果是其他 VID/PID,也都能在列表中找到一行对应项。

注意:attach 成功之后,Windows 侧的程序如果同时也在访问这个 USB 设备,可能会导致冲突。比如你在 Windows 上开着某个串口终端工具占用了串口,WD 或者烧录软件还开着,最好先关掉。

3.3 让设备在 WSL 启动后自动挂载

usbipd 的 bind 操作是系统级的,电脑重启后仍会生效,但 attach 操作是临时的。也就是说,每次重启电脑或重启 WSL 之后,你都需要手动在 Windows 侧执行一次 attach 命令,设备才会重新出现在 WSL 里。

经常烧板子的人肯定受不了这个重复劳动。一个不吃亏的办法是写一个 Windows 侧的启动脚本,开机时自动 attach。在 PowerShell 里新建一个计划任务,触发器设为“用户登录时”,操作设为执行:

usbipd attach --wsl --busid=<你的BUSID>

还有一种做法是写一个简单的.cmd文件放在启动目录里,双击或开机自动运行。我个人的习惯是做个flash.ps1脚本,在里面先自动执行 attach,再切到 WSL 里调用 make flash,一条命令完成设备接入和烧录,后面会展开讲。

4. 动手烧录:一次完整的实践

4.1 准备编译产物

假设你的工程目录已经构建完成,生成的固件文件叫build/firmware.elf。如果你只有.bin或.hex文件,OpenOCD 也都支持,但.elf文件里带有调试符号和段信息,后续做调试体验更好,所以建议工程里默认生成.elf。

进入 WSL 工程目录,先确认文件存在:

ls -l build/firmware.elf

如果你的 Makefile 还没做烧录目标,先手工执行 OpenOCD 命令把链路走通,再回填到 Makefile 里。

4.2 OpenOCD 烧录命令逐行拆解

OpenOCD 的烧录命令基本结构是这样:

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

这段命令看着短,但每个部分都有讲究。

interface/stlink.cfg指定调试器接口配置文件。OpenOCD 把“调试器硬件”和“目标芯片”分成两层配置:interface 层描述你手里的调试器,target 层描述你正在烧的芯片。如果你用的是板载 DAP-Link 调试器,那么 interface 文件要换成cmsis-dap.cfg。这里如果选错了,后面一定会报连接错误。

target/stm32f4x.cfg是芯片型号配置文件。我这边例子用的是 STM32F4 系列,如果是 F1 系列就换成stm32f1x.cfg,F0 系列用stm32f0x.cfg。选 target 文件时要注意:它不只是告诉 OpenOCD 芯片叫什么名字,还包含 Flash 算法、扇区大小、RAM 地址等关键参数。选错型号会导致烧录时地址错乱,芯片直接进入 HardFault。

program子命令后面的参数依次是:

  • 固件文件路径
  • verify:烧录完成后从芯片 Flash 读回内容做比对,确认数据没有写歪
  • reset:烧录完成后复位 MCU,让程序开始运行
  • exit:烧录完成后退出 OpenOCD 进程

这条命令是“写完校验、复位运行、自动退出”,很适合日常开发。如果你只想先把固件写进去先不运行,可以把reset去掉,改成program firmware.elf verify exit。

执行时经常会遇到权限问题。如果报Permission denied或者无法打开设备节点,先用sudo openocd ...试,能用就说明是当前用户对 USB 设备节点没有访问权限。后面我会讲怎么用 udev 规则根治这个问题。

4.3 验证烧录结果

烧录完成后,肉眼可见的验证方式有几个:

  1. 看 OpenOCD 的输出日志。如果verify通过,日志里会有类似verified的字样,出现这个基本可以确认 Flash 内容正确。
  2. 看开发板运行状态。如果固件控制了一个 LED,复位后 LED 按预期闪烁,说明烧录和运行都正常。
  3. 读回 Flash 对比。有些场景下你怀疑烧录过程有异常,可以用 OpenOCD 的dump_image命令把 Flash 内容导出来和原始文件做 md5 对比。

如果烧录成功但程序不运行,优先怀疑复位操作没生效,可能是复位引脚配置问题,也可能是reset命令里的 halt 状态没处理干净。这种情况可以在 program 后再手动执行一次:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "init; reset run; exit"

强制让目标芯片进入运行状态。

5. 常见问题与排障速查

5.1 设备“附加成功”但 lsusb 看不到

这个问题出现频率很高。明明 Windows 侧 usbipd attach 没有报错,但 WSL 里lsusb输出还是老样子。

首先确认lsusb是不是执行得太快了。attach 命令执行完成和 WSL 内设备节点创建之间有一点点延迟,等一两秒再执行。如果还是看不到,执行dmesg | tail -n 50看内核日志。常见的报错是 usbip 客户端协议版本不匹配,这几乎都是 WSL 内核版本太旧引起的,回到 Windows 侧执行wsl --update再试。

有时候是 WSL 里缺少 usbip 客户端工具。Debian/Ubuntu 系发行版可以执行:

sudo apt update && sudo apt install -y linux-tools-generic usbip

安装后重新 attach 一次。注意 apt 里的 usbip 版本不一定和内核完全对应,但多数情况下能正常工作。

5.2 权限错误与 udev 规则

WSL 里 OpenOCD 打开设备时报权限不足,最省事的办法是用 sudo 跑。但如果团队里多个人共用一台开发机,或者你不想每次都在 sudo 和普通用户之间切换,可以配置 udev 规则。

在 WSL 里编辑/etc/udev/rules.d/99-stm32.rules,写入类似下面这样的内容:

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

这里的0483是 ST-Link 常见的厂商 ID。规则写完后执行:

sudo udevadm control --reload sudo udevadm trigger

然后拔插一次 USB 设备再重新 attach。这里要强调一点:WSL2 对 udev 的支持没有传统 Linux 发行版那么完整,某些情况下规则不生效,此时不要死磕,直接用 sudo 跑 OpenOCD 也不丢人。开发调试的机器,安全模型本来就比生产环境宽松。

5.3 烧录失败或校验失败

烧录过程中最常见的三类错误:

  • 连接超时,OpenOCD 找不到调试器。先回到 Windows 侧看设备是否已被 attach,有时电脑休眠后 USB 链路会断开,重新 attach 一次就好。
  • 校验失败。多半是供电不稳或者 SWD 线接触不良,尤其使用杜邦线连接时,接触电阻会造成写 Flash 失败。换成短而粗的线,或者把 SWD 频率调低一点。
  • 芯片读保护开启。一些板子出厂带了 RDP 保护,OpenOCD 烧录时报 target 连接已锁定。需要用 OpenOCD 先执行 unlock 操作,或者通过调试器厂商工具解除读保护。不同系列芯片的处理命令不一样,建议查对应 target 配置文件的文档。

还有一点值得提:用 USB Hub 供电不足也会导致烧录失败。我遇到过开发板插前面板 USB 口时烧录不稳定,换到主板背部 USB 口马上就好。这类环境问题排查起来最费神,先从供电角度排除总没错。

5.4 串口看不到输出

固件烧进去之后,想通过串口看打印日志,这在 WSL 里经常卡住。如果你的开发板用的是 USB 转串口芯片(比如 CH340、CP2102),并且这个设备已经通过 usbipd attach 到 WSL,那么串口设备会出现在/dev/ttyUSB0这类路径下,直接用 picocom 或 minicom 打开就行:

picocom -b 115200 /dev/ttyUSB0

但如果你用的是 ST-Link 的虚拟串口,attach 之后一般会生成/dev/ttyACM0,波特率参数仍然设置成你固件里初始化的值。

看不到输出时,先执行ls /dev/ttyUSB* /dev/ttyACM*确认设备节点是否存在。如果节点存在但输出乱码,检查波特率是否匹配。如果节点根本不存在,那么大概率是 USB 转串口芯片没有被正确 attach,回到 usbipd list 里确认这个芯片是不是单独的设备,有时候它和调试器是同一个 USB 设备下的多个接口,有时又是独立的一个 USB 设备。

记住一个原则:在 Windows 侧打开过的串口终端一定先关闭,否则它会占用串口,attach 之后 WSL 里拿到的是被占用状态,OpenOCD 或串口工具都会报错。

6. 进阶:封装一套自己的烧录命令

6.1 Makefile 里的 flash 目标

命令行流程跑通后,下一步就是把烧录固化到工程里。在 Makefile 中追加一个 flash 目标:

OPENOCD := openocd OCD_FLAGS := -f interface/stlink.cfg -f target/stm32f4x.cfg .PHONY: flash flash: build/firmware.elf $(OPENOCD) $(OCD_FLAGS) -c "program $< verify reset exit"

以后每次写完代码只需要make flash,编译完成后自动烧录。这里有个细节:目标依赖里的$<只在 make 的规则里展开成第一个前置文件,也就是build/firmware.elf,这样固件路径不会散落得到处都是。

如果你的调试器型号固定、芯片型号固定,这套 Makefile 配置可以长期复用。换芯片系列时,只需要改OCD_FLAGS里的 target 文件即可。

6.2 一键完成 USB 附加与烧录

前面提到过,attach 操作每次重启后都要手动执行。把它一起收进自动化流程会更舒服。在 Windows 侧写一个 PowerShell 脚本prepare-flash.ps1:

$busid = "你的BUSID" usbipd attach --wsl --busid=$busid wsl -d Ubuntu -- cd /path/to/project && make flash

执行这个脚本时用管理员权限运行 PowerShell。如果你不想每次都要右键管理员运行,也可以改成启动计划任务。这么做的收益是:把“找板子、附加设备、编译、烧录”这些步骤压缩成一条命令。

如果你经常在多个开发板之间切换,可以在脚本里加一个参数,根据 BUSID 区分不同板卡,做成flash.ps1 -board dev1的样子。这样整个团队用起来都很方便。


关于 USB 透传这事,我在实际操作中踩过不少坑。印象最深的一次是在 WSL 里折腾半天 OpenOCD 总是连不上 ST-Link,最后发现是 Windows 侧的某个旧版编程软件一直占着设备,usbipd attach 之后权限交接出现隐藏冲突,把那个程序彻底退出再重新 attach 才解决。另外一个小技巧:如果你经常遇到 WSL 里 USB 设备掉线,可以优先检查 Windows 是否有自动更新在切换 USB 供电策略,禁用该 USB 端口的“允许计算机关闭此设备以节约电源”选项,设备稳定性能明显提升。烧录未必是嵌入式开发里最有趣的一环,但它一旦卡住,整个迭代节奏都会被打断。工具链从编译到烧录在 WSL 里完全打通之后,这套开发体验才算真正闭环。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 1:05:47

STM32智能体重秤实战:原理图+代码+仿真全开源

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 1:04:45

显示异常排查四域法:信号源、传输、接收与协同域诊断

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 1:03:14

信号链芯片国产化实测:ADC、AFE与低功耗MCU的替代实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 1:03:05

显示异常排查方法论:覆盖80%黑屏花屏闪屏问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 1:03:05

ESP32桌面状态屏硬件实战:从焊接点亮到Demo跑通

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 1:02:54

TMS320F28335 PGFA工程实战:电机控制与数字电源调参精要

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华