恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
WSL2下STM32开发板USB映射与烧录实战:usbipd-win+OpenOCD方案
首页
资讯中心
/
WSL2下STM32开发板USB映射与烧录实战:usbipd-win+OpenOCD方案
WSL2下STM32开发板USB映射与烧录实战:usbipd-win+OpenOCD方案
发布时间:2026/10/16 4:12:27
1. 先说结论WSL2 里看不到板子不是板子坏了这个系列写到第七篇编译链路、CMake 构建在 WSL 里都已经跑顺了真正让我对着屏幕喊出“我板子呢”的是烧录这一步——WSL 里敲lsusb列出来的只有虚拟的 USB 控制器STM32 开发板的调试器明明插在电脑上却像消失了一样。先给结论在 WSL2 里看不到板子绝大多数时候不是板子坏了也不是调试器坏了而是 WSL2 根本没有把宿主机上的 USB 设备“转交”给 Linux 环境。很多嵌入式开发者跟我一开始的思路一样Windows 下装个 STM32CubeProgrammerWSL 里只负责编译烧录再切回 Windows 双击一下。这样确实能干活但每次编译完都要切换系统、打开 GUI、选文件、点烧录非常割裂。既然前面已经把工具链全部 Linux 化了烧录这关也应该在 WSL 里打通。这篇文章就是我踩完坑之后沉淀出的完整方案适合所有不想装双系统、想用 WSL 完成 STM32“编辑-编译-烧录-调试”闭环的人。1.1 WSL1 和 WSL2 在“USB 视图”上的本质差异先解释清楚为什么会出现这种“看不见”的情况这关系到后面所有排错思路。WSL1 是一个系统调用翻译层它没有自己的内核而是把 Linux 的系统调用翻译成 Windows NT 内核调用。在这种情况下设备模型在很大程度上沿用了 Windows 的驱动框架USB 设备挂载后WSL1 里访问/dev/ttyUSB0这类节点是通过 Windows 驱动间接实现的所以反而能比较“自然”地碰到宿主机设备。WSL2 就不一样。WSL2 是标准的轻量虚拟机运行在 Hyper-V 虚拟化平台上它有自己的 Linux 内核、自己的驱动栈、自己的/dev设备树。你插在 Windows 上的 USB 设备物理上连接在宿主机虚拟机里的内核不会自动看到它。这就像合租房里住在独立卧室客厅插座上插着充电器卧室里的你不可能直接摸到那个充电头除非有人拉一根延长线从门缝接进来。搞清楚这一点后面所有操作就都有逻辑了我们要做的就是把客厅插座上的设备通过一根“延长线”接到卧室里。1.2 usbip 这套“USB 延长线”是怎么工作的这根“延长线”的标准名字叫 USB/IPLinux 内核自带的协议。思路不算复杂宿主机操作系统把 USB 设备抽象成一个“可以被网络访问的资源”虚拟机里的操作系统通过内核模块把这个远程资源挂载成自己的 USB 设备。对 WSL2 里的程序来说它看到的就是一个本地 USB 设备读写、控制都能走标准 Linux 驱动。Windows 侧对应的服务端实现是一个叫 usbipd-win 的开源工具。它是 Windows 端的“服务器”负责把你的 ST-Link、USB 转串口、J-Link 这些设备通过 USB/IP 协议暴露出来WSL2 端则作为一个“客户端”把这个远程设备 attach附加进来。两者之间通过 WSL2 的虚拟网络通信所以你会发现 attach 时需要知道 Windows 的 IP 或者使用 usbipd 提供的封装命令。用生活类比再解释一遍Windows 是仓库管理员usbipd-win 是仓库物流系统WSL2 是远在另一个城市的工厂。你给物流系统发指令“把这盒芯片发过去”仓库那边打包、装车工厂收到货拆包后生产线上的工人觉得芯片就在本地仓库里。整个过程对上层应用完全透明。这也就是为什么我们不能在 WSL2 里直接打开 Windows 的某个 USB 设备节点而必须先 bind绑定放行再 attach挂载进来。1.3 先用三个命令确认踩的是不是这个坑在自己动手绑定之前建议先花一分钟确认问题方向确实是对的免得白折腾。第一步打开 Windows PowerShell普通权限就行插入你的 ST-Link 或者开发板自带的调试器然后输入usbipd list注意这一步需要你已经在 Windows 上安装过 usbipd-win 工具。如果还没装下一节会讲。这条命令会列出宿主机上所有 USB 设备以及它们的绑定状态。如果这里看不到你的板子说明问题更基础——可能是驱动没装好、USB 线有问题、板子没上电这跟 WSL 没关系先去设备管理器看有没有未知设备。第二步回到 WSL2 终端输入lsusb正常情况下如果从来没有 attach 过这里只能看到一些虚拟设备比如1d6b:0002 Linux Foundation 2.0 root hub找不到任何0483开头的设备。0483是 ST-Link 调试器的 USB Vendor ID后面跟着的是 Product ID比如374b。第三步看内核日志dmesg | tail -20如果完全没有任何 USB 插入事件说明设备确实没进来。如果你看到usb 1-2: new high-speed USB device number 3 using vhci_hcd一类的输出说明 USB/IP 已经生效只是设备列表显示方式和你预期的不一样。这三步做完基本就能确定是否需要在 WSL 里做 USB 设备共享。我第一回踩坑时就是直接跳到最后一步openocd烧录报错“找不到设备”绕了好大一圈才发现是压根没 attach。2. 从“看不到”到“看得到”usbipd-win 绑定实操与报错排查确认问题方向之后接下来的任务就很明确在 Windows 侧绑定设备把它 attach 进 WSL2。这节我把从安装到验证的完整流程写清楚每个步骤都附带我实际执行时的输出和踩坑记录。2.1 装好 usbipd-win并确认 WSL2 内核支持Windows 侧需要安装 usbipd-win。最省事的方式是用 winget 直接装winget install usbipd-win装完后需要开一个新的“管理员权限”的 PowerShell 窗口普通权限终端在执行 bind 和 attach 时会提示权限不足。安装过程中如果提示需要安装 USB/IP 驱动选择同意即可。接下来确认 WSL2 内核版本足够新。老版本内核可能缺少 USB/IP 相关的内核模块和用户态工具。建议直接更新到最新wsl --update更新完重新进入 WSL检查内核版本uname -r我当前的环境是 5.15 之后的内核USB/IP 的 vhci-hcd 驱动已经默认编译进去。如果你发现版本比较旧先更新再说。另外提醒一句WSL2 发行版本身也要保证是用 WSL2 模式跑的不是 WSL1可以用wsl -l -v查看。如果某个发行版还停在 WSL1整个 USB/IP 方案就不适用需要手动转换版本。2.2 标准三步list、bind、attach正常流程可以压缩成三步熟练之后十秒钟就能完成一次 attach。先在管理员 PowerShell 里查看设备usbipd list输出大概长这样BUSID VID:PID DEVICE STATE 4-2 0483:374b ST-LINK/V2 Not attached第一列 BUSID 是设备在 USB 总线上的编号第四列 STATE 表示当前是否被附加到了某个虚拟机。如果 STATE 显示Attached说明已经被 WSL 拿走了再看lsusb应该能发现设备。首次使用某个设备需要先 bind 一次。bind 的含义是把这个设备标记为“允许被附加”相当于在物流系统里给货物办了出库手续usbipd bind --busid4-2这一步只需要做一次设备会保持“已绑定”状态。但注意绑定的只是“允许”关系真正把它接入当前运行的 WSL2 实例还需要 attachusbipd attach --wsl --busid4-2在较新版本的 usbipd-win 中--wsl参数会自动完成 WSL 侧的挂载动作不需要你手动在 WSL 里敲usbip attach。如果这条命令执行后没有任何报错回到 WSL 终端验证lsusb这时候应该能看到类似这样的行Bus 001 Device 003: ID 0483:374b ST-LINK/V2看到这行字恭喜板子“进来”了。值得说明的是如果你用的 usbipd-win 版本较老或者你想手动走传统 usbip 流程也可以在 WSL 侧加载内核模块sudo modprobe vhci-hcd sudo usbip attach -r Windows的IP -b 4-2这种方式需要先安装 usbip 用户态工具比较折腾。建议直接用usbipd attach --wsl的封装命令减少变量。2.3 一张表讲完我这几天遇到的报错这几天的实操里我把能踩的报错基本踩了个遍整理成一张表方便你遇到问题时直接对号入座。报错或异常现象根因解决方案usbipd: error: 未找到设备或 list 里没有目标设备设备没插好、驱动未安装、USB 口供电不足换口换线查看 Windows 设备管理器确认驱动正常usbipd: error: 设备未绑定bind 步骤被跳过先执行usbipd bind --busid4-2attach 成功但 WSL 里 lsusb 仍看不到内核版本过旧或用了 WSL1执行wsl --update并确认发行版是 WSL2usbip: error: open /dev/devicesvhci-hcd 内核模块未加载执行sudo modprobe vhci-hcdattach 报 device busy / 设备被占用Windows 下某个程序正在独占该 USB 设备关闭 STM32CubeProgrammer、串口助手等程序重新 attachattach 后设备反复掉线USB 线质量差、前置 USB 口供电不稳换线或插到主机背面原生 USB 口我最常撞上的就是 Windows 侧有程序偷偷占着设备。比如之前用 STM32CubeProgrammer 烧过录程序虽然关了一半但后台的调试服务进程还没退出这时 attach 就会失败。解决方式也很朴素把相关软件全部退出或者在任务管理器里检查有没有残留进程干净了再 attach。2.4 复合设备与 Windows 程序占用的隐藏坑这里要单独说一个很多人忽略的细节ST-Link 这类调试器往往是一个复合 USB 设备。它不只有一个功能通常同时包含调试接口SWD/JTAG和虚拟串口。在 Windows 里你会看到它占用多个 COM 口在 usbipd list 里它显示成一行但底层其实是个包含多个 Interface 的组合体。这意味着当你 attach 整个设备之后不仅仅是烧录器进入了 WSL它的虚拟串口也会跟着进来。这本来是好事因为后面我们还要用串口看 printf 输出。但副作用是一旦 attachWindows 这边的串口号会立即消失。如果你在 Windows 侧开着串口监视软件那边会瞬间丢数据甚至导致 attach 报错。所以实操中要养成一个习惯attach 之前先确认 Windows 侧没有在占用这个设备。反过来也一样如果你拔掉板子回到 Windows 使用先确认 WSL 里没有程序还握着串口否则 Windows 侧也可能抢不到设备。还有一个观察某些开发板会把 ST-Link 和板载虚拟串口做成同一个 USB 设备你 attach 时会看到设备列表里多出两个 CDC 串口节点。这是正常现象别以为是自己操作错了。3. 烧录这步棋OpenOCD、st-flash 与 Linux 权限的最后一公里设备成功进入 WSL只是万里长征走了一半。烧录这件事本身在 Linux 下还有一层绕不开的权限问题以及工具链选型问题。我最后敲定的方案是 OpenOCD 为主、st-flash 为辅这节讲清楚为什么这么选以及实际烧录的一整套命令。3.1 为什么在线烧录选 OpenOCD 而不是 GUI很多从 Windows 开始接触 STM32 的开发者习惯了 STM32CubeProgrammer 的图形界面点几个按钮就能烧录。但在这个系列里前面几篇已经把工程构建完全改为 CMake GCC 命令行流程烧录环节如果再依赖 GUI整个自动化闭环就断了。OpenOCD 是目前嵌入式调试烧录场景里最“标准”的开源工具。它支持 ST-Link、J-Link、CMSIS-DAP 等多种调试器芯片支持列表里也能找到覆盖全系列的 STM32 配置。更重要的是它天然就是命令行驱动的可以很自然地和make flash、ninja flash这类构建目标结合写完代码一条命令编译加烧录不用切窗口。st-flash 是更轻量的选择来自 stlink 开源项目专门针对 ST-Link 调试器烧写 bin 文件非常快但功能相对简单。OpenOCD 则更像瑞士军刀除了烧录还能做调试、RTOS 感知、Flash 保护等高级操作。我的建议是两者都装日常快速烧 bin 用 st-flash需要精细控制或者挂 GDB 调试时用 OpenOCD。三款工具的对比也列出来供参考工具配置复杂度命令行友好度适用场景WSL 兼容性OpenOCD中高支持脚本化调试 烧录 自动化好st-flash低很高快速烧录 bin好STM32CubeProgrammer CLI较高中等重量级官方生态支持加密和选项字节可用但繁琐3.2 最小可用的烧录命令与参数拆解安装工具在 Ubuntu 系的 WSL 发行版里很简单sudo apt update sudo apt install -y openocd stlink-tools装完后确认版本openocd --version st-info --version如果你的 WSL 发行版软件源里版本太旧也可以从源码编译最新版但对绝大多数 F1/F4/G0 系列芯片来说发行版自带的版本足够稳定没必要折腾。真正烧录的时候我用 OpenOCD 的命令是这样openocd \ -f interface/stlink.cfg \ -f target/stm32f1x.cfg \ -c program build/app.elf verify reset exit逐个参数拆开看-f interface/stlink.cfg告诉 OpenOCD你手上用的调试器是 ST-Link。如果你用的是 J-Link这里换成interface/jlink.cfg。如果你是某第三方开发板自带的 CMSIS-DAP 调试器换成interface/cmsis-dap.cfg。-f target/stm32f1x.cfg是目标芯片配置。F1 系列用这个F4 系列换成stm32f4x.cfgG0 系列用stm32g0x.cfg配置文件命名基本和芯片系列一一对应。-c program build/app.elf verify reset exit是 OpenOCD 启动后要执行的 TCL 命令序列。program表示烧录后面跟 ELF 文件路径verify烧完马上回读校验reset复位芯片让程序跑起来exit完成后退出 OpenOCD。这里我强烈建议保留verify烧录失败但校验通过能拦住我至少三次低级问题。如果只想快速烧个 bin用 st-flash 更直接st-flash write build/app.bin 0x080000000x08000000是 STM32 Flash 的起始地址bin 文件没有地址信息必须手动指定ELF 文件自带地址段所以 OpenOCD 的program命令不需要你手动指定地址。3.3 WSL 下的 USB 权限udev 规则与 systemd 的关系设备 attach 进来、命令看起来也没问题但 OpenOCD 一跑就报错最常见的是这两句Error: open failed libusb error: LIBUSB_ERROR_ACCESS这就是 Linux 的经典权限问题USB 设备节点默认属于 root当前普通用户没有读写权限。在原生 Linux 开发板或者桌面机上标准做法是写 udev 规则把设备节点权限放开或分配给指定用户组。WSL2 里思路一样但有一个前提要注意。先看你的 WSL2 是否启用了 systemd。新版 WSL 可以像正常 Linux 那样用 systemd 管理服务在/etc/wsl.conf里加一行配置即可[boot] systemdtrue改完在 PowerShell 里执行wsl --shutdown再重新进 WSL然后检查ps -p 1 -o comm如果输出systemd说明 udev 规则可以正常工作。如果输出还是别的进程说明 systemd 没启用这时 udevadm 可能不可用规则写了也不生效。在 systemd 可用的前提下创建规则文件sudo vim /etc/udev/rules.d/99-stlink.rules内容这样写SUBSYSTEMusb, ATTR{idVendor}0483, ATTR{idProduct}374b, MODE0666, GROUPplugdev SUBSYSTEMusb, ATTR{idVendor}0483, ATTR{idProduct}3748, MODE0666, GROUPplugdev然后重载规则sudo udevadm control --reload-rules sudo udevadm trigger我实际使用中发现WSL2 对热插拔设备的规则重载偶尔不那么灵敏最省心的做法是加完规则后直接把板子拔掉重插再重新 attach 一次。另外也可以把当前用户加进plugdev组sudo usermod -aG plugdev $USER改完用户组需要重开 WSL 才生效。如果你实在不想折腾 udev临时方案是直接 chmod 对应设备节点但 USB 设备节点路径每次插拔都可能变治标不治本不推荐长期用。3.4 烧录过程常见报错与验证手段第一次烧录成功之前报错几乎是必经之路。我整理几个高频错误Error: couldnt set the SCK frequency通常是 SWD 接线接触不良或者线太长降低时钟频率试一下。很多 ST-Link 板子默认 4 线 SWDSWCLK、SWDIO、GND、3V3少一根或者接触不好都会报告警。Error: target not halted一般是芯片处于低功耗模式或者供电不稳检查复位引脚和 BOOT0 引脚状态。如果之前烧过让芯片进入 stop 模式的程序可以先按住复位键再烧录。Error: unable to find a matching interface基本就是 interface 配置文件选错比如明明是 ST-Link 却用了 jlink.cfg。烧录成功的标志很明确OpenOCD 输出里会有** Programming Started ** ** Programming Finished ** ** Verify Started ** ** Verified OK ** ** Resetting Target **st-flash 烧录结束时也会打印Flash written and verified之类的信息。看到这个程序已经在板子上跑起来了可以顺手观察板载 LED 或者串口输出验证一下。这一刻的满足感还是很真实的。从编译链接全靠 WSL到烧录也终于不用切回 Windows整个开发闭环算是真正打通了。4. 拔了又插怎么办热插拔、自动绑定与串口观看 printf烧录打通了但如果只用一次随时都要手动敲命令那这套方案还是不够顺手。实际项目开发中板子会不停地插拔程序会反复烧录串口数据也要随时看。这节的几个经验能让“板子呢”变成“板子一直都在”。4.1 attach 关系的“有效期”和正确拔插顺序首先要明确一个现实attach 不是永久绑定的。WSL2 实例重启后之前的 attach 关系不会自动恢复板子拔掉再插上WSL 里同样不再持有设备。这不是 usbipd-win 的缺陷而是 USB 协议本身的特性——USB 设备在物理断开后就等于丢失重新枚举必须重新挂载。正确的热插拔顺序很重要。我的习惯是先物理插入板子等 Windows 环境识别完成。在 PowerShell 里执行usbipd list确认设备出现了。再执行usbipd attach --wsl --busid4-2把设备交给 WSL。反过来从 WSL 切回 Windows 使用前先确认 WSL 里没有程序占用设备然后执行 detachusbipd detach --busid4-2如果不 detach 直接关闭 WSL大多数情况下设备状态会变成Not attached但偶尔会留在Detached或者Attached的悬挂状态下次 attach 会报错。遇到这种状态可以先usbipd detach再重新 attach一般能解决。另一个经验是不要在一个终端里长时间握着设备。比如你开了 minicom 看串口又想在 Windows 那边用官方工具读芯片信息两边会抢设备表现为一边正常一边频繁报错。嵌入式调试是单资源的活谁占用得提前想清楚。4.2 做一个一键 attach 烧录的小脚本手动流程熟练之后我建议把它脚本化。最实用的是两个脚本配合一个在 Windows 侧负责 attach一个在 WSL 侧负责烧录。Windows 侧 PowerShell 脚本可以写成一个attach.ps1$stlink usbipd list | Select-String 0483 | Select-Object -First 1 if ($stlink -match ^\S\s(\S)\s) { $busid $matches[1] usbipd bind --busid $busid usbipd attach --wsl --busid $busid Write-Host Attached $busid } else { Write-Host 没找到 ST-Link先插板子 }这个脚本的核心思路是从usbipd list输出里过滤出包含0483的那一行提取 BUSID然后自动完成 bind 和 attach。注意它的局限如果你同时插了两块 ST-Link脚本只会选取第一块。更严谨的做法是在脚本外显式传入 BUSID或者通过 VID:PID 精确匹配目标设备。WSL 侧可以写一个flash.sh把“检查设备是否在”和“烧录”合并#!/bin/bash set -e if ! lsusb | grep -q 0483:374b; then echo 板子不在 WSL 里请先在 Windows 管理员 PowerShell 执行 attach 脚本 lsusb exit 1 fi openocd \ -f interface/stlink.cfg \ -f target/stm32f1x.cfg \ -c program build/app.elf verify reset exit把它放进工程目录配合 CMake 的add_custom_target就能做到make flash一条命令自动检查设备并烧录。我实际使用下来唯一需要记住的就是“如果报找不到板子先去 Windows 侧跑一遍 attach.ps1”。这里有个小细节lsusb | grep 0483判断的是 USB Vendor ID如果设备 attach 进来了但处于异常状态可能 ID 一样但烧录失败。脚本只是做个快速前置检查真正的校验还要靠 OpenOCD 的 verify 机制。4.3 串口监视是另一块雷区烧录完成只是第一步程序跑起来之后最常做的事情是看串口输出。很多基于 C 的嵌入式工程都会用 printf 重定向到串口在 WSL 里怎么看数据先回答一个高频问题在 WSL2 里串口号不是 Windows 的 COM3 或 COM5而是 Linux 风格的/dev/ttyACM0或/dev/ttyUSB0。ST-Link 的虚拟串口通常以/dev/ttyACM0出现USB 转串口模块比如 CP2102、CH340通常以/dev/ttyUSB0出现。前提是这些设备已经通过 USB/IP attach 进来了。看串口数据我推荐 minicom足够轻量sudo apt install -y minicom minicom -D /dev/ttyACM0 -b 115200-b 115200是波特率一定要和工程的 USART 配置保持一致否则全是乱码。也可以用 picocom参数更简洁picocom /dev/ttyACM0 -b 115200如果提示权限不足除了前面说的 udev 规则还可以把当前用户加进dialout组sudo usermod -aG dialout $USER这同样需要重开 WSL 生效。顺便多说一句minicom 退出时按CtrlA再按X很多人卡在这。picocom 退出是CtrlA再按CtrlX。串口数据观感上容易遇到两种坑。一种是 reset 刷屏因为 ST-Link 的虚拟串口跟随目标芯片的复位状态芯片一复位串口设备会重新枚举minicom 会断开。这不是配置问题把板子上的复位引脚接好或者程序里不要反复复位即可。另一种是设备节点在拔插后从 ttyACM0 变成 ttyACM1这是因为 Linux 内核枚举顺序变了用ls /dev/ttyACM*看一下再打开就行。4.4 把“我板子呢”变成六条排查口诀整个流程跑顺之后我再也没在群里喊过“我板子呢”因为每次出现问题我都会按一套固定顺序排查两分钟内定位。这里把最终的排查口诀分享出来Windows 侧先问一句usbipd list看板子在不在。不在就去查 USB 线和驱动。确认已 bind设备行有没有被绑定没有就usbipd bind --busid4-2。执行 attachusbipd attach --wsl --busid4-2看有没有报 busy。WSL 侧验证 lsusb能看到0483开头就说明设备进来了。看权限OpenOCD 报LIBUSB_ERROR_ACCESS就查 udev 规则和用户组。再烧录make flash或 OpenOCD 命令注意看 verify 是否通过。这个顺序从底层到上层每一步的失败都能直接指向前一步的原因。我自己 90% 的问题出在第 3 步——不是忘了 attach就是 Windows 某个程序偷偷占着设备。最后再分享一个小体会这套方案跑顺之后我在 WSL 里烧录的时间跟以前在 Windows 里几乎没差别多出来的几秒钟主要是等 attach。区别最大的是心态——以前编译完要切系统心里总有一条无形的边界现在一条终端搞定所有事C 工程的全链路自动化才算真正闭环。下一期如果继续写我会接着聊聊怎么把 GDB 调试也搬进 WSL那是另一个值得单独讲的故事。