简介:这份资源面向在嵌入式 Linux 平台上开发串口通信的工程师与学习者,聚焦 Tina Linux 系统下 USB 转串口通信的落地实现,并已在全志 Linux 平台上完成验证。内容围绕 CH340、FTDI、PL2303 等常见桥接芯片的工作原理展开,涵盖 USB 设备驱动代码、驱动编译进内核或按模块加载的配置方式、udev 规则与设备节点权限设置,以及基于标准串口 API 的数据收发与参数配置,同时给出内核日志排查与错误处理思路。压缩包共 2 个文件,包含 1 个 c 源码文件与 1 份 pdf 文档,源码对应驱动实现,文档提供步骤指南与代码示例,整体约 305KB,体积轻便、便于随取随用。目前已有 798 人学习,适合需要快速打通 USB 转串口链路的开发者对照实践与查漏补缺。
1. 一块全志板子插上 USB 转串口模块之后:Tina Linux 下到底要打通哪几层
手上有一块跑 Tina Linux 的全志方案板,调试口被占满,或者板子上原生串口不够用,最省事的办法就是插一个 USB 转串口模块(CH340、CP2102、FT232、PL2303 这类)。插上去之后lsusb能看到设备,/dev/ttyUSB0却迟迟不出现,或者出现了但一读就报错——这是很多人第一次在 Tina Linux 上做 USB 转串口通信时遇到的真实场景。这份资源围绕的就是这件事:在全志平台的 Tina Linux 上,把 USB 转串口从内核驱动、设备节点、权限配置到应用层收发完整跑通,并且是经过验证的实现方法。它适合做嵌入式调试、工控数据采集、板卡外设扩展的从业者,也适合刚接手全志方案、需要快速把串口链路搭起来的新手。核心要打通的其实是四层:USB 主机控制器认到设备、内核串口驱动绑定、tty 节点生成、应用层按正确参数打开。任何一层断了,现象都长得差不多,所以后面按层拆。
2. 驱动与内核配置:为什么你的 ttyUSB0 不出现
2.1 先分清 USB 转串口的三段式结构
USB 转串口不是「插上就是一个串口」这么简单。物理上是一个 USB 设备,协议上走的是 USB CDC 或者厂商私有协议,内核里需要三样东西配合:USB 主机控制器驱动(全志平台常见的是 MUSB、EHCI/OHCI 或 DWC2,取决于具体芯片)、USB 串口类驱动(usbserial核心加具体芯片的转换驱动,如ch341、cp210x、ftdi_sio、pl2303)、以及 tty 层。前两者绑定成功后,tty 层才会注册出/dev/ttyUSB0这样的节点。
所以排查顺序永远是自下而上:先确认 USB 主机控制器工作正常(lsusb能列出设备),再确认对应转换驱动已加载(lsmod或dmesg有绑定日志),最后才看 tty 节点。很多人一上来就cat /dev/ttyUSB0,节点都没有,当然报 No such file。常见做法是插上模块后立刻dmesg | tail -30,看内核有没有打印usb 1-1: new full-speed USB device以及后续的ch341-uart converter now attached to ttyUSB0之类信息,这一条日志基本能定位到卡在哪一层。
2.2 内核配置项怎么开
Tina Linux 基于 Linux 内核,配置走的是内核 menuconfig 加 Tina 自己的 defconfig。需要确保的配置项大致如下表,具体名字随内核版本略有差异,但方向一致。
| 配置项 | 作用 | 不开的后果 |
|---|---|---|
CONFIG_USB_SUPPORT | USB 子系统总开关 | 整个 USB 栈不存在 |
CONFIG_USB/CONFIG_USB_EHCI_HCD等 | 主机控制器 | lsusb无设备 |
CONFIG_USB_ACM | CDC ACM 类设备 | 部分模块识别不了 |
CONFIG_USB_SERIAL | USB 串口核心 | 无 ttyUSB 节点 |
CONFIG_USB_SERIAL_CH341 | CH340/CH341 | 国产模块最常见,必开 |
CONFIG_USB_SERIAL_CP210X | CP210x | Silicon Labs 模块 |
CONFIG_USB_SERIAL_FTDI_SIO | FTDI | FT232 系列 |
CONFIG_USB_SERIAL_PL2303 | PL2303 | 老模块常见 |
改完之后重新编译内核和模块,烧录验证。如果驱动编成模块(=m),还要确认根文件系统里带了对应的.ko并且在启动时被加载,否则设备插上也不会自动绑定。这一步是血泪经验:配置里明明开了,但编成了模块又没打进镜像,现象和没开一模一样。
2.3 用命令确认绑定链路
插上模块后按顺序跑下面几条,基本能把问题锁死在哪一层。
# 1. 看 USB 设备有没有被主机控制器枚举到 lsusb # 2. 看内核有没有把设备绑定到串口驱动 dmesg | grep -iE "usb|tty|ch34|cp210|ftdi|pl2303" # 3. 看串口驱动模块是否已加载 lsmod | grep -iE "usbserial|ch34|cp210|ftdi|pl2303" # 4. 看 tty 节点是否生成 ls -l /dev/ttyUSB*lsusb有输出但dmesg里没有attached to ttyUSB,说明 USB 枚举成功、驱动没绑上,重点查 2.2 的配置项和模块加载。dmesg里有绑定日志但/dev/ttyUSB*不存在,多半是 devtmpfs 没挂或者 udev/mdev 规则问题,检查/dev是否正常挂载。两条都正常,才轮到应用层。参数上,dmesg加-w可以实时盯日志,插拔模块时特别有用;lsusb -t能看设备挂在哪个总线和端口上,排查供电和 hub 问题时用得上。
3. 设备节点、权限与 udev/mdev 规则:节点有了却打不开
3.1 权限问题的典型表现与处理
节点生成了,应用一打开就Permission denied,这是第二高频的坑。Tina Linux 的精简根文件系统里,/dev/ttyUSB0默认属主通常是 root、权限 660 或 600,普通用户进程打不开。临时验证可以直接chmod 666 /dev/ttyUSB0,但这只是验证手段,重启或重新插拔就失效,不能当方案。
稳妥做法是写 udev 或 mdev 规则。Tina 的精简系统常用 mdev(BusyBox 提供),规则放在/etc/mdev.conf;如果系统跑的是 udev,则放/etc/udev/rules.d/。下面给一个 mdev 的写法示例,按 VID/PID 匹配更精准,避免把所有 ttyUSB 都放开。
# /etc/mdev.conf 片段:按 USB 转串口模块的 VID/PID 设置属主和权限 # CH340 常见 VID:PID 为 1a86:7523,实际以 lsusb 输出为准 ttyUSB[0-9]* 0:0 660 @/bin/sh -c 'echo $MDEV > /tmp/last_ttyusb'逻辑说明:mdev 在设备节点创建时按规则匹配节点名,0:0是属主属组,660是权限,@后是设备插入时执行的命令。这里用一条命令把最近插入的节点名记到临时文件,方便应用层动态取节点,而不是硬编码ttyUSB0——多模块同时插的时候,节点编号是会变的,硬编码迟早翻车。参数上,VID/PID 一定要用lsusb实际读到的值,不同批次模块可能换芯片,照抄网上 PID 是常见误用。
3.2 节点编号漂移与固定绑定
一台板子插两个以上 USB 转串口模块时,ttyUSB0和ttyUSB1谁是谁取决于枚举顺序,重启后可能对调。工控场景里接错口就是事故。解决办法是用 udev 的SYMLINK按序列号或物理端口生成固定名字,例如/dev/ttyPLC、/dev/ttyMeter。前提是模块带唯一序列号(FTDI、CP210x 多数带,CH340 部分不带),不带序列号的只能按 USB 物理端口路径绑定,换口就失效,这点要提前跟硬件确认。
# udev 规则示例:按序列号生成固定软链接 # /etc/udev/rules.d/99-usb-serial.rules SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A1234567", SYMLINK+="ttyPLC"逻辑说明:SUBSYSTEM=="tty"限定只匹配 tty 设备,ATTRS{idVendor}/ATTRS{idProduct}锁定芯片型号,ATTRS{serial}锁定具体那一个模块,SYMLINK+="ttyPLC"生成固定名字。应用层永远打开/dev/ttyPLC,不再关心底层编号。参数上,序列号用udevadm info -a -n /dev/ttyUSB0查,别猜。如果系统是纯 mdev 没有 udev,就得在应用启动脚本里自己按lsusb或 sysfs 路径解析节点,逻辑更啰嗦但可行。
4. 应用层收发:termios 参数设错,数据就是收不全
4.1 串口参数里真正会咬人的几个
打开串口只是第一步,termios 里几个参数设错,现象是「能发不能收」「收到乱码」「偶尔丢字节」,非常难查。波特率、数据位、停止位、校验位这些是基础,真正容易忽略的是:VMIN/VTIME的读超时语义、CLOCAL与CRTSCTS流控、以及ICANON是否关掉。默认的规范模式(canonical)会按行缓冲、处理特殊字符,做二进制协议时必须在 raw 模式下工作,否则 0x0D、0x11 这类字节会被当成控制字符吃掉,这就是「数据收不全」的经典黑匣子。
4.2 一份可直接抄的初始化代码
下面这段 C 代码把打开、raw 模式、波特率、超时都设好,是全志板子上验证过的写法,参数按需改。
#include <fcntl.h> #include <termios.h> #include <unistd.h> #include <stdio.h> #include <string.h> int uart_open(const char *dev, int baud) { int fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) { perror("open"); return -1; } struct termios tio; memset(&tio, 0, sizeof(tio)); tcgetattr(fd, &tio); cfmakeraw(&tio); // 关掉规范模式和特殊字符处理 tio.c_cflag |= (CLOCAL | CREAD); // 忽略调制解调器线,启用接收 tio.c_cflag &= ~CRTSCTS; // 不用硬件流控,按实际接线改 tio.c_cflag &= ~CSTOPB; // 1 位停止位 tio.c_cflag &= ~PARENB; // 无校验 tio.c_cflag &= ~CSIZE; tio.c_cflag |= CS8; // 8 位数据位 cfsetispeed(&tio, baud); cfsetospeed(&tio, baud); tio.c_cc[VMIN] = 0; // 读超时模式:VTIME 决定等待 tio.c_cc[VTIME] = 10; // 单位 0.1s,即 1s 超时 tcsetattr(fd, TCSANOW, &tio); tcflush(fd, TCIOFLUSH); // 清掉打开前的残留数据 return fd; }逻辑说明:O_NOCTTY防止串口成为进程控制终端,O_NONBLOCK配合VMIN=0/VTIME=10实现「最多等 1 秒、有多少读多少」的语义,适合协议帧不定长的场景。cfmakeraw一次性把ICANON、ECHO、ISIG等关掉,比逐位清更不容易漏。tcflush在打开后清一次缓冲,避免读到上次残留的半帧数据——这一步很多人省掉,结果第一帧永远解析失败。参数上,VTIME设 0 且VMIN设 0 会变成纯非阻塞立即返回,CPU 空转;VMIN设大于 0 则变成「攒够 N 字节才返回」,协议帧长固定时可以用,不定长时别用。
4.3 收发与错误处理
写用write,读用read,但要注意write可能短写,循环写满为止;read返回 0 在VMIN=0模式下表示超时而非对端关闭,别当成断链。出错时errno为EINTR要重试,EIO通常意味着 USB 模块被拔了,需要重新打开设备。下面是一个读循环的骨架。
unsigned char buf[256]; int n = read(fd, buf, sizeof(buf)); if (n > 0) { // 正常收到 n 字节,交给协议解析 } else if (n == 0) { // VTIME 超时,无数据,继续下一轮 } else { if (errno == EINTR) continue; // 被信号打断,重试 if (errno == EIO) { /* 设备可能被拔出,走重连逻辑 */ } }逻辑说明:把「超时」和「错误」分开处理是稳定性的关键,很多实现把n<=0一律当错误,结果空闲时疯狂重连。参数上,缓冲区大小按最大帧长加余量取,256 对多数 Modbus 类协议够用,长帧协议要放大。重连逻辑里重新uart_open之前先close旧 fd,避免 fd 泄漏。
5. 避坑与排查:五条踩过的坑,按现象对号入座
5.1 插上没反应,lsusb 都看不到
现象:模块插上,lsusb列表里没有新设备,dmesg也没有枚举日志。原因:多数是供电不足或 USB 主机控制器没起来。全志板子的 USB 口供电能力有限,CH340 这类模块功耗不大一般没事,但带隔离或带大电容的模块可能拉垮;另外部分板子的 USB 口默认配置成 device 模式而非 host 模式,需要改 dts 里的dr_mode。解决:换带供电的 hub 试,确认 dts 中 USB 控制器节点是 host 或 otg 且 otg 能切 host,再看dmesg有没有vbus相关报错。
5.2 有设备没节点,驱动没绑上
现象:lsusb能看到,dmesg里只有new full-speed USB device,没有attached to ttyUSB。原因:对应转换驱动没编进内核也没加载,或者 VID/PID 不在驱动支持列表里(杂牌模块换 PID 很常见)。解决:按 2.2 确认配置项,lsmod看模块,必要时modprobe ch341手动加载看报错;PID 不匹配的可以用new_id动态添加,但更稳的是换正规模块。
5.3 节点在但打开报 Permission denied
现象:/dev/ttyUSB0存在,应用打开失败。原因:权限或属主不对,精简系统没有 udev 规则。解决:按 3.1 写 mdev/udev 规则,别长期用chmod 666糊弄,量产镜像里权限每次启动都会重置。
5.4 能发不能收,或收到乱码
现象:write成功,read拿不到数据或全是乱码。原因:termios 没进 raw 模式导致特殊字节被吞;或者波特率、数据位与对端不一致;或者 TX/RX 接反。解决:按 4.2 用cfmakeraw,用示波器或回环(TX 短接 RX)先验证本端收发,再查对端参数。回环测试是最快的二分法,别一上来就怀疑协议。
5.5 跑一段时间后 EIO,设备掉线
现象:长时间运行后read/write返回EIO,节点消失。原因:USB 线材质量差、接触不良,或模块在电磁干扰环境下掉枚举;也可能是应用没处理EIO导致 fd 泄漏后彻底卡死。解决:换屏蔽线、加磁环,应用层实现EIO后close再重开的看门狗逻辑,并在重连时重新走一遍 3.2 的固定节点解析。
6. 进阶:把串口链路做成可自愈的守护进程
单次跑通和长期稳定运行是两回事。工控和户外设备上,USB 转串口模块被拔、被干扰掉枚举、节点编号漂移,都是会发生的。我一般会把 4.2 的打开逻辑和 5.5 的重连逻辑包成一个守护进程:主循环里读串口,EIO或连续超时达到阈值就close并重新解析固定节点名(/dev/ttyPLC这类软链接),重开成功后清缓冲再继续。阈值别设太激进,连续 3 次、每次 1 秒超时再重连,能过滤掉偶发干扰,又不至于真掉线了还傻等。
验证方法上,除了回环测试,建议做一个「拔插压测」:脚本每 30 秒echo一帧到串口并读回应答,人为拔插模块若干次,观察守护进程能否在 5 秒内恢复。这个测试比任何静态检查都管用,能同时暴露权限规则、节点漂移和重连逻辑的问题。参数记录建议落一张小表,把每个模块的 VID/PID、序列号、固定节点名、波特率、流控开关记下来,换板子或换模块时直接对照,省得重新摸一遍。
| 记录项 | 示例值 | 用途 |
|---|---|---|
| VID:PID | 1a86:7523 | 写 udev/mdev 规则 |
| 序列号 | 无 / A1234567 | 决定能否固定绑定 |
| 固定节点 | /dev/ttyPLC | 应用层唯一入口 |
| 波特率/格式 | 115200 8N1 | 与对端一致 |
| 流控 | 无 | 决定 CRTSCTS 开关 |
有个习惯我保持了挺久:任何新板子第一次接 USB 转串口,都强制走一遍「lsusb → dmesg → 节点 → 回环 → 拔插压测」这五步,不跳步。早期图快直接写应用,结果在权限和节点漂移上反复翻车,返工的时间远超这五步。希望帮到你。
本文还有配套的精品资源,点击获取