1. 从一根USB转串口线说起:CH341的“自我介绍”
如果你玩过单片机、树莓派,或者调试过路由器、工控板,那你抽屉里大概率躺着一两根USB转串口的小玩意儿。它们长得像U盘,一头是USB-A口,另一头是几根杜邦线,用来连接设备的串口引脚。这东西的核心,往往就是一颗小小的芯片,比如我们今天要聊的CH341。
CH341是沁恒微电子推出的一款非常经典的USB转串口芯片。它便宜、稳定、出货量巨大,几乎成了电子爱好者和嵌入式开发者的“标配”。当你把它插上电脑的USB口,在Linux系统里,它可能就变成了 /dev/ttyUSB0 这个设备文件。你可以用 minicom、screen 或者自己写的程序打开这个文件,像读写普通文件一样和远端的单片机“聊天”。这个过程看似简单,背后却是一场跨越了USB总线驱动、serial抽象层和TTY子系统三个“王国”的精密协作。
我自己在调试一块STM32核心板时就遇到过坑。插上CH341的转换器,dmesg 里能看到设备识别了,但就是没有 /dev/ttyUSB0 出现。当时一头雾水,只能凭感觉瞎试,一会儿怀疑内核没编译驱动,一会儿怀疑权限问题。后来才明白,问题出在内核的serial层模块没有自动加载。这个经历让我下定决心,必须把从USB插口到 /dev/ttyUSB0 这条路上的每一道关卡都搞清楚。今天,我就以CH341这个“老朋友”为线索,带你深入Linux内核,看看数据究竟是怎么“过五关斩六将”,最终变成我们能用的串口的。我们会重点关注那个关键的“粘合层”——usb-serial层,看它如何巧妙地桥接起USB的“高速世界”和TTY的“字符流世界”。
2. 第一站:CH341设备驱动的“标准动作”
让我们先从最具体的代码开始。在Linux内核源码里(比如 drivers/usb/serial/ch341.c),你能找到CH341的驱动程序。它看起来大概是这样的:
static struct usb_serial_driver ch341_device = {
.driver = {
.owner = THIS_MODULE,
.name = "ch341-uart",
},
.id_table = ch341_id_table,
.num_ports = 1,
.open = ch341_open,
.close = ch341_close,
.write = ch341_write,
.read_bulk_callback = ch341_read_bulk_callback,
.attach = ch341_attach,
.port_probe = ch341_port_probe,
.port_remove = ch341_port_remove,
};
这就是一个 usb_serial_driver 结构体的实例。你可以把它理解为CH341芯片的“人格画像”或“操作手册”,专门告诉内核:“嘿,我是CH341,我长这样(id_table),我能干这些活(open, write等函数)”。
.id_table 是关键,它是一张“身份证”列表:
static const struct usb_device_id ch341_id_table[] = {
{ USB_DEVICE(0x4348, 0x5523) }, // 厂商ID: 0x4348, 产品ID: 0x5523
{ USB_DEVICE(0x1a86, 0x7523) }, // 另一个常见的CH341变体
{ } /* Terminating entry */
};
当内核的USB核心检测到一个新插入的USB设备时,会拿着它的厂商ID和产品ID,去所有已注册驱动的 id_table 里挨个比对。一旦匹配上,就说明“哦,这个设备归你管了!”
那么,这个“操作手册”是怎么交到内核手里的呢?在驱动文件的最后,你会看到一行宏:

1231

被折叠的 条评论
为什么被折叠?



