最近在调试一块基于CH344芯片的USB转4串口设备时,遇到了一个典型的Linux驱动冲突问题。设备插入后,系统自动加载了通用的cdc_acm驱动,导致设备节点被识别为/dev/ttyACM0-3,而不是我们期望的/dev/ttyCH343USB0-3。这种情况在实际嵌入式开发中并不少见,特别是使用国产芯片时,经常会遇到内核自带驱动与厂商专用驱动的兼容性问题。
通过dmesg日志可以看到,系统确实检测到了CH344设备,但错误地分配了驱动:
code复制[ 18.778422] ch343: loading out-of-tree module taints kernel.
[ 18.779108] usbcore: registered new interface driver usb_ch343
[ 18.779112] ch343: USB serial driver for ch342/ch343/ch344/ch346/ch347/ch339/ch9101/ch9102/ch9103/ch9104/ch9143, etc.
而实际的设备节点却是:
code复制crw-rw-rw- 1 root dialout 166, 0 Feb 11 12:08 /dev/ttyACM0
crw-rw-rw- 1 root dialout 166, 1 Feb 11 12:08 /dev/ttyACM1
crw-rw-rw- 1 root dialout 166, 2 Feb 11 12:08 /dev/ttyACM2
crw-rw-rw- 1 root dialout 166, 3 Feb 11 12:08 /dev/ttyACM3
这种情况的根本原因在于Linux内核的设备驱动匹配机制。当USB设备插入时,内核会根据设备的vendor ID和product ID在已注册的驱动中寻找最匹配的一个。cdc_acm作为通用的USB串口驱动,其匹配规则较为宽松,经常会"抢走"本应由专用驱动管理的设备。
WCH官方提供了开源的Linux驱动,我们需要先获取最新版本的源代码:
bash复制git clone https://github.com/WCHSoftGroup/ch343ser_linux.git
cd ch343ser_linux/driver
这个仓库不仅包含CH343驱动,还支持CH342/CH344/CH346/CH347等多个型号。驱动采用GPL协议开源,代码结构清晰,主要包含:
ch343.c - 驱动主模块Makefile - 编译配置readme.txt - 使用说明在Ubuntu系统上编译内核模块需要安装开发工具链和内核头文件:
bash复制sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)
特别提醒:内核头文件版本必须与当前运行的内核严格匹配。使用uname -r查询内核版本,确保安装的headers版本一致。
编译过程相对简单:
bash复制make
sudo make install
make install会执行以下操作:
.ko文件复制到/lib/modules/$(uname -r)/kernel/drivers/usb/serial/depmod更新模块依赖关系/etc/modprobe.d/ch343.conf配置文件安装完成后,手动加载驱动:
bash复制sudo modprobe ch343
验证驱动是否加载成功:
bash复制lsmod | grep ch343
应该能看到类似输出:
code复制ch343 32768 0
Udev是Linux的设备管理器,负责在设备插入时创建设备节点并设置权限。要让系统正确识别CH344设备,需要编写自定义udev规则。规则文件通常存放在/etc/udev/rules.d/目录,文件名以数字开头表示优先级(数字越小优先级越高)。
对于CH344设备,我们需要解决三个核心问题:
新建/etc/udev/rules.d/99-ch344.rules文件,内容如下:
bash复制# CH344设备规则 - 确保使用ch343驱动而不是cdc_acm
# 第一步:当设备插入时,立即解绑cdc_acm驱动
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d5", \
RUN+="/bin/sh -c ' \
for iface in 0 2 4 6; do \
if [ -f /sys/bus/usb/drivers/cdc_acm/%k:1.$iface ]; then \
echo %k:1.$iface > /sys/bus/usb/drivers/cdc_acm/unbind 2>/dev/null || true; \
fi; \
done \
'"
# 第二步:为ch343驱动创建的设备节点创建符号链接
ACTION=="add", KERNEL=="ttyCH343USB[0-3]", SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d5", \
SYMLINK+="ttyCH344USB%n", MODE="0666", GROUP="dialout"
# 第三步:确保设备权限正确
ACTION=="add", KERNEL=="ttyCH343USB[0-3]", SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d5", \
MODE="0666", GROUP="dialout"
这个规则文件分为三个关键部分:
驱动解绑:在设备添加时,立即检查并解绑可能被cdc_acm驱动占用的接口。CH344的四个串口对应接口编号0、2、4、6(1、3、5、7可能是保留或GPIO接口)。
符号链接创建:为每个串口创建/dev/ttyCH344USBx的符号链接,指向实际的/dev/ttyCH343USBx设备节点。这样做的好处是保持与Windows命名习惯一致,方便跨平台开发。
权限设置:将设备节点权限设置为666,并加入dialout组,确保普通用户可以直接访问串口设备。
应用新规则:
bash复制sudo udevadm control --reload-rules
sudo udevadm trigger
验证规则是否生效:
bash复制ls -l /dev/ttyCH343USB*
ls -l /dev/ttyCH344USB*
正确配置后,应该看到类似输出:
code复制lrwxrwxrwx 1 root root 12 Feb 11 12:08 /dev/ttyCH344USB0 -> ttyCH343USB0
lrwxrwxrwx 1 root root 12 Feb 11 12:08 /dev/ttyCH344USB1 -> ttyCH343USB1
lrwxrwxrwx 1 root root 12 Feb 11 12:08 /dev/ttyCH344USB2 -> ttyCH343USB2
lrwxrwxrwx 1 root root 12 Feb 11 12:08 /dev/ttyCH344USB3 -> ttyCH343USB3
现象:modprobe ch343命令执行后没有报错,但lsmod看不到驱动加载。
排查步骤:
检查内核日志:
bash复制dmesg | tail -20
确认内核版本匹配:
bash复制uname -r
确保编译时使用的头文件版本一致。
尝试手动加载:
bash复制sudo insmod /lib/modules/$(uname -r)/kernel/drivers/usb/serial/ch343.ko
查看具体错误信息。
解决方案:
/lib/modules/$(uname -r)/目录是否存在现象:普通用户无法打开串口设备,提示权限不足。
排查步骤:
bash复制ls -l /dev/ttyCH343USB*
bash复制groups
查看当前用户是否在dialout组中。解决方案:
bash复制sudo chmod 666 /dev/ttyCH343USB*
bash复制sudo usermod -aG dialout $USER
现象:/dev/ttyCH344USB*不存在,但/dev/ttyCH343USB*存在。
排查步骤:
bash复制sudo udevadm test /sys/class/tty/ttyCH343USB0 2>&1 | grep -i error
bash复制sudo udevadm control --reload-rules && sudo udevadm trigger
解决方案:
为了让系统启动时自动加载ch343驱动,可以创建modprobe配置文件:
bash复制echo "ch343" | sudo tee /etc/modules-load.d/ch343.conf
对于需要稳定设备名的应用场景,可以使用by-id或by-path方式引用设备:
bash复制ls -l /dev/serial/by-id/
这种方式不依赖设备插入顺序,更适合生产环境。
当系统连接多个CH344设备时,可以通过以下方式区分:
示例规则:
bash复制ACTION=="add", KERNEL=="ttyCH343USB[0-3]", SUBSYSTEM=="tty", ATTRS{serial}=="123456", \
SYMLINK+="ttyCH344USB_A%n", MODE="0666", GROUP="dialout"
ACTION=="add", KERNEL=="ttyCH343USB[0-3]", SUBSYSTEM=="tty", ATTRS{serial}=="654321", \
SYMLINK+="ttyCH344USB_B%n", MODE="0666", GROUP="dialout"
对于高速串口通信,可以调整内核缓冲区大小提升性能:
bash复制sudo stty -F /dev/ttyCH344USB0 ospeed 921600
sudo setserial /dev/ttyCH344USB0 low_latency
如果硬件支持RTS/CTS流控,可以通过以下方式启用:
bash复制sudo stty -F /dev/ttyCH344USB0 crtscts
为防止驱动崩溃导致设备不可用,可以配置看门狗定时器:
bash复制echo 10 | sudo tee /sys/module/ch343/parameters/watchdog_timeout
这个设置会在10秒无响应后自动重置设备。
使用简单的Python脚本测试串口通信:
python复制import serial
ser = serial.Serial('/dev/ttyCH344USB0', 115200, timeout=1)
ser.write(b'Hello CH344\n')
response = ser.readline()
print(response.decode())
ser.close()
同时测试四个串口:
bash复制for port in {0..3}; do
sudo stty -F /dev/ttyCH344USB$port 115200
echo "Test $port" > /dev/ttyCH344USB$port
cat /dev/ttyCH344USB$port &
done
运行24小时持续通信测试:
bash复制sudo apt install stress-ng
stress-ng --serial 4 --serial-dev /dev/ttyCH344USB0 -t 24h
建议定期检查驱动更新:
bash复制cd ~/ch343ser_linux
git pull
make clean
make
sudo make install
升级内核后需要重新编译驱动:
当设备异常时的标准排查步骤:
dmesg输出这套解决方案不仅适用于CH344芯片,其思路和方法也可以推广到其他USB转串口设备的驱动管理场景。关键在于理解Linux的设备驱动匹配机制和udev规则的工作原理,这样遇到类似问题时就能快速定位并解决。