将串口数据给进WSL再给进Docker——一次成功流水账(优化稿)

⚙️ 本文是一次实操记录:把物理串口设备的数据,先接入 Windows,再转发进 WSL,最后映射进 Docker 容器。以乐动单线激光雷达 LD14P(串口芯片 CH9102)为例,每一步都带验证。

背景与整体思路

平时主力是 Windows 电脑,偶尔需要 Linux 环境。Win11 可以安装 WSL,自带图形界面服务(WSLg),显示窗口没有问题;进一步在 WSL 中配置 Docker,就能直接在容器镜像里用 ROS 等工具。难点在于要同时读取串口数据——数据要穿过 三层驱动:

  1. Windows 串口驱动:让 Windows 认到 USB 转串口设备(COM 口)。
  2. USB/IP 转发:用 usbipd 把 USB 设备从 Windows 转发进 WSL。
  3. 容器映射:把 WSL 里的串口设备(如 /dev/ttyACM0)映射进 Docker。

下面按这三层顺序推进,每一层都先配置、再验证。

一、Windows 侧:安装串口驱动并验证

1.1 安装 Windows 串口驱动

安装 CH9102 对应的 Windows 驱动(CH343SER):

C:\Users\HongwuZhu\Downloads\激光雷达资料\串口驱动\CH9102\CH343SER(CH9102的windows驱动)\CH343SER\Driver

请添加图片描述

1.2 确认 COM 口

插入设备,在设备管理器查看是否新增 COM 口。

插入 USB 前:
请添加图片描述

插入 USB 后:
请添加图片描述

1.3 在 Windows 中测试雷达端口

运行 CLI 测试脚本,看是否有数据输出。如果有数据,说明连接和 Windows 驱动都没问题:

cd D:\Project\turtlebot3_training\LD14P
python ld14p_cli.py COM5

如果有多个端口,分别测试一下。有数据输出,就说明 Windows 串口正常、雷达硬件没问题。

💡 如果 usbipd 已经把 USB 设备"独占"转发给了 WSL,此时要在 Windows 测试,得先归还:

# PowerShell 管理员
usbipd detach --busid 1-4

等几秒 Windows 会重新识别,COM 口就回来了。测完再挂回去:

usbipd attach --busid 1-4 --wsl

二、把 USB 设备转发给 WSL(usbipd)

usbipd 是一个基于 USB/IP 协议的服务端程序(守护进程),核心作用是把一台电脑上连接的物理 USB 设备,通过网络共享给其他系统使用——这里就是共享给 WSL。

2.1 查看设备的 BUSID

# (base) PS C:\Users\HongwuZhu> usbipd list
Connected:
BUSID  VID:PID    DEVICE                                                        STATE
1-4    1a86:55d4  USB-Enhanced-SERIAL CH9102 (COM3)                             Not shared

请添加图片描述

由此得到对应的 BUSID = 1-4。1a86:55d4 是设备的 VID:PID(厂商号/产品号),在不同系统中不会变化。如果状态已是 Shared,直接在 WSL 中重新触发一下即可。

说明:设备管理器里看到的是 COM 口,而 usbipd list 里不显示 COM——这是正常的。COM 端口号对 usbipd 转发完全没有影响,它走的是 USB 总线,不经过 COM 端口。核心数据 BUSID=1-4、VID:PID=1a86:55d4 都对,就说明设备被正确识别了。

2.2 绑定并挂载到 WSL

# 假设 BUSID 是 1-4,绑定并转发到 WSL
usbipd bind --busid 1-4 --force
usbipd attach --busid 1-4 --wsl
  • usbipd bind --busid 1-4 --force:将主机上指定总线ID(1-4)的USB设备强制绑定到usbipd服务,使其可以被后续重定向。
  • usbipd attach --busid 1-4 --wsl:将已绑定的USB设备挂载到WSL中,让WSL能够像使用本地设备一样访问该串口。

三、在 WSL 中验证 USB 接口

进入 WSL 后,能否认到设备取决于加载了哪个驱动。下面两种情况按优先级尝试。

情况一(推荐,本例成功):cdc-acm 直接认到 CH9102

只加载 cdc-acm,不去碰 ch341,让内核自动匹配:

# 只加载 cdc-acm,不要 echo new_id(让内核自动匹配)
sudo modprobe cdc-acm
# 看看设备是否已自动绑定
dmesg | tail -10
ls -la /dev/ttyACM* /dev/ttyUSB* 2>/dev/null

cdc-acm 是 Linux 内核的 USB 串口驱动模块,负责将符合 CDC ACM 协议(如 Arduino、3G 模块、部分 FTDI 设备)的 USB 设备映射为 /dev/ttyACM* 虚拟串口,让用户空间程序像操作普通串口一样读写。

如果 cdc-acm 认到了 CH9102,会出现 /dev/ttyACM0,这就是最理想的情况。测试:

sudo chmod 666 /dev/ttyACM0
cd /mnt/d/Project/turtlebot3_training/LD14P
python3 ld14p_win_wsl_cli.py /dev/ttyACM0

