前言
最近在开发一个AMR(Autonomous Mobile Robot)机器人,一般出于方便考虑,可以在PC上进行开发,而我因为主力PC是windows系统,所以一般用wsl2解决需要linux发行版的地方,比如这里需要用到的ros框架,我现在正在使用Isaac sim配合ros2进行windows下的机器人开发。
由于不想装双系统,以及我相信wsl是最好的linux发行版(doge),所以这里也是用wsl2进行开发,但是在使用一个pcan的设备时遇到了问题。pcan原本是在linux下免驱的,可以通过canutils、以及socketcan等工具非常方便的使用。但是在wsl2就遇到了一些问题,主要是
- usb设备在windows下,wsl无法访问
- wsl内核没有提供can设备的支持
解决:usb设备在windows下,wsl无法访问
首先,根据我所购买的usb to can设备,其在插上设备后,在windows设备管理器中是可以显示XCAN-USB的,且带有无驱动的问号icon。如果安装了windows下的驱动程序,则可以显示PCAN-USB且能正常使用。
因为我这里肯定不在windows下使用,所以跳过了这一部分。
幸运的是,我曾在去年或前年注意到,有用户需要在WSL中调试蓝牙外设,而该外设位于Windows系统下。为此,他们使用了一个开源的桥接工具,将USB设备映射到WSL中。
我今天在检索的时候,发现这个工具已经在Microsoft的官方Learn文档中。
https://learn.microsoft.com/zh-cn/windows/wsl/connect-usb
读者可以直接通过上面这个链接完成把USB设备共享到WSL内。
这主要是基于USBIPD-WIN这个开源项目实现的,完成安装后可以使用如下命令列出所有连接到 Windows 的USB设备。usbipd的list命令和bind命令可能需要管理员模式。
usbipd list
根据开源工具和微软的文档,可以使用 usbipd bind –busid=<BUSID> 命令来共享设备,从而允许它附加到 WSL。例如我上图中的BUSID为6-4的设备就已经被设置为了Shared表示该设备已经共享。随后还需要使用 usbip attach –remote=<HOST> –busid=<BUSID> 命令将usb设备附加到wsl2中。例如我上图中的BUSID为6-3的设备就已经被设置为了Attached,表示该设备已经共享。
参考微软文档,如果要附加到wsl,可以的命令内容为
usbipd bind --busid <busid> # 将指定usb设备设为共享 usbipd attach --wsl --busid <busid> # 将指定usb设备附加到wsl usbipd detach --busid <busid> # 将指定usb设备从wsl断开
需要注意的是,USB设备附加到WSL后,Windows将无法使用。仅bind命令需在Windows终端管理员模式下执行,设置后无需重复操作。每次断开连接后,需使用attach命令重新附加USB设备到WSL。
打开 Ubuntu(或首选的 WSL 命令行),使用以下命令列出附加的 USB 设备:
lsusb
例如我这里可以看到刚刚所连接的一个PCAN-USB设备。在 WSL 中完成设备使用后,可物理断开 USB 设备,或者从windows的终端运行 usbipd detach 命令。
本来我已经事情到这里就已经结束了,可以顺利使用can设备了,结果我在运行 ip link show type can 命令时发现,并没有can类型的设备。因此,后续的can启用命令,包括can-utils等工具也无法使用。
过程
经过一番检索,相关的资料比较少,wsl的github issue上内容也很多,不过也算好不容易找到了几种解决办法。
根据 Peak-System 的社区文章,有人遇到了和我一样的现象。不过有人表示:(WSL) 并不是一个真正的完全运行的 Linux 系统,因此无法使用,驱动也可能无法使用。
不过后面有人说内核版本大于6.0的linux_kernel有这个支持,且他解决了这个问题。我发现我的wsl2的linux_kernel内核版本是5.x的,因此参考 微软的文档 编译了一个6.6的内核,但是not work。
后来也在github的issue上看到有人提到了这个问题,但是没有给出解决方案。CAN bus functionality 这个issue中表示它的版本已经支持了can设备,只要使用usbipd那个工具就可以了。就很奇怪。
直到我看到这个Issue:CAN Bus/USBCAN(canable) support,我大概意识到了可能是需要在内核中启用CAN网络的支持,且前一个issue也提到了他曾自己编译了一个wsl kernel。
随后我在两个地方(reddit: SocketCAN/can-utils on Windows 和 Enabling SocketCAN on WSL2 walkthrough)看到了关于在内核中启用虚拟can部分的内容。以及一篇CSDN的文章 WSL2使用USB转CAN(canable)。虽然由于csdn这个文章我打不开,但是看目录,我认为实现顺序基本一致:把USB附加到WSL、查看WSL内核版本、下载WSL源码、修改配置、编译并使用。
是的,这是三四年前就有解决方案的问题。
解决:wsl内核没有提供can设备的支持
下载WSL源码
首先,查看自己的wsl2内核版本(可选):
luheqiu@LuHeQiu-PC:~$ uname -r 5.15.167.4-microsoft-standard-WSL2
比如我这里运行 uname -r 后是5.15.167.4的内核。
可选的原因是换成哪个内核都不影响,我试过了,换成6.6版本的内核,还是5.15的内核,都可以正常运行,而且换内核也非常方便。(毕竟是在windows上操作,乐。哪怕内核有问题也不担心开不了机,大不了换回去就行了。)
打开 WSL2-Linux-Kernel 的github源码网址,切换到5.15.y的分支。
可以看到,我这里最新的提交正好就是5.15.167.4。这也是因为我的wsl2的版本已经是最新的ltsc版本了。
你可以选择直接拉取指定的分支以节约存储,例如:
git clone --branch linux-msft-wsl-5.15.y https://github.com/microsoft/WSL2-Linux-Kernel.git
不过我这里是拉取了默认的master分支(因为忘了),然后切换到目标分支:
git clone https://github.com/microsoft/WSL2-Linux-Kernel.git # 克隆仓库 cd WSL2-Linux-Kernel # 进入文件夹 git checkout --track origin/linux-msft-wsl-5.15.y # 切换到远程分支(同时本地也会自动创建)
我这里仓库大概下载了2GB多一些。
修改配置
参考github页面的readme,编译wsl2 kernel安装一些前置的库,同时可能还需要安装 ncurses 库,因为这是打开配置菜单menuconfig必须依赖的库。
sudo apt install build-essential flex bison dwarves libssl-dev libelf-dev # 编译kernel需要的库 sudo apt install libncurses5-dev libncursesw5-dev # 使用配置菜单需要的库
使用下述三个命令开始配置编译选项:
cat /proc/config.gz | gunzip > .config make prepare modules_prepare make menuconfig
这几个命令从压缩的 /proc/config.gz 文件中检索内核配置信息,将其解压缩,并将其保存到名为 .config 的文件中。然后根据这个配置文件,使用make构建系统打开菜单配置界面,就是下面这个蓝灰的框框。
使用方向键和Enter进行选择,使用Y包含功能到内核、使用N排除功能,使用M把功能作为独立模块特性编译。
根据GPT的解释,当选择M时,该功能会被编译为一个独立的内核模块,该功能不会在内核启动时加载,而是可以在运行时根据需要动态加载或卸载。
所以,如果你需要在内核启用的时候,一直启用这些模块,就需要使用Y,此时功能的前面显示为*,如果需要按需启用,且设置为M后,每当使用can的时候需要额外的命令启用can功能。
注意此处有坑,可以跳转到文章最后一章了解。
CAN的相关配置在Networking support子选项内,我按M启用了:
- <M> CAN bus subsystem support
然后按Enter进入CAN bus subsystem support的子选项,确保启用了
- <M> Raw CAN Protocol
其他的不做修改,然后继续进入CAN Device Drivers子选项内,启用了
- <M> Virtual Local CAN Interface #其实现在我感觉这个不需要,好像不需要虚拟can
- <*> CAN bit-timing calculation #确保这个启用的
- <*> CAN devices debugging messages #确保这个启用的
另外这个页面还有一个串口转CAN设备的支持,因为我没有用所以没看,但我看到有的教程中提到了这个(也有一些没有)。
- Serial /USB serial CAN Adaptors (slcan)
slcan就是把can模拟成串口的意思,应该是适配一些slcan的can模块用的。但是理论上slcan都模拟成串口了应该可以直接作为usb设备使用才是。也许会有一些更高级的can支持把。
然后我还继续进入了CAN USB interfaces子选项,启用了
- <M> 8 devices USB2CAN interface
- <M> PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD
这个是其他的教程里没有提及,但我感觉我的场景需要的,因为我用到了PCAN,反正加了没坏处,我就加上了,另外第一个似乎是同时启用这个页面的8个设备接口,因为我数了数,正好有八个可以支持的设备接口,所以可能这两个择一即可。但是我输入H查看help,提示好像指的其实是(http://www.8devices.com)这个网址内的8个设备。但我觉得问题不大,都加上。
有些其他的相关配置,可以自行参考HELP选择,help信息都很丰富。
最后Enter一下Save,然后Exit即可保存配置并退出。
编译并使用
最后进入编译流程:
make modules sudo make modules_install make -j $(nproc) sudo make install
编译完成并运行了install后,应该已经把一些必要的模块和库安装了。此外编译后应该还可以得到两类内核文件,一个在工程根目录下的vmlinux,还有一个是 arch/x86_64/boot/目录下的 bzImage(这个目录下的bzImage我看指向了arch/x86/boot/下的bzImgae,可能这两个是一样的),这两类文件我都测试了都可以工作,但是一个体积10-20MB、一个800MB,不知道有什么区别。当然,我也问了GPT。
在编译 WSL2 的 Linux 内核时,生成的 vmlinux 和 bzImage 是两种不同形式的内核文件,它们各有用途和特性:
- vmlinux
用途:
它是未经压缩和封装的 Linux 内核映像,包含完整的调试信息。
通常用于内核调试和分析,例如使用 GDB 或 perf 工具时。
WSL2 内核开发者可以借助它查看内核内部的符号和源代码映射。
特点:
体积较大:因为它包含所有符号表和调试信息。
未经压缩:适合在开发和调试阶段直接使用。- bzImage
用途:
它是用于实际引导系统的压缩和封装内核映像,用于启动内核时加载到内存中。
在 WSL2 环境中,这个文件是最终实际运行的内核文件。
特点:
体积较小:通过压缩算法(通常是 gzip 或 bz2)显著缩减体积,去掉了调试符号。
适合引导:封装了加载器代码,用于引导加载内核。
WSL2 会使用此文件作为运行内核的载体。—— 内容来源于Chatgpt4,不保证真实性
我这里选择arch/x86_64/boot/下的内核映像,是因为我的电脑是x86架构的。其实网上一堆教程都用的vmlinux,我之所以产生是不是应该用bzImage,是因为我一开始按照微软官方文档编译linux 6.6内核的时候,它使用的是bzImage。
使用下面这个命令把镜像移到d盘,我一开始移到c盘,但使用sudo也提示无权限,乐。
cp arch/x86_64/boot/bzImage /mnt/d/
另外,也可以直接在windows文件资源管理器中操作,但注意此时需要复制arch/x86/boot/下的bzImage,因为x86_64目录下的是一个硬链接,只有几字节,不能用的。
然后再windows的终端中关闭wsl:
wsl --shutdown
修改wsl配置文件,该文件路径位于用户目录下的.wslconfig。
例如我的为:C:\Users\MyUserName\.wslconfig
修改配置如下:
[wsl2] kernel=C:\\wsl_kernel\\wsl_kernel_5.15.167.4\\bzImage
这里kenel后面需要填写为你的bzImage内核映像。这个切换内核映像还是比较丝滑的,就算kernel有问题,大不了把这个配置删了,它又会使用原来的正常kernel。也不担心内核映像异常导致系统无法启动,还要修boot。
在WSL2中使用CAN设备
现在回到开始,先使用usbipd attach工具把can usb设备附加到wsl,如果显示已经attach了,但是之前wsl重启过了,可能没有识别到,建议先detach解除附加,再使用attach重新附加到wsl。
此时wsl内依然可以看到这个usb设备。
luheqiu@LuHeQiu-PC:~$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 002: ID 0c72:000c PEAK System PCAN-USB Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub luheqiu@LuHeQiu-PC:~$
此外,此时也可以看到在网络中识别到了can设备,只不过暂时是关闭的,state为DOWN。
luheqiu@LuHeQiu-PC:~$ ip link list type can 7: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can
我开始插入第二个can设备,重复上述流程,此时可以看到两个can设备:
luheqiu@LuHeQiu-PC:~$ ip link list type can 7: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can 8: can1: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can
然后设置can0设备的波特率并启用can0设备:
luheqiu@LuHeQiu-PC:~$ sudo ip link set can0 down luheqiu@LuHeQiu-PC:~$ sudo ip link set can0 type can bitrate 1000000 luheqiu@LuHeQiu-PC:~$ sudo ip link set can0 up luheqiu@LuHeQiu-PC:~$ ip link list type can 9: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10 link/can 10: can1: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can
可以看到can0设备已经正常启动了,注意要先设置波特率后才能启用。(这个地方卡了我半天,我一直up发现启动不了,问了gpt,gpt说可能内核配置不对,可恶。)
重复上述流程启用can1设备,并安装can-utils开始收发测试。
sudo apt-get install can-utils
这里有出现了第二个坑,因为之前跟着网上其他人的解决方案做的,我把所有的模块都设置成了M,结果使用can-utils的时候,一直有问题,提示:
luheqiu@LuHeQiu-PC:~$ sudo candump can0 socket: Address family not supported by protocol
后来才知道,那个需要按需启用的意思是需要自己输入命令启动的。
sudo modprobe can #启用can支持 sudo modprobe can_raw #启用can协议
把两个can设备的CANH和CANL相互连接,开启两个终端,一个终端运行candump can0监听can0,一个终端运行cansend can1 666#010203040A往can1执行发送。此时可以看到数据正常的收发了
tql,全文最详细的帮助文章
你是真捧hhh