手把手教你编译Linux下的CH340驱动:从零开始,不靠包管理器也能用串口
你有没有遇到过这种情况:手里的开发板插上电脑,ls /dev/tty*死活看不到/dev/ttyUSB0?
dmesg 一查,满屏都是unknown USB device或者no such device……
而你用的不过是常见的 Arduino Nano CH340 版、ESP-12F 模块或者某宝十块钱包邮的 USB 转 TTL 线——核心芯片正是CH340。
别急,这多半不是硬件坏了,而是你的 Linux 系统里缺了那个关键的小东西:ch34x 驱动模块。
更糟的是,如果你用的是老旧发行版、定制嵌入式系统,甚至没网的工控机,想靠apt install或yum install安装驱动?根本行不通。
那怎么办?总不能每次调试都换台电脑吧?
答案是:自己动手,编译一个.ko文件,把驱动“种”进内核里。
这篇文章不讲虚的,也不复制粘贴手册。我会带你一步步从源码开始,完成 CH340 驱动的获取、编译、安装和验证全过程。全程不需要联网下载额外包(除了源码),适合离线环境部署,也帮你真正搞懂 Linux 内核模块是怎么跑起来的。
为什么 CH340 在 Linux 上经常“失灵”?
我们先来理清楚一个问题:为什么 FTDI 和 CP210x 几乎即插即用,而 CH340 却常常要手动折腾?
简单说,就一句话:
CH340 不是标准的 CDC ACM 设备。
什么意思?
大多数现代 USB 转串芯片(比如 FT232RL、CP2102)遵循 USB 组织定义的CDC ACM(Communication Device Class - Abstract Control Model)标准。这类设备一旦插入,Linux 内核自带的cdc-acm驱动就会自动识别并绑定,立刻生成/dev/ttyACM0或/dev/ttyUSB0。
但 CH340 不走寻常路。它使用的是厂商自定义类(bInterfaceClass = 0xFF),不属于任何标准类别。这就意味着:必须有一个专门写给它的驱动程序才能工作。
这个驱动就是ch34x.ko。
好消息是,从 Linux 内核4.10+ 开始,官方已经将ch34x驱动合并进了主线代码树。所以如果你用的是较新的 Ubuntu、Debian、Fedora,大概率插上去就能用。
坏消息是:
- 很多国产开发板配套的镜像基于老内核(如 3.18、4.4)
- 某些裁剪过的嵌入式系统为了精简体积,直接去掉了ch34x模块
- Secure Boot 启用后,第三方模块加载受限
这些情况都会导致你明明插了设备,系统却“视而不见”。
这时候,你就得自己动手丰衣足食了。
第一步:确认问题出在驱动,而不是别的地方
在动手前,先做一次快速诊断,避免白忙一场。
1. 插上设备,看内核日志
dmesg | tail -15如果看到类似输出:
usb 1-1: new full-speed USB device number 5 using xhci_hcd usb 1-1: New USB device found, idVendor=1a86, idProduct=7523 usb 1-1: Product: USB2.0-Serial usb 1-1: Manufacturer: wch.cn恭喜你,USB 层已经识别成功!VID 是1a86,PID 是7523—— 这正是 CH340G 的标志性参数。
但如果下面没有紧接着出现:
usbcore: registered new interface driver ch34x usbserial: USB Serial support registered for ch34x ch34x ttyUSB0: ch34x converter now attached那就说明:驱动没加载。
2. 检查是否已有 ch34x 模块
运行:
lsmod | grep ch34x如果有输出,说明模块已加载。如果没有,再查一下系统有没有这个模块文件:
find /lib/modules/$(uname -r) -name "ch34x.ko*"如果找不到,那就只能自己编译了。
第二步:准备编译环境
要编译内核模块,你需要三样东西:
- 当前运行内核的头文件(headers)
- 构建工具链(make, gcc)
- 驱动源码
我们一个个来。
1. 安装内核头文件(最关键!)
模块编译必须与当前运行的内核版本完全匹配。否则会出现Unknown symbol in module或Module version mismatch错误。
查看当前内核版本:
uname -r # 输出示例:5.4.0-42-generic根据你的发行版安装对应的头文件包:
| 发行版 | 命令 |
|---|---|
| Ubuntu/Debian | sudo apt install linux-headers-$(uname -r) |
| CentOS/RHEL | sudo yum install kernel-devel-$(uname -r) |
| Fedora | sudo dnf install kernel-devel-$(uname -r) |
⚠️ 注意:某些系统中
uname -r返回的版本号可能无法直接用于包管理器(例如带有本地修改的定制内核)。此时需要手动查找匹配的 headers 包,或从源码重建。
2. 安装基本构建工具
# Debian/Ubuntu sudo apt install build-essential # RHEL/CentOS/Fedora sudo yum groupinstall "Development Tools"3. 获取 CH340 驱动源码
最稳妥的方式是从Linux 内核源码树中提取ch34x.c,因为它保证与你当前使用的内核 API 兼容。
但我们也可以直接从沁恒官网或 GitHub 上找开源版本。
推荐使用社区维护良好的仓库:
git clone https://github.com/juliagoda/CH341SER.git cd CH341SER该仓库包含了适用于多种平台的ch34x.c和 Makefile,并支持主流内核接口。
结构大致如下:
CH341SER/ ├── ch34x.c ├── Makefile └── README.md第三步:编写正确的 Makefile 来编译模块
这是最容易出错的地方。很多人照搬网上老旧的 Makefile,结果编译失败。
我们要写一个符合kbuild 系统规范的双段式 Makefile。
创建Makefile文件内容如下:
ifneq ($(KERNELRELEASE),) obj-m := ch34x.o else KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: rm -f *.o *.ko *.mod.* *.cmd *.markers *.order *.symvers endif关键点解释:
obj-m := ch34x.o:表示我们要把ch34x.c编译为可加载模块(注意是obj-m,不是obj-y)$(MAKE) -C $(KDIR):跳转到内核构建目录(通常是/lib/modules/.../build,它是指向真实内核源码的符号链接)M=$(PWD):告诉内核构建系统“回到当前目录继续编译模块”- 双段结构是为了兼容内核构建系统的递归调用机制
保存后执行:
make如果一切顺利,你会看到:
Building modules, stage 2. MODPOST 1 modules CC ch34x.mod.o LD [M] ch34x.ko并且生成了ch34x.ko文件。
🎉 成功一半了!
第四步:加载模块并验证功能
现在进入最后一步:把.ko文件“注入”内核。
1. 加载模块
sudo insmod ch34x.ko不出意外的话,你应该能在 dmesg 中看到:
ch34x: loading out-of-tree module taints kernel. USB Serial support registered for ch34x ch34x: ch34x converter detected usbcore: registered new interface driver ch34x ch34x ttyUSB0: ch34x converter now attached同时,设备节点也生成了:
ls /dev/ttyUSB* # 应该输出 /dev/ttyUSB02. 测试通信
可以用screen或minicom连接测试:
sudo screen /dev/ttyUSB0 115200如果你连接的是单片机并发送了日志,这时应该能看到输出了!
按Ctrl+A→K→Y退出 screen。
常见坑点与解决方法
❌ 问题1:insmod: error inserting 'ch34x.ko': -1 Invalid module format
原因:内核版本不匹配,或者 headers 没装对。
解决方案:
- 确认uname -r与安装的 headers 完全一致
- 检查/lib/modules/$(uname -r)/build是否存在且能访问
- 尝试重新安装 headers 包
❌ 问题2:Unknown symbol in module(如 usb_serial_register)
这是典型的 API 变更问题。旧版驱动用了已被废弃的函数。
解决方案:
- 使用更新的驱动源码(优先选用 Linux 主线中的版本)
- 或打补丁修复符号引用
建议参考 Linux 内核源码中的drivers/usb/serial/ch341.c(注意名字是 ch341,但它也支持 ch340)
❌ 问题3:Secure Boot 阻止模块加载
在启用了安全启动的系统上(如 Ubuntu 20.04+ 默认开启),加载第三方模块会报错:
The system is running in secure boot mode...解决方案有两个:
方法一:禁用 Secure Boot(临时方案)
进 BIOS 设置,关闭 Secure Boot。
方法二:签名模块(生产级做法)
生成密钥并对模块签名:
# 生成私钥和公钥 openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=CH34X Driver/" # 注册公钥到 MOK(Machine Owner Key) sudo mokutil --import MOK.der # 重启后按提示设置密码,完成注册 # 签名模块 /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 ./MOK.priv ./MOK.der ch34x.ko # 再加载 sudo insmod ch34x.ko这样就能通过 Secure Boot 验证了。
如何让驱动开机自动加载?
每次重启都要手动insmod太麻烦。我们可以让它自动加载。
方法1:加入模块加载列表
echo 'ch34x' | sudo tee /etc/modules-load.d/ch34x.conf系统启动时会自动执行modprobe ch34x。
方法2:安装到系统模块路径
sudo cp ch34x.ko /lib/modules/$(uname -r)/kernel/drivers/usb/serial/ sudo depmod -a然后就可以用modprobe ch34x替代insmod了。
进阶技巧:为 ARM 板交叉编译驱动
如果你的目标平台是树莓派、全志H3/H5、RK3399 等 ARM 设备,就不能在 x86 主机上直接编译。
你需要做交叉编译。
修改 Makefile 添加架构和工具链:
ARCH ?= arm CROSS_COMPILE ?= arm-linux-gnueabihf- KDIR := /path/to/target/kernel/build obj-m := ch34x.o all: $(MAKE) -C $(KDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules clean: rm -f *.o *.ko *.mod.* *.cmd *.markers *.order *.symvers然后在主机上运行:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KDIR=/home/user/rpi-kernel/build生成的.ko文件拷贝到开发板即可加载。
总结一下:我们到底学会了什么?
这不是一篇“复制命令就能跑”的快餐教程。我们真正掌握的是:
✅ 如何判断 CH340 设备是否被正确识别
✅ 如何搭建独立的内核模块编译环境
✅ 如何使用标准 kbuild 系统编译.ko文件
✅ 如何手动加载模块并排查常见错误
✅ 如何应对 Secure Boot 和交叉编译等现实挑战
更重要的是,你不再依赖别人打包好的.deb或.rpm文件。你可以面对任何一台没有网络、没有包管理器、甚至连驱动都没装的嵌入式设备,自信地说一句:
“让我给你装个驱动。”
而这,正是嵌入式工程师的核心能力之一。
最后一点建议
虽然现在主流发行版基本都自带ch34x支持,但在国产化替代浪潮下,越来越多的设备采用 CH340/CH341 芯片。了解其驱动机制,不仅能解决眼前问题,还能帮助你在面试、项目评审、故障排查中脱颖而出。
下次当你看到同事还在百度“Linux 插 CH340 没反应怎么办”,你可以默默打开终端,敲下make && sudo insmod ch34x.ko,然后看着/dev/ttyUSB0出现在屏幕上。
那一刻,你已经不是一个普通的使用者,而是掌控系统的那个人。
如果你觉得这篇实战指南有用,欢迎收藏转发。也欢迎在评论区分享你在实际项目中遇到的奇葩驱动问题,我们一起拆解解决。