看到数据流,就说明 WSL 中雷达完全正常了。

情况二:给 ch341-uart 打补丁(new_id)

如果走的是 ch341 驱动,需要手动把设备的 VID:PID 喂给 ch341-uart,让它接管:

echo "1a86 55d4" | sudo tee /sys/bus/usb-serial/drivers/ch341-uart/new_id
sudo chmod 666 /dev/ttyUSB0 /dev/ttyUSB1
cd /mnt/d/Project/turtlebot3_training/LD14P
python3 ld14p_cli.py /dev/ttyUSB0

这条命令的作用,是强制让 Linux 内核的 ch341-uart 驱动去识别并接管一个它默认不支持的 USB 转串口设备——相当于给驱动"打补丁",让系统把设备认作串口(如 /dev/ttyUSB0),以识别市面上那些非标准 PID 的 CH341 芯片。

  • ch341-uart 是 Linux 内核里用于支持 CH341/CH340 系列 USB‑串口转换芯片的内核驱动模块(.ko 文件)。它包含在主线内核的 drivers/usb/serial/ch341.c 中,属于“USB 串口转换器驱动”的子系统。当插入 CH341 芯片的 USB 串口线时,系统会自动加载这个内核模块,生成 /dev/ttyUSB0 等设备节点。
  • tee 指令 是一个 Linux/Unix 命令,作用是从标准输入读取数据,并同时将数据写入一个或多个文件以及输出到标准输出(通常是终端)。典型的用法是 command | tee output.txt,这样 command 的输出既会显示在屏幕上,也会被保存到 output.txt。常用于需要同时“看一眼结果”和“留一份记录”的场景。

⚠️ 局限:ch341 驱动对 CH9102 不完全兼容。常见症状是 /dev/ttyUSB0 能打开但收到 0 帧数据,换 /dev/ttyUSB1 则直接 I/O 错误。遇到这种情况,说明这条路走不通,优先回到"情况一"用 cdc-acm。

情况三:安装 Linux 专用串口驱动(源码编译)

编译厂商提供的 Linux 驱动源码)。
有尝试这个方法,但是在make的时候报错,感觉很难解决

  • 常见排查步骤:
    1. 在 WSL 里执行 uname -r 获取内核版本,然后安装对应的头文件:sudo apt install linux-headers-$(uname -r)。
    2. 检查驱动源码里的 Makefile 是否硬编码了内核路径,若用 KERNELDIR 变量,需确认它指向 /lib/modules/$(uname -r)/build。
    3. 如果源码是针对较老内核(如 4.x)写的,而 WSL 是 5.10+,可能需要手动修改 Kconfig 或补丁,或者考虑在 Docker 容器里编译(但要保证容器内核与 WSL 一致)。
  • 实在无法编译的替代方案:
    • 如果只是需要串口 USB 转接(如 CH340、CP2102),WSL 内通常已自带 ch341、cp210x 等内核模块,无需额外编译。检查 lsmod | grep usb 是否已加载。如果 lsmod | grep usb 的输出中已经包含 ch341、cp210x 等模块,那么插上对应的 USB 串口设备后,WSL 内核会自动加载驱动并创建 /dev/ttyUSB0(或类似名称)节点,你可以直接通过该设备名访问。

如果模块未加载,先确认模块名(例如 ch341 或 cp210x),然后手动加载:

sudo modprobe ch341   # 若为 CH340/CH341 芯片
sudo modprobe cp210x  # 若为 CP2102 芯片

加载后再次运行 lsmod | grep usb 确认,再插拔设备即可。若提示 modprobe: FATAL: Module not found,说明 WSL 内核未包含该模块,此时需要重新编译带有该驱动模块的内核(参考 WSL 自定义内核文档),但一般常见模块都已内置。

cdc-acm 与 ch341 / cp210x 是不同类别的USB串口驱动,不是“一起”的:

  • cdc-acm 是 Linux 内核内置的通用驱动,遵循 USB CDC ACM 协议,常用于 Arduino Uno、STM32 虚拟串口、某些 FTDI 芯片等设备。
  • ch341 和 cp210x 是特定芯片(CH340/CH341、CP2102)的专用驱动,属于内核中的独立模块,不归入 cdc-acm 体系。
  • 你的设备应由芯片决定加载哪个模块:CH341 芯片 → ch341;CP2102 → cp210x;若设备是 CDC ACM 兼容 → cdc-acm。
  • 原文只列出了 CH341 和 CP210x 的加载命令,因为它们常见且用户可能主动需要。cdc-acm 通常已内置在 WSL 内核中,无需手动 modprobe,所以原文未提及。若要确认,可用 lsmod | grep acm 查看是否已自动加载。
    流程是:usbipd 在 Windows 侧将 USB 设备打包成 TCP 数据流,WSL 内核的 usbip-host 模块负责解包并重新模拟出一个 USB 设备。这个“模拟出的设备”对 WSL 内核而言就是一块新挂载的 USB 硬件,和物理插入 U 盘没有区别——因此 WSL 内核仍然需要对应设备的 Linux 驱动(例如 ch341、cp210x、ftdi_sio 等串口驱动)才能识别设备并创建 /dev/ttyUSB0 节点。
    如果 WSL 默认内核已包含该驱动(大多数常见 USB 串口芯片都已内置为模块或编译进内核),那就无需自己编译;但如果遇到厂商独有、内核未收录的驱动,你仍然需要编译内核模块并加载。

