1. 为什么 Jetson 上装 CH340 驱动不是“插上就能用”
把一块 CH340 芯片的 USB 转串口模块插到 Nvidia Jetson 上,指望/dev/ttyUSB0立刻出现,结果等来的只有lsusb里孤零零的一行1a86:7523,这是很多入门边缘计算的开发者都遇到过的事。CH340 是目前最常见的 USB 转 TTL 芯片,Arduino、STM32、飞控、3D 打印机、交换机调试线,几乎每张桌子上都有一块;但到了 Jetson 上,驱动安装就不再只是 Windows 下一个 exe 的事,而是要看内核模块、设备树、编译环境和权限配置。这篇就记录我完整复现并解决 Nvidia Jetson CH340 驱动安装的过程,也把大家在搜索热词里最常问的几个坑统一填掉。
这篇文章适合几类人看:刚拿到 Jetson Nano、Jetson Xavier NX 或 Orin Nano,想把 CH340 转串口模块接上 MCU 做上下位机通信的;在 JetPack 系统里modprobe ch341失败,或者编译官方 ch34x 驱动时被内核头文件问题卡住的人;还有那些明明已经看到ttyUSB0,但一拔一插就找不到设备的老手。我会把从判断芯片、加载模块、编译官方驱动、配置 udev 到故障排查的完整链路写一遍,其中很多坑是我实际踩出来的,希望能帮你省掉一下午的折腾时间。
1.1 你在哪些项目里会遇到 CH340
CH340 是南京沁恒的产品,早期叫 CH340/CH341,后来扩展出 CH342、CH343、CH344、CH347 等型号。它之所以在开发者圈子里普及,是因为一颗芯片就能完成 USB 到 UART 的转换,而且价格低、外围电路简单。你在以下几类场景里几乎绕不开它:STM32 的串口下载和调试、Arduino 板载 USB 转串口、飞控地面站参数调整、3D 打印机主板通信、工业路由器和交换机的 console 调试线。
在 Jetson 项目里,CH340 最常见的使用方式是把 Jetson 当作上位机,通过串口和 STM32 或者其它单片机通信。比如我们用 Jetson 做视觉识别,识别到目标后通过串口发一帧协议数据给 STM32,再由 STM32 去控制舵机、电机,这种“高算力主控 + 实时 MCU”的组合在机器人、无人机、智能小车项目里非常多。此外,有些激光雷达、GPS 模块、惯导模块也使用 USB 转串口芯片,插到 Jetson 上之后一样会遇到 CH340 驱动的识别问题。
1.2 L4T 内核默认驱动并没有想象中省心
很多第一次接触 Jetson 的开发者,习惯把 Windows 那套“插上自动装驱动”的经验搬过来,结果发现 Ubuntu/L4T 系统里根本没有自动弹窗。Linux 下 USB 转串口芯片靠的是内核的 USB serial 驱动模块,常见的有ch341、ftdi_sio、cp210x、pl2303这几个。Jetson 的 L4T 内核分支和桌面 Ubuntu 一样,通常会把驱动模块放在/lib/modules/$(uname -r)/kernel/drivers/usb/serial/下面,但“有内核模块”和“系统主动加载这个模块”是两回事。
官方镜像为了控制体积和减少启动时的依赖,不会把所有外设驱动都放进 ramdisk,也不会保证每个 USB 设备插上后 udev 都会自动触发对应模块。所以经常出现的情况是:lsusb能看到 CH340,但dmesg里没有ch341-uart converter相关输出,/dev下也没有ttyUSB0。更麻烦的是,某些 JetPack 版本会把设备绑定到cdc_acm驱动上,生成的节点是/dev/ttyACM0而不是/dev/ttyUSB0,程序里如果硬编码了设备名,一样会找不到串口。
1.3 搜索热词背后藏着三类典型场景
我整理了一下大家经常搜索的关键词,发现 CH340 相关的问题其实可以归成三类,理解这三类场景之后,后面排查思路会清晰很多。
第一类是“驱动装上了但不可用”,典型的热词有“Windows 11 CH340 不能使用”“CH340 预安装成功”“CH340 频繁掉线”。这种情况经常发生在 Windows 下用串口工具烧录或者调试 Jetson 配套的 MCU 时,系统已经弹出了“驱动预安装成功”,但打开设备管理器仍然看到黄色感叹号。多数原因是旧驱动残留、USB 选择性暂停,或者芯片型号较新而系统自带的驱动版本太老。
第二类是“Ubuntu/Linux 下不知道怎么装驱动”,典型的热词有“Ubuntu CH340 串口驱动”“CH340 Linux 驱动”“Ch340 驱动安装教程”。这类场景里,用户已经意识到 Linux 不是插上就有,但又被网上各种互相冲突的教程搞晕了:有人说要编译内核,有人说一个modprobe ch341就够,还有人直接扔给你一个 WCH 官网的 tar 包,结果在板子上 make 的时候报一堆错。这些坑我都会在第 3 节详细拆开讲。
第三类是“把 CH340 和别的调试工具混淆”,典型的热词有“STLink 驱动安装”“JLink 驱动安装”“FT232R USB UART 驱动安装”。这些设备虽然芯片不同,但排查思路完全一致:先lsusb找到 VID/PID,再找对应内核模块,最后看dmesg是否绑定成功。第 4 节我会专门用一张表把这几个设备串起来。
2. 动手前先把环境看明白
2.1 三分钟确认板子型号和内核版本
任何驱动安装的第一步都不是下载驱动,而是确认目标环境。尤其是 Jetson,不同代产品的内核版本差异很大,JetPack 4.x 的 R32 系列内核还是 4.9,JetPack 5.x 的 R35 系列已经换成内核 5.10,到了 JetPack 6.x 又换到内核 5.15 甚至更高。CH340 驱动源码和内核版本直接相关,老驱动在新内核上编译可能报错,新驱动在旧内核上加载也可能出现版本 magic 不匹配。
上机先看这三条命令:
uname -a cat /etc/nv_tegra_release dpkg-query -W -f='${Version}\n' nvidia-l4t-core 2>/dev/nulluname -a是王道,它输出的内核版本号会直接决定你后面需要匹配的编译头文件。cat /etc/nv_tegra_release可以看到 L4T 版本,比如R35.4.1,对应 JetPack 5.1.2。最后一条命令可以确认你当前跑的是不是 NVIDIA 官方发布的核心组件,如果这条命令没输出,说明你可能装的是第三方整理的镜像,那后面遇到内核头文件缺失的概率会更大。
2.2 确认设备识别到哪一步
判断问题出在硬件还是软件,可以用下面这一组命令按顺序看:
lsusb | grep 1a86 lsusb -t sudo dmesg | grep -iE 'ch34|usb 1-1|ttyUSB' ls -l /dev/ttyUSB* /dev/ttyACM* 2>/dev/nulllsusb | grep 1a86是第一步。1a86 就是沁恒 WCH 的 USB Vendor ID,如果这里没有输出,基本可以排除驱动问题,直接去查线材、USB 口、模块供电。如果能看到类似1a86:7523这样的设备,说明 USB 枚举已经成功,问题出在“系统没有合适的驱动来绑定它”。
接下来看dmesg。假如你看到usb 1-1: ch341-uart converter now attached to ttyUSB0,那说明驱动已经生效,串口节点也建好了,问题已经解决。假如dmesg里只有new full-speed USB device之类的枚举信息,没有ch341-uart、ch34x或者ttyUSB相关字样,那大概率是内核模块没有被加载。这时候再用lsusb -t查一下设备挂在哪个端口、有没有Driver字段,会看得更清楚。
2.3 判断该用内核内置 ch341 还是官方 ch34x
这是很多人栽跟头的地方。Linux 内核自带的是ch341模块,它支持 CH340/CH341 系列的常见型号,尤其是老的 CH340G、CH340C 和 CH341T,USB ID 通常是1a86:7523。而 WCH 官方发布的驱动模块叫ch34x,它覆盖的范围更全,包括 CH342、CH343、CH344、CH347 这些新芯片,USB ID 可能是1a86:55d4、1a86:5523之类。如果你的设备是老的 CH340G,内核自带ch341完全够用,完全没必要去折腾编译;如果芯片型号很新,lsusb里的 ID 不在7523,那才需要走官方ch34x驱动。
我个人的判断顺序是:
| 情况 | 处理方式 |
|---|---|
| `lsmod | grep ch341已有输出,/dev/ttyUSB0` 存在 |
find /lib/modules/$(uname -r) -name 'ch34*.ko*'能查到ch341.ko | 先modprobe ch341,大概率就能用 |
lsusb显示 ID 是1a86:7523,但ch341.ko加载后无效 | 再考虑编译官方ch34x驱动 |
lsusb显示 ID 不是7523,比如55d4 | 直接走官方ch34x驱动,内核自带模块大概率不支持 |
有一点必须提醒:内核自带ch341和官方ch34x不要同时加载,两个驱动会抢同一个 USB 设备,轻则dmesg报device busy,重则导致系统日志刷屏。我一般先modprobe -r ch341,再modprobe ch34x,确保只有一个驱动绑定设备。
3. 在 Jetson 上编译安装 CH340 驱动
3.1 最省事方案:先试试内核自带 ch341
如果你的 CH340 是老型号,整个安装过程其实只需要两条命令:
sudo modprobe ch341 lsmod | grep ch34如果模块存在,lsmod会看到ch341,同时dmesg会出现类似ch341-uart converter now attached to ttyUSB0的信息,ls /dev/ttyUSB*应该就能看到节点了。为了开机自动加载,再写一个配置:
echo ch341 | sudo tee /etc/modules-load.d/ch341.conf这里有一个细节:如果插入模块后dmesg没有任何反应,但系统也没有报错,可以把 USB 线拔掉重新插一次。因为内核的 USB serial 驱动需要设备上线时触发 probe,有时候模块加载晚了,设备已经枚举完,等下一次插拔才会绑定成功。
假如modprobe ch341直接提示Module ch341 not found,那就说明当前 L4T 内核根本没有编译这个模块。这种时候不要硬来,直接跳到 3.2 节看怎么准备编译环境。
3.2 需要编译官方驱动时,先解决内核头文件
这是 Jetson 用户最常见的卡点。WCH 官方ch34x驱动下载下来之后是一个 tar 包,解压后make需要依赖/lib/modules/$(uname -r)/build这个软链接。如果这个软链接指向的目录不存在,make 一定会报类似kernel source tree not found或者cannot find /lib/modules/.../build的错误。
上板先检查:
ls -l /lib/modules/$(uname -r)/build ls -l /usr/src/ | grep linux桌面 Ubuntu 上通常有linux-headers-$(uname -r),可以apt install linux-headers-$(uname -r)装好。但 Jetson 的 L4T 镜像不一定有现成的 apt 包。很多第三方整理的镜像里/usr/src是空的,/lib/modules/.../build指向一个不存在的目录,这时候如果你直接跑make,必挂。
解决办法有两个方向。第一个方向是尝试从 apt 源安装,命令还是上面那条,如果能装上,说明镜像源里有对应内核 headers,这是最省心的。第二个方向是去 NVIDIA 开发者官网下载和当前 JetPack 版本匹配的 L4T BSP 包,从public_sources里解出 kernel source,放到板子上按官方 README 准备。这个过程比较繁琐,但为了兼容性值得搞一次。无论哪种方式,都必须保证内核源码版本和uname -r完全一致,否则编译出来的.ko模块加载时一定会报Invalid module format。
3.3 编译安装 WCH 官方 ch34x 驱动
确认/lib/modules/$(uname -r)/build存在之后,就可以下载 WCH 官方 Linux 驱动。解压后一般是一个目录,里面有ch34x.c、Makefile和说明文档。然后依次执行:
tar xjf ch34x_linux_driver.tar.bz2 cd ch34x make sudo make install sudo depmod -a sudo modprobe -r ch341 sudo modprobe ch34x执行make install会把ch34x.ko复制到/lib/modules/$(uname -r)/kernel/drivers/usb/serial/,然后depmod -a更新模块依赖关系。之后sudo modprobe ch34x应该能加载成功,再插拔一次 USB 线,dmesg就能看到ch34x-uart converter now attached to ttyUSB0。
这个过程中有一个非常常见的坑:老版本 WCH 驱动源码在 Linux 5.10 以上的内核里编译会报函数签名不匹配或者隐式声明错误,因为内核 API 一直在变。如果make报错,不要反复尝试,去下载最新版驱动,或者直接改用内核自带ch341模块。我自己在 JetPack 5.1.2 上就遇见过老驱动编译不过去的问题,后来换了一个更新版本的驱动才顺利通过。
3.4 开机自启与 udev 固定设备名
模块加载成功后,为了以后不用每次手动modprobe,创建一个模块加载配置:
echo ch34x | sudo tee /etc/modules-load.d/ch34x.conf接着强烈建议写一条 udev 规则,给 CH340 固定设备名。因为 Jetson 上如果同时插了多个 USB 转串口设备,/dev/ttyUSB0的编号会随插拔顺序变化,程序引用起来很不可靠。在/etc/udev/rules.d/99-ch340.rules里写:
KERNEL=="ttyUSB*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0666", GROUP="dialout", SYMLINK+="ttyCH340"保存后执行sudo udevadm control --reload-rules && sudo udevadm trigger,再插拔一下设备,ls -l /dev/ttyCH340就能看到固定链接。这里注意规则里要写ATTRS而不是ATTR,因为idVendor、idProduct通常是在 USB 父设备上,用单个ATTR会匹配不到。
3.5 一次完整的从插线到 ttyUSB0 的操作记录
我最近一次实际踩坑是在一块 Jetson Nano 上,官方镜像刷完之后接了一个 CH340 转 TTL 模块,模块丝印是 CH340C。插上线后lsusb能看到1a86:7523,但/dev/ttyUSB0始终不出现。我先执行sudo modprobe ch341,结果系统没有任何提示,lsmod里也没有ch341模块,说明当前内核没有把ch341.ko编进去。
这时候我检查/lib/modules/$(uname -r)/build,发现软链接指向的目录不存在。于是我重新用官方 BSP 补全了内核源码和 headers,再把 WCH 最新版ch34x驱动拉到板子上编译。整个过程大概 20 分钟,其中大头花在内核头文件准备上,真正make加make install不到两分钟。加载完后插拔 USB,dmesg立刻出现ch34x-uart converter now attached to ttyUSB0,再配置好/etc/modules-load.d/ch34x.conf和 udev 规则,后续重启直接就能用/dev/ttyCH340访问串口。
4. 高频问题与排查技巧实录
4.1 能看到 USB 设备但始终没有 /dev/ttyUSBx
这个现象在 Jetson 上非常典型,lsusb能看到1a86:7523,说明 USB 枚举已经成功,问题出在驱动绑定环节。第一反应看dmesg:
sudo dmesg | tail -30如果日志里只有 USB 枚举信息,比如new full-speed USB device number 9 using tegra-xusb,但没有任何ch341-uart或者ch34x-uart相关输出,那基本可以断定驱动没有绑定。解决办法就是手动modprobe ch341,如果模块不存在则查内核是否编译了该模块。
另一种情况是dmesg报device busy。这说明设备被另一个驱动占用了,最常见的是usbhid或者cdc_acm。可以先用lsusb -t查看当前端口挂载的 driver 是什么,如果是cdc_acm,生成的节点会是/dev/ttyACM0,程序里可以试试这个路径;如果是usbhid,就需要手动指定驱动绑定,或者用官方ch34x驱动进行覆盖。
还有一种很容易忽略的原因是供电不足。CH340 模块本身耗电不大,但如果你把它接在一个不带独立供电的 USB Hub 上,而且 Hub 上还插了无线网卡、鼠标、U盘,Jetson 的 USB 口可能会因为电压跌落导致设备反复 reset。表现为lsusb时有时无,dmesg里刷USB disconnect。这种问题换一个直连口或者换一个带供电的 Hub 立刻就好。
4.2 编译和加载模块时的三个典型报错
第一个报错是kernel source tree not found,原因就是/lib/modules/$(uname -r)/build不存在,或者软链接断了。解决方式在 3.2 节已经说过,核心是找到和当前内核完全匹配的 headers 或源码,不要拿桌面 Ubuntu 的 headers 来凑。
第二个报错是Invalid module format,原因通常是内核源码版本和uname -r不一致。内核模块加载时有个 module version magic,会对比 vermagic 字符串,差一个 commit 都加载不进。遇到这个错误,优先重编当前内核匹配的驱动,不要试图用insmod -f强制加载,那样容易把系统搞崩。
第三个报错是Unknown symbol in module,一般是 WCH 驱动源码和内核 API 不匹配造成的。老驱动编译时如果没有报错但模块加载时缺 symbol,说明源码里用了一些当前内核已经删除的符号。这时候老老实实升级驱动版本,或者在官方源码库找补丁,硬改源码的成本很高。
4.3 串口权限不足与频繁掉线
驱动装好之后,/dev/ttyUSB0可能默认只有 root 能读,普通用户一打开串口就报Permission denied。解决方法是把用户加入dialout组:
sudo usermod -aG dialout $USER执行完要重新登录一次生效。如果你不介意权限,也可以在 udev 规则里直接把MODE设成0666,像前面写的那样,适合临时测试。
频繁掉线又是另一个独立问题。除了供电,Linux 的 USB autosuspend 也很容易在 Jetson 上引发串口掉线。系统为了省电,会让 USB 设备在一段时间无数据后进入挂起状态,CH340 模块在挂起后回归时的握手经常有问题。可以关掉 autosuspend:
echo -1 | sudo tee /sys/module/usbcore/parameters/autosuspend_delay_ms如果想一劳永逸,可以在/etc/udev/rules.d/99-usb-power.rules里加一条:
ACTION=="add", SUBSYSTEM=="usb", ATTR{power/control}="on"写完重载 udev 规则即可。
4.4 从 CH340 到 STLink/FT232R 的通用排查思路
很多人搜“STLink 驱动安装”“JLink 驱动安装”“FT232R USB UART 驱动安装”是因为概念混淆。其实这些设备在 Linux 下的排查思路是完全一样的,底层都是 USB 设备,只是 VID/PID 和内核驱动模块名不同。拿到任意一个不认识的 USB 调试工具,先执行lsusb,把 Vendor ID 和 Product ID 查出来,然后去内核里搜对应驱动模块。
比如 FT232R 芯片对应的是ftdi_sio,CP2102 对应cp210x,STLink 调试器在较新内核里对应stlink驱动树。它们的安装方式都是先确认modinfo是否存在,不存在再编译安装,最后用dmesg验证是否绑定成功。这套流程可以帮你处理绝大多数 USB 外设在 Jetson 上的驱动问题。
4.5 常见问题速查表
| 症状 | 可能原因 | 处理建议 |
|---|---|---|
lsusb看不到 1a86 | 线材、USB 口、模块供电异常 | 换线换口,在另一台电脑上确认模块本身可用 |
lsusb能看到但没有/dev/ttyUSB0 | 驱动未加载或内核无对应模块 | modprobe ch341,或编译安装ch34x |
modprobe ch341提示 not found | 当前内核未编译 ch341 模块 | 使用官方 ch34x 驱动源码自行编译 |
| 打开串口提示 Permission denied | 用户不在 dialout 组 | sudo usermod -aG dialout $USER |
| 串口用一段时间后消失 | USB autosuspend 或供电不稳 | 关闭 autosuspend,换直连口或供电 Hub |
dmesg报 Invalid module format | 内核源码版本和当前内核不匹配 | 重新获取对应版本 headers 并重编驱动 |
| Windows 下预安装成功但设备带感叹号 | 旧驱动残留或 USB 选择性暂停 | 设备管理器卸载设备,重启重装驱动 |
5. 驱动就绪之后,把串口真正用起来
5.1 回环测试:验证收发链路
驱动装好、/dev/ttyUSB0出现后,别急着接外部设备,先做一个最简单的回环测试。用一根杜邦线把 CH340 模块的 TX 和 RX 短接,然后在终端里发数据,如果发送的数据能在同一端口被收到,说明整条串口链路完全没问题。
推荐用minicom做快速验证:
sudo apt install minicom sudo minicom -D /dev/ttyUSB0 -b 115200进入 minicom 后随便敲几个字符,正常情况下你会看到键盘输入的字符被回显出来,因为 TX 和 RX 短接后数据绕了一圈又回来了。如果能看到回显,串口的发送和接收都没有问题,接下来就可以放心接 MCU 了。如果没有回显,先查波特率设置,再查杜邦线是否接对,最后确认模块的 TX/RX 电平是否匹配。
5.2 用 Python 或 minicom 访问串口
实际项目里推荐直接用 Python 操作串口。先安装 pyserial:
sudo apt install python3-serial然后使用下面的脚本快速验证:
import serial ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1) ser.write(b'ping\n') data = ser.readline() print(data) ser.close()如果你要做上下位机通信,建议读数据用read配合超时和帧头解析,不要直接readline,因为从 MCU 发出来的数据不一定按行结束。写数据时注意协议字节序和校验位,CH340 在标准波特率下非常稳,但如果你把波特率拉到 921600 甚至更高,线材质量差的话可能误码,这时候就要降低波特率或者换好的 USB 延长线。
5.3 给 CH340 一个固定的设备名
前面写了 udev 规则的例子,这里再补充一个细节。如果你同时插了两块一样的 CH340,靠idProduct区分不了,可以再加一个PHYSDEVPATH或者KERNELS属性来区分不同的物理端口。先用udevadm查到完整属性:
udevadm info -a -n /dev/ttyUSB0 | grep -E 'idVendor|idProduct|KERNELS'然后把规则写细一点,比如给 USB 物理端口 1-1 上的 CH340 固定为ttyCH340_A,给端口 1-2 上的固定为ttyCH340_B。这样即使两个模块同时插着,程序里也能根据固定设备名准确访问各自的串口,不会因为内核枚举顺序变化而连错设备。
固定设备名的最大价值在长期运行的项目里。嵌入式设备一旦上电就可能在无人值守环境下运行,如果某个串口驱动加载顺序变了,ttyUSB0和ttyUSB1对调,程序读到的设备就是错的。用 udev 绑定后,这个问题从根上就消除了。
5.4 项目中的一点经验
最后分享一点我在实际项目中的体会。CH340 驱动本身并不复杂,真正让人崩溃的往往是三类问题:一是线材质量差导致 USB 枚举不稳定,二是内核 headers 版本不匹配导致编译失败,三是权限和 udev 规则没配置好导致设备路径漂移。前两个可以靠换硬件、换高质量线材、严格匹配内核版本解决,第三个一定要在一开始就规划好,不然项目跑着跑着串口就“失踪”了。
另外,如果你的 Jetson 项目对串口实时性要求很高,比如每 10 毫秒就要和 MCU 交换一次控制指令,建议优先考虑板子原生的硬件 UART,比如 Jetson 上的/dev/ttyTHS0或/dev/ttyTHS1。USB 转串口芯片适合调试、低速传感器采集、偶尔发指令这种场景,CPU 负载高或 USB 总线繁忙时,USB 串口偶尔会产生几十毫秒的延迟,这是 USB 协议本身的特性。CH340 驱动安装到位只是第一步,后续根据项目负载选择合适的串口通道,才是让整个系统稳定跑起来的关键。