简介:面向工控机、串口服务器及老式外设维护场景,这套PCI转串口卡驱动程序覆盖Y-7503、Y-7504、Y-7505、Y-7509、Y-7507等型号,主要解决操作系统识别PCI串口卡、分配I/O端口与中断资源、管理多串口并发通信以及处理超时和数据校验错误等问题,适合硬件调试、设备兼容性验证与驱动安装排错。压缩包共828个文件,约32.11MB,包含exe安装程序、sys驱动核心、inf安装信息、cat签名文件、dll运行库,以及html、pdf、txt说明文档和bat静默安装脚本,兼顾正式驱动、参数配置工具与诊断资料,目录按型号和文件用途组织,便于检索对应版本。包内还提供用户手册、设备配置工具及系统日志或诊断文件,可辅助设置波特率、数据位、停止位和奇偶校验。已有895人学习下载。读者可借助完整驱动、配置工具与说明文档完成串口参数设置、通信测试和异常排查,并参考日志与诊断文件定位兼容性问题,减少因型号差异反复搜索驱动的时间。
1. 卡插好了、灯也亮了,/dev/ttyS* 却没多出一个
工控机上最常见的加装操作之一,就是插一块 PCI 转串口卡,扩出 2 口或 4 口 RS-232/RS-485 去接 PLC、称重仪、老式扫码枪。可很多人插完重启,ls /dev/ttyS*还是主板自带的那几个,多一个都没有。原因不复杂:这类卡只有一部分桥接芯片的 ID 在内核自带的 8250_pci 表里,剩下那些必须单独装 PCI 转串口卡驱动程序,内核才认。装不好不会报错,只会安静地少几个设备节点,等你到现场才发现某个口打不开。这篇写的就是从识别芯片、编译模块、抢设备、配参数到回环验证的完整过程,新手能照着走,熟手可以只看版本差异和几个坑。
2. 装前清单:先认清芯片方案,再对齐内核版本
2.1 三条命令把卡的底细摸清
动手装之前,先别急着找源码包。同一张“4 口 PCI 转串口卡”,不同批次可能换过桥接芯片,驱动完全不通用。我一般按顺序跑三条命令,第一条认设备,第二条看资源,第三条看内核当前怎么处理它。
# 1) 列出所有串口/通信类 PCI 设备,-nn 会带上厂商号和设备号 lspci -nn | grep -Ei 'serial|comm' # 2) 看具体那块卡的 BAR 映射、中断号和已绑定驱动 lspci -vvv -s 03:00.0 | grep -E 'Region|Interrupt|Kernel driver' # 3) 看内核有没有认出串口,以及认出了几个 dmesg | grep -Ei 'ttyS|8250|serial'第一条命令的输出里,方括号中的[xxxx:yyyy]就是厂商 ID 和设备 ID,后面对源码里的 ID 表全靠它。以第二、第三条命令的输出为准:如果Kernel driver in use显示serial,说明内核的 8250_pci 已经接管了这块卡,只是口数或编号不对;如果显示Kernel driver in use为空、或者显示的是nvidia、ahci这类明显无关的驱动,那多半是 ID 没被识别,必须上厂商驱动。第三条dmesg里出现ttyS4 at I/O 0xd000 (irq = 16) is a 16550A这种行,说明注册成功;一行都没有,就是没注册。
还有一点容易被忽略:卡上印的型号和芯片型号经常对不上。我见过外壳写着“工业级 4 口”,拆开是两颗双口桥接芯片的板子,lspci会列出两个设备。所以命令要在装驱动前跑一遍,装完再跑一遍,前后对得上才算数。
2.2 内核自带 8250_pci 覆盖到哪儿
内核源码里的drivers/tty/serial/8250/8250_pci.c有一张很大的 ID 表,常见的老式 16C550 兼容桥接、单芯片多口方案、部分工控卡都在里面。判断方不方便,可以直接查内核配置和表:
# 看当前内核有没有编进 8250_pci grep -i 8250_pci /boot/config-$(uname -r) # 看模块是否已加载 lsmod | grep 8250 # 查内核源码里的 ID 表(需要有源码或 headers 包) grep -n "PCI_DEVICE" /usr/src/linux-headers-$(uname -r)/drivers/tty/serial/8250/8250_pci.c | head/boot/config-*里出现CONFIG_SERIAL_8250_PCI=y或=m,说明内核侧有能力接管;is not set就只能走厂商驱动。这几种情况我按经验分了一下:
| 场景 | 现象 | 建议路线 |
|---|---|---|
ID 在内核表里,Kernel driver in use: serial | 有 ttyS 节点,但只有 1~2 个口 | 优先调内核参数,不换驱动 |
| ID 在内核表里但功能不全(如只出 2 口、不支持 RS-485) | 节点少、ioctl 报 EINVAL | 改装厂商驱动 |
ID 不在内核表里,Kernel driver为空 | 完全无节点 | 必须装厂商驱动 |
| 装了厂商驱动后出现两套节点 | /dev/ttyS4和/dev/ttyS8都指向同一块卡 | 先卸内核模块再加载厂商模块 |
老系统(3.10 那一代内核)对 PCIe 转串口的新芯片支持普遍偏弱,很多卡在 3.10 上完全探测不到,换到 5.15 或者 6.x 内核反而直接能用。如果机器允许升级内核,先试新内核比折腾驱动源码省事得多;不允许升级的产线设备,就只能老老实实编译厂商驱动。
2.3 编译环境:头文件版本必须和运行内核严格一致
厂商驱动基本都是外部模块(out-of-tree module),编译时要用当前运行内核的头文件。这一步错位的概率最高:机器上装了linux-headers-5.15.0-91,实际跑的是5.15.0-88,编出来的.ko加载时会报version magic ... should be ...。
# 当前运行内核版本 uname -r # Debian/Ubuntu 侧装对应头文件和编译工具 sudo apt update sudo apt install -y build-essential linux-headers-$(uname -r) # RHEL/CentOS 侧 sudo yum install -y gcc make kernel-devel-$(uname -r) kernel-headers-$(uname -r) # 确认头文件目录存在,且指向正确版本 ls -l /lib/modules/$(uname -r)/build/lib/modules/$(uname -r)/build必须是一个有效软链接,指向/usr/src/linux-headers-$(uname -r)。这个链接断了,make会在第一步就报No such file or directory: /lib/modules/.../build。产线机器上常见的情况是升级过内核但没重启,uname -r是旧内核,而apt install linux-headers-$(uname -r)按旧版本装上了,编译能过、重启后新内核又用不了——所以装完驱动先别急着重启,把内核固定住(apt-mark hold或yum versionlock)再谈后续。
编译工具链还有个隐性要求:内核 5.15 之后对-Werror更敏感,厂商驱动里那些老式写法(未使用变量、隐式函数声明)会直接被当错误。如果你拿到的源码在 3.10 上能编、在 5.15 上报一堆 warning as error,那不是环境问题,是源码没适配,属于下一章要处理的事。
3. 编译安装:把厂商驱动编成模块并让设备节点冒出来
3.1 源码目录里先看三个文件
拿到驱动源码包,解压后别急着make。我习惯先确认三件事:Makefile里的模块名、README里的加载方式、驱动.c里的 ID 表。
tar -xzf pci-serial-src.tar.gz cd pci-serial-src ls -l grep -n "obj-m" Makefile grep -rn "PCI_DEVICE" *.c | headobj-m后面的名字就是最终模块文件名,比如obj-m := pci_serial_demo.o,编译产物就是pci_serial_demo.ko,后面modprobe用的也是这个名字,不是目录名。PCI_DEVICE那几行里会写明支持的厂商 ID 和设备 ID,和 2.1 里lspci -nn拿到的值对一下,对不上就是拿错驱动了——这种情况我遇到过两次,一次是厂家发的是同系列另一个口数的版本,编出来能加载,但只认出 2 个口,剩下 2 个口永远不出现,查了半天以为是中断问题。
如果源码包是压缩过的二进制加源码混合结构,通常还会有一个driver/子目录,Makefile在那里面,编译时先cd driver。这一步没有统一标准,看README最快。
3.2 make、make install、depmod、modprobe 四步
标准的外部模块编译流程不复杂,关键是四步不能省:
# 1) 编译,指定当前内核的构建目录 make -C /lib/modules/$(uname -r)/build M=$PWD modules # 2) 安装模块到 /lib/modules/$(uname -r)/extra/ 之类目录 sudo make install # 3) 刷新模块依赖,让 modprobe 能按名字找到它 sudo depmod -a # 4) 加载并确认 sudo modprobe pci_serial_demo lsmod | grep pci_serial dmesg | tail -30第一步里的-C是切到内核构建目录执行,M=$PWD告诉内核“只编译当前目录这个外部模块”,两个参数缺一个都会报No rule to make target。第二步make install有的厂商 Makefile 会顺手执行depmod,有的不会,所以第三步我一般手动补一遍,成本很低。第四步的modprobe比insmod好在会处理依赖关系,但前提是depmod跑过;直接insmod ./pci_serial_demo.ko也能加载,只是下次重启不会自动生效。
加载完立刻看dmesg。正常应该出现类似pci_serial_demo: 4 ports found和几行ttyS4 at I/O ... (irq = 16) is a 16550A的注册信息。如果只有前半句没有后半句,说明探测到了卡但注册串口失败,多半是资源冲突或者口数参数不对。
# 确认设备节点真的生成了 ls -l /dev/ttyS* cat /proc/tty/driver/serial # 老内核上能看到每个口的 uart/port/irq ls /sys/class/tty/ | grep ttyS/proc/tty/driver/serial在 5.x 之后的新内核上可能被裁剪掉,那就用/sys/class/tty/和dmesg组合判断,别因为文件不存在就以为驱动没装上。
3.3 让厂商模块先抢到设备:卸载内核模块或加黑名单
这是最容易踩的一步。内核自带的8250_pci只要 ID 表里有这个设备,启动时就会抢先 probe,设备被它占住,厂商驱动加载时拿不到,dmesg里会看到probe of 0000:03:00.0 failed with error -16(设备忙)或者干脆一点反应都没有。
处理方式有两种,按侵入性从小到大:
# 方式一:手动解绑,适合调试阶段,重启后失效 echo -n "0000:03:00.0" | sudo tee /sys/bus/pci/drivers/serial/unbind ls /sys/bus/pci/devices/0000:03:00.0/driver # 确认已无驱动绑定 # 方式二:写黑名单,持久生效,适合产线 sudo tee /etc/modprobe.d/blacklist-8250pci.conf <<'EOF' # 只黑 8250_pci,不要黑 8250 本身,否则主板自带的串口也会一起丢 blacklist 8250_pci EOF sudo update-initramfs -u # Debian/Ubuntu 侧,RHEL 侧用 dracut -funbind里的设备地址来自lspci的输出,格式是域:总线:设备.功能,常见的是0000:03:00.0。解绑后driver目录会消失,说明设备已经“裸”了,此时再modprobe厂商模块,成功率最高。
黑名单只黑8250_pci而不黑8250,这一点一定要分清:8250是串口核心,主板自带的 ttyS0~ttyS3 靠它工作;8250_pci才是 PCI 探测那部分。两个都黑掉,你的调试终端如果是串口控制台,重启后会直接失去控制台,只能进机房接显示器。
更精细的做法是用driver_override强制指定:
# 先解绑 echo -n "0000:03:00.0" | sudo tee /sys/bus/pci/drivers/serial/unbind # 指定强制绑定的驱动名 echo -n "pci_serial_demo" | sudo tee /sys/bus/pci/devices/0000:03:00.0/driver_override # 再触发绑定 echo -n "0000:03:00.0" | sudo tee /sys/bus/pci/drivers/pci_serial_demo/binddriver_override写在/etc/udev/rules.d/里也能持久化,但对一次性的现场部署,手动执行完就不管了,重启后再跑一遍脚本。产线多台机器的话,还是黑名单稳妥。
3.4 设备节点权限与 udev 规则
节点出来了不代表能打开。默认/dev/ttyS*是root:dialout 0660,普通用户直接Permission denied。老办法是usermod -aG dialout 用户名,重新登录生效;如果卡上带 RS-485 自动方向控制,还需要额外的 ioctl,那就得看驱动实现有没有对应的TIOCSRS485支持。
# 把用户加进 dialout 组,注意要重新登录,不是重新开终端 sudo usermod -aG dialout $USER id $USER | grep dialout # udev 规则:固定权限 + 固定别名,避免编号漂移 sudo tee /etc/udev/rules.d/99-pci-serial.rules <<'EOF' KERNEL=="ttyS[4-9]", SUBSYSTEM=="tty", GROUP="dialout", MODE="0660" KERNELS=="0000:03:00.0", SUBSYSTEM=="tty", SYMLINK+="ttyPLC0", GROUP="dialout", MODE="0660" EOF sudo udevadm control --reload-rules sudo udevadm trigger --subsystem-match=tty ls -l /dev/ttyPLC0第一条规则管权限,第二条用内核设备路径匹配,生成一个固定的软链接/dev/ttyPLC0。程序里直接写这个别名,不管这次是 ttyS4 还是 ttyS8,都不需要改配置。KERNELS==匹配的是物理路径,比KERNEL==(只看节点名)稳定得多,这是固定编号最省心的做法。
4. 参数配置:编号、波特率、中断逐个对齐
4.1 编号漂移的根源是 NR_UARTS 和探测顺序
老内核编译时CONFIG_SERIAL_8250_NR_UARTS默认只有 4,也就是系统最多提供 ttyS0~ttyS3 四个口。你插的是 4 口卡,加上主板自带 2 口,实际上只想用卡的 4 个,结果是主板占满 4 个名额,卡的口全部注册失败,dmesg里只会留一行too many ports之类的提示,很容易被忽略。
# 看当前内核配置的串口上限 grep -i "SERIAL_8250_NR_UARTS" /boot/config-$(uname -r) # 临时提高上限(重启失效):改启动参数 sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="8250.nr_uarts=16 /' /etc/default/grub sudo update-grub && sudo reboot # 重启后确认 cat /proc/cmdline8250.nr_uarts=16这个参数在内核 5.x 上默认值已经是 32,很多情况下不用改;但 3.10 那一代必须显式加,否则永远只出 4 个口。改完grub记得update-grub,RHEL 侧是grub2-mkconfig -o /boot/grub2/grub.cfg,命令不一样,写错版本是很常见的低级错误。
编号顺序本身也不保证稳定。同一块 4 口卡,PCI 枚举顺序变了,ttyS4 到 ttyS7 的对应关系可能整体错位。所以生产环境一律用 3.4 里的 udev 别名,程序里不写裸ttyS*。这是血泪经验,换过一次主板之后所有口全乱,靠别名救回来的。
4.2 stty 和 setserial 把线路参数落下去
节点有了、权限有了,接下来是线路参数。裸设备默认是echo、icanon模式,直接cat会看到回显和行缓冲,收数据时表现得很奇怪。
# 查看当前参数 stty -F /dev/ttyS4 -a # 设成 115200 8N1、原始模式、无流控、无回显 stty -F /dev/ttyS4 115200 cs8 -cstopb -parenb -crtscts raw -echo # 再确认一次关键项 stty -F /dev/ttyS4 -a | grep -E 'speed|cs8|parenb|cstopb|crtscts'raw是关闭行缓冲和信号处理,串口通信基本都要;-echo关本地回显,否则你发出去的数据会被终端自己吐回来,看着像对方回了;-crtscts关硬件流控,只有在对方设备确实接了 RTS/CTS 线时才开,随便开会导致大数据量时莫名卡死。
setserial管的是驱动层参数,比如baud_base(基准时钟分频)。有些厂商驱动默认baud_base是 115200,最高只能跑到 115200,接高速设备就不够用。
# 查看串口的底层参数 setserial -g /dev/ttyS4 # 把基准时钟改成实际晶振对应的值(示例:芯片用 14.7456MHz 晶振) sudo setserial /dev/ttyS4 baud_base 921600 # 确认生效 setserial -g /dev/ttyS48250 兼容 UART 的波特率算法是divisor = uartclk / (16 × baud),divisor最小为 1,所以最高波特率上限就是uartclk / 16。14.7456MHz 晶振对应上限 921600,想跑 1.5M 就必须换晶振或者芯片本身支持更高时钟,改参数是改不出来的。这一点在选型阶段就要确认,别等装完了才发现设备要 3Mbps。
4.3 非标波特率与自定义分频
工业设备上非标波特率不少,比如 1.5M、3M、250k。标准 8250 驱动只认 2 的幂次加上少数几个常见值,遇到非标值要开custom_divisor或者走spd_cust。
# 目标 250000:先设 baud_base,再用 spd_cust 走自定义分频 sudo setserial /dev/ttyS4 baud_base 4000000 stty -F /dev/ttyS4 spd_cust stty -F /dev/ttyS4 250000 # 验证:读回的值是否接近目标 stty -F /dev/ttyS4 -a | grep speedspd_cust的意思是“别用标准表算,按自定义分频来”,配合baud_base做整除,实际波特率会有误差。误差超过 2% 就开始丢字节,超过 5% 基本没法通信。所以设完之后一定要用示波器或者逻辑分析仪量一下,没有仪器就用回环测试跑长时间大数据,看有没有 CRC 错、丢包。我一般用 10000 字节回环对比,错一个字节就重新核算分频。
部分厂商驱动会暴露模块参数,加载时直接指定,比setserial更稳:
# 加载时指定口数和自定义分频,参数名以 modinfo 输出为准 modinfo pci_serial_demo sudo modprobe pci_serial_demo nr_uarts=4 custom_divisor=16modinfo列出来的parm:行就是可用参数,写进/etc/modprobe.d/pci-serial.conf就能开机生效:options pci_serial_demo nr_uarts=4 custom_divisor=16。别凭记忆写参数名,拼错了modprobe会直接报unknown parameter,加载失败。
4.4 中断共享:/proc/interrupts 是黑匣子
PCI 设备的中断可能是共享的,尤其老主板 + 多块卡。表现是数据能收但偶尔丢,或者高波特率下严重丢包。
# 看串口占用的中断号和计数 grep -Ei 'serial|8250|ttyS' /proc/interrupts # 持续观察计数是否在涨 watch -n1 "grep -Ei 'serial|8250' /proc/interrupts"计数不涨,说明中断根本没进来,数据是靠轮询捡的或者完全没收到;计数猛涨但数据不对,可能是共享中断被别的设备抢了,cat /proc/interrupts里同一行 IRQ 后面会跟多个设备名。这种情况先试pci=nomsi启动参数排除 MSI 兼容问题,再考虑换 PCI 槽位,把卡从共享槽换到独占槽。别小看换槽这一步,有的主板 PCIe 插槽走的是同一组中断线,换一个槽位直接就好了。
5. 排错避坑:驱动装上了却没反应的 5 个高频翻车点
5.1 现象:modprobe 报 Key was rejected by service
原因:开启了 UEFI 安全启动,未签名的外部模块被拒绝加载。
解决:进 BIOS 关掉 Secure Boot,或者给模块签名。产线设备不方便关的,就走签名流程,/usr/src/linux-headers-*/scripts/sign-file配合自签证书,签完再modprobe。这个报错信息藏得深,dmesg里只有一行Loading of unsigned module is restricted,很容易被当成驱动本身的问题。
5.2 现象:模块加载成功,dmesg 有 probe 但没 ttyS 节点
原因:内核的 8250_pci 抢占了设备,厂商驱动的 probe 返回-EBUSY,或者 8250 的端口名额已满。
解决:按 3.3 解绑或加黑名单,同时确认8250.nr_uarts够用。先ls /sys/bus/pci/devices/0000:03:00.0/driver看当前绑的是谁,是serial就先解绑再加载厂商模块。
5.3 现象:编译时报 implicit declaration of function 'init_timer'
原因:厂商驱动是老代码,内核 4.15 之后init_timer()被移除,改成timer_setup(),类似的还有get_user_pages参数变化、->ioctl改成->unlocked_ioctl。
解决:优先找厂商的新版本驱动;找不到就自己改,把init_timer(&x)换成timer_setup(&x, callback, 0),回调签名也要跟着改。改完编译通过只是第一步,定时器逻辑有没有变味要自己测。这种适配在 5.15 上很常见,3.10 的源码直接搬过来基本都要改一遍。
5.4 现象:能收不能发,或者发出去对方收不到
原因:多半是 RS-232 与 RS-485 模式选错,或者 DB9 的 2、3 脚接反,或者流控开错。
解决:先确认卡是 232 还是 485,485 需要驱动支持方向控制 ioctl,不支持就得外加转换器。再用回环头(短接 2、3 脚)自测,能自发自收说明本机链路没问题,问题在接线或对端参数。-crtscts一定要先关掉再测,硬件流控是最常见的假故障来源。
5.5 现象:跑几分钟后丢包,重启驱动就好
原因:驱动里的环形缓冲太小,或者中断处理里有锁竞争,高波特率下溢出。
解决:先用cat /proc/tty/driver/serial看rx:后面的 overflow 计数,涨得快就是缓冲问题。厂商驱动如果有rx_fifo之类的模块参数就调大;没有的话stty里调min和time只能缓解,治标不治本。长期跑高速率还是换支持 DMA 或者 FIFO 更深的方案,别在软件层硬扛。
6. 验证跑通:短接回环加 pyserial 收发一轮
装完不算完,要证明这条链路真的能通。最省事的验证是回环:找一个 DB9 母头,把 2 脚(RXD)和 3 脚(TXD)短接,插到要测的口上,写什么就应该读回什么。
# 后台起一个读进程,2 秒超时 timeout 2 cat /dev/ttyS4 > /tmp/loop.out & # 往同一个口写数据 printf 'PING-12345\r\n' > /dev/ttyS4 sleep 1 cat /tmp/loop.outtimeout 2是防止读进程一直挂着,cat读的是原始字节流,所以写进去的\r\n也会原样回来。如果cat一个字都没读到,先确认stty里-echo和raw有没有生效,再确认短接的是 2、3 脚而不是 2、5 脚。
用脚本测更接近真实场景:
import serial # 打开设备,115200 8N1,超时 1 秒 ser = serial.Serial('/dev/ttyS4', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=1) payload = b'PING-12345\r\n' ser.reset_input_buffer() # 清掉残留数据,避免上次的脏数据干扰 ser.write(payload) echo = ser.read(len(payload)) # 回环时应读回等长数据 print('sent:', payload) print('recv:', echo) print('match:', echo == payload) ser.close()reset_input_buffer()这行别省,驱动刚加载时 FIFO 里可能有上电噪声;read(len(payload))限定读等长,避免一直阻塞。match打印True才算真的通。
按顺序把这几项过一遍,基本能定位到具体哪一层出问题:
| 验证项 | 命令 | 期望结果 |
|---|---|---|
| 设备识别 | lspci -nn | grep -i serial | 能看到卡的厂商/设备 ID |
| 驱动绑定 | ls /sys/bus/pci/devices/0000:03:00.0/driver | 指向厂商驱动目录 |
| 节点生成 | ls -l /dev/ttyPLC0 | 软链接指向 ttyS4 之类 |
| 参数正确 | stty -F /dev/ttyS4 -a | speed 与目标一致,raw、-echo |
| 回环收发 | 上面那段 Python | match 输出 True |
| 长稳测试 | 循环发 10000 字节比对 | 无丢字节、无 overflow 增长 |
回环通了只代表本机链路 OK,接真实设备前还要拿实际线序和协议跑一遍。我现在的习惯是每装完一台机器,先把回环测试脚本和 udev 别名规则一起存进部署脚本里,下次换机器直接跑,不再靠记忆去ls /dev/ttyS*猜哪个口是哪个。吃过一次现场返工的亏之后,这套验证我每次都强制走一遍,哪怕只是换了一根线。希望帮到你。
本文还有配套的精品资源,点击获取