四、启动 Docker,把串口映射进容器

4.1 先检查设备权限(在 WSL 中)

ls -la /dev/ttyACM0
# 应显示: crw-rw---- 1 root dialout 166, 0 ... /dev/ttyACM0
# 如果是 crw------- 没有 dialout 组,需要: 
sudo chmod 666 /dev/ttyACM0

4.2 启动容器

方式 A:用封装脚本(推荐)

bash docker_run_wsl.sh --port /dev/ttyACM0 -e
  • --port /dev/ttyACM0:指定要映射进容器的串口设备(已确认在 WSL 中是 ttyACM0)。
  • -e:脚本里写的 enter 参数,启动后直接进入容器命令行。

方式 B:直接用原始 docker run 命令

适用于 Win11 + WSLg 环境(直接复制整行到 WSL 终端粘贴):
在 docker run 命令的 --name tb3_training 后面(或任意 -v 之前的位置)插入 --device=/dev/ttyACM0:/dev/ttyACM0 即可

docker rm -f tb3_training 2>/dev/null; docker run -it -d --name tb3_training --device=/dev/ttyACM0:/dev/ttyACM0 --network host -e DISPLAY=:0 -e LIBGL_ALWAYS_SOFTWARE=1 -e TURTLEBOT3_MODEL=burger -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg -v "/mnt/d/Project/turtlebot3_training:/workspace" -w /workspace tb3-training:latest bash

如果串口设备名不固定(如 ttyACM1、ttyUSB0),可将 /dev/ttyACM0 替换为实际路径;也可用 --privileged 映射全部设备(不推荐,安全性低):
--privileged -v /dev:/dev。
若串口在 WSL 中已通过 usbipd 挂载且显示为 /dev/ttyACM0,上述命令即可在容器内通过 /dev/ttyACM0 访问。

五、重开终端后如何重新进入环境

关闭所有终端后想重新进入 Docker 环境,按容器状态分两种情况。

5.1 容器仍在运行(最常见)

容器第一次启动后通常一直在后台运行,只需两步:

# [PowerShell] 第一步:进入 WSL
wsl
# [WSL] 第二步:进入容器
docker exec -it tb3_training bash

进入后提示符变为 root@...:/workspace#,即可正常使用 ros2、python 等命令。

💡 小技巧:可以开多个 PowerShell 窗口,每个都执行上面两步,实现多终端同时操作同一个容器。

5.2 容器已停止(如电脑重启后)

Docker Desktop 会自动启动,但容器需手动拉起:

# [WSL] 先启动容器,再进入
docker start tb3_training
docker exec -it tb3_training bash

若Docker 容器启动时配置了设备映射 --device /dev/ttyACM0,但USB 串口设备未插入或未挂载到 WSL。docker start tb3_training会提示无法启动。

  • WSL 默认不自动挂载 Windows 的 COM 端口,需要手动通过 usbipd-win 或 wsl --mount 的方式将物理串口映射到 WSL 的 /dev/ttyACM0。

解决步骤:

  1. 若设备已连接,使用 usbipd attach 将 USB 设备绑定到 WSL(需安装 usbipd-win,参考 官方文档)。
  2. 若不想挂载物理设备,可临时修改容器配置:移除 --device 参数, 若容器已停止 你需要不加载 USB 设备重新启动容器,保留其他挂载/参数。同样容器 ID 不许重复创建,可先 docker start 试试——如果原容器是 docker run -d --device=/dev/ttyUSB0 ... 启动的,现在设备不存在,docker start 会失败(设备不存在)。
    此时应创建一个新容器(不绑定 --device),比如:
    docker run -it --rm \
      --name my_container_no_usb \
      -v /some/path:/some/path \
      your_image bash
    
  3. 若想修改已有容器的配置(不重新创建):
    Docker 不支持运行时变更 --device 参数。你只能 commit 镜像后基于新镜像创建新容器,或直接创建新容器(推荐)。

5.3 常用容器管理命令

docker exec -it tb3_training bash   # 进入容器
docker stop tb3_training            # 停止容器
docker start tb3_training           # 启动容器
docker rm -f tb3_training           # 强制删除容器

六、常见问题

现象原因解决
/dev/ttyACM0 报 permission denied容器内用户无访问权限在 WSL 执行 sudo chmod 666 /dev/ttyACM0
容器内找不到 /dev/ttyACM0USB 未挂载到 WSL回到 PowerShell 执行 usbipd attach --busid 1-4 --wsl

更多推荐