TECHNICAL GUIDE

串口扩展芯片驱动移植指南_Linux与Android下多路UART设备节点与调试方法 | 上海耀罡电子

扩展芯片出来四个串口,在应用层就是四个 ttyS/ttyUSB。本文把设备节点规划、驱动结构、权限与调试手段讲透,让你少走两周的弯路。

更新于 2026-10-26 约 10 分钟读完 上海耀罡电子科技有限公司

先明确目标:让扩展口变成标准 tty

最终效果

驱动的核心目标不是「把数据搬过去」,而是让扩展出来的每一路串口,在操作系统眼里就是一个普通的串口设备。

具体来说,系统里应该出现这样的设备节点:

/dev/ttyS0   ←  主控自己的 UART
/dev/ttyS1   ←  扩展通道 1
/dev/ttyS2   ←  扩展通道 2
/dev/ttyS3   ←  扩展通道 3
/dev/ttyS4   ←  扩展通道 4

注意后缀是 ttyS 而不是 ttyUSB。这是刻意的:ttyUSB / tftdi 那一类是 USB 转串口芯片的驱动,走 USB 枚举、热插拔、CDC 协议这一整套机制;而扩展出来的串口在硬件上就是主控 UART 的延伸,根本没有 USB 这层,套用 USB 驱动模型只会带来不必要的复杂度。用标准 TTY 模型的直接好处是:应用程序完全不需要改代码,原本打开 ttyS0 的程序,把参数改成 ttyS1 就能用。

设备节点规划:为什么顺序会变

内核版本带来的差异

Linux 3.1 以下与 3.1 以上使用的是两套完全不同的架构。这个分界点几乎所有做串口扩展的工程师都踩过。

系统 串口架构 开发方式
Linux 3.1 以上 Device Tree 架构(DTS) 在设备树中描述硬件,驱动代码尽量通用
Linux 3.1 以下 平台设备 + 私有结构体 每个平台一份私有驱动,移植成本高
Android 5.0 – 8.0 基于 Linux 3.x 内核 沿用内核串口框架

为什么用 DTS 更省事

在 Device Tree 架构下,硬件描述和驱动代码是分离的。设备树文件负责声明「有几路串口、每路的寄存器地址和中断号是多少、时钟频率多少」,驱动代码只负责按标准框架注册这些端口。

这样带来一个非常实际的收益:同一份驱动代码可以跨多个平台复用。你在 A 平台(RK3568)上调试好的驱动,换到 B 平台(STM32MP1)通常只需要改设备树,不用重写驱动。对于同时做多款产品的方案公司,这个差别是很大的。

设备编号为什么会跳号

很多开发者会遇到一个问题:明明只接了一个扩展芯片,为什么系统里冒出 ttyS3 到 ttyS8 一串节点,而不是 ttyS1 到 ttyS4?

原因是内核在枚举串口时会给所有可能的端口编号分配节点,包括那些硬件上不存在、但驱动注册时声明了编号的口。主控自身占用了若干编号,扩展芯片再往后排。

这带来一个实际影响:不能假设扩展口一定是连续的 ttyS1–ttyS4。正确做法是在设备树里显式指定编号,或者在应用层通过设备特征(VID/PID、序列号)来识别,而不是写死节点名。

驱动结构与子口配置方式

一层主控口 + 多个子口

扩展芯片的典型结构是:一个主串口连到主控,芯片内部再分出多个子串口。主控实际上只占用一个 UART 外设。

这带来一个直接的好处:主控的引脚资源压力被大幅缓解。一个原本只有 2~3 路串口的 MCU,接上扩展芯片后能同时管理十几台串口设备。对 POS 机、网关这类设备密度高的产品,价值非常直接。

子口参数独立配置

WK2168 与 WK2124 的每个子口都支持独立配置,包括波特率、数据位、停止位、校验方式,以及独立的 256 级收发 FIFO。

「独立」两个字是关键。很多 MCU 的硬件 UART 有波特率分频器但不够用,导致两路设备波特率不同时必须软件模拟;而扩展芯片每个子口都有自己的分频与 FIFO,四路设备波特率互不相同也可以同时稳定工作。这对接不同厂商设备的现场集成非常实用。

寄存器的组织方式

典型的扩展芯片寄存器空间分三块:公共寄存器(中断使能、电源管理、全局配置)、通道寄存器(每通道的波特率分频、FIFO 控制、工作模式)、以及 FIFO 数据区。

驱动的寄存器定义通常用结构体把位域拆开,这样上层调用时是可读的:``c typedef struct { volatile uint32_t CTRL; /* 全局控制 */ volatile uint32_t INT_EN; /* 中断使能 */ volatile uint32_t INT_STATUS;/* 中断状态 */ /* 每通道寄存器基址,循环访问 */ volatile uint32_t CH_REG[CH_NUM]; } wk_dev_t; ``

这种位域拆分的写法比裸位运算好维护得多——调试时可以直接打印字段值定位问题。

流控配置:硬件流控 vs 软件流控

两种流控的差别

方式 机制 优点 代价
软件流控 用 XON/XOFF 字符在数据流里插入控制码 不占额外引脚 控制码会与二进制数据冲突,占带宽
硬件流控 RTS/CTS 独立信号线 不干扰数据,可靠性高 每个子口要占用一对 GPIO

什么时候必须用硬件流控

在以下场景里,软件流控会出问题:

数据是二进制的,可能包含 0x11(XON)或 0x13(XOFF)字节,接收端会把它误当成流控指令而停等;波特率较高且数据量大,控制码插入会占用有效带宽;干扰环境复杂,靠字符级握手不如硬件信号可靠。

WK2168-IQPG 支持硬件流控,在需要接高速或二进制协议的设备时(如某些扫码枪、带加密握手协议的读卡器),这是选它而不是 WK2124 的实际理由。

半双工 485 的方向控制

前面提到过 RS485 半双工需要 DE/RE 控制。有硬件流控能力的芯片可以把方向控制纳入芯片内部时序,主控侧不需要额外管 GPIO。

即使主控要自己控制方向,也要遵循一个硬性规则:必须等移位寄存器发送完成(TC/TXE 标志置位)才能切换方向,不能等发送缓冲区空。这是「最后一个字节丢失」这类偶发故障的根本原因。

权限:为什么普通程序打不开设备

两个层次的权限

Linux 下访问串口设备要通过两道关:

第一层是设备节点的属主与权限位(-rw-rw---- 1 root dialout),决定「哪些用户组可以读写这个节点」;第二层是 SELinux / AppArmor 等安全模块的策略,在 Android 上尤其严格。

Android 5.0 到 8.0 的 SELinux 策略相对宽松,但从 Android 9 开始,未在策略中声明的设备节点,即使是 root 身份也可能被拒绝访问。这一点在把方案从 8.0 往上迁移时经常成为阻碍。

实用做法

场景 做法
Linux 应用 把运行账号加入 dialout 组,或 udev 规则里设置 MODE=0666
Android 应用 在 sepolicy 中为设备节点声明类型与权限
临时调试 chmod 666 设备节点,或 setenforce 0(仅调试用)

udev 规则的写法

如果需要按 USB 路径或序列号做稳定匹配,可以写一条 udev 规则把设备节点固定成软链接,这样即使枚举顺序变了,应用层也不用改:

# /etc/udev/rules.d/99-wk-serial.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="04d8", \
  SYMLINK+="wk_serial0", MODE="0666"

这样 /dev/wk_serial0 就稳定指向扩展出来的第一路,彻底摆脱了「为什么这次编号变了」的困扰。强烈建议在量产方案里这么处理——设备枚举顺序在不同批次、不同启动时序下都可能变化,靠写死 ttyS 编号的代码早晚会在客户现场出问题。

调试方法与故障排查表

先分清是哪一层的问题

串口不通时,正确的排查顺序是从物理层往上一层走:

物理层  →  线路/电平/接线/终端电阻
    ↓
驱动层  →  设备节点是否出现、能否 open
    ↓
协议层  →  波特率/数据位/校验/流控配置
    ↓
应用层  →  应用逻辑与解析

最常见的错误是一上来就怀疑应用代码。实际上如果设备节点根本没出现,说明驱动或设备树就没配好,跟应用层毫无关系。

常用调试手段

工具 用途
ls /dev/ttyS* 确认节点是否创建,编号是否与预期一致
dmesg | grep -i tty 看驱动初始化日志,有无探测到硬件
stty -F /dev/ttyS1 -a 读取当前串口参数,核对波特率与流控
cat /dev/ttyS1 直接读原始数据,判断是硬件没发还是解析错
串口调试助手 PC 端模拟主机,验证从机行为
示波器 / 逻辑分析仪 看实际波形,判断有无数据、极性对不对

故障排查表

现象 可能原因 排查方向
设备节点根本没出现 设备树配置错误 / 驱动未编译 dmesg 看探测日志
节点存在但 open 失败 权限不足 / 节点被占用 ls -l 看权限;lsof 查占用
能 open 但收不到数据 接线错 / RX TX 接反 / 无共地 交换 TX RX 试一次
数据乱码 波特率或校验位不匹配 stty -a 核对两端参数
数据偶尔丢最后一字节 485 方向切换过早 等 TC 标志再切方向
高负载时丢包 FIFO 溢出 / 中断处理不及时 减小单次读写长度,提高中断优先级

常见问题

串口扩展芯片的驱动是通用的吗?

在 Linux 3.1 以上的 Device Tree 架构下,驱动代码可以做到基本通用,不同平台主要改设备树。Linux 3.1 以下是平台设备架构,基本每个平台都要一份私有驱动。WK 系列支持 Linux 2.6 至 4.4 以及 Android 5.0 至 8.0 多个内核版本,具体适配情况可联系我们确认。

扩展出来的串口为什么不是 ttyUSB?

因为扩展口在硬件上是主控 UART 的延伸,不存在 USB 枚举与 CDC 协议这一层。用标准 TTY 模型(ttyS)可以省掉热插拔、VENDOR/PID 匹配、驱动重绑定等一整套 USB 设备管理机制,而且应用程序无需任何改动——原本打开 ttyS0 的程序换个节点名即可。

四路扩展的波特率可以各不相同吗?

可以。WK2168 与 WK2124 的每个子口都有独立的波特率分频器和独立的 256 级收发 FIFO,四路设备波特率不同也能同时稳定工作。这是扩展芯片相比 MCU 硬件 UART 的一个实际优势——很多 MCU 的 UART 分频器不够用,两路不同波特率就必须软件模拟。

驱动移植最常卡在哪一步?

绝大多数情况卡在两个地方:一是设备树里设备节点号写死,换平台或换启动顺序后编号变了,应用代码全错;二是 Android 高版本的 SELinux 策略没有声明对应设备节点类型,权限配置对但依然访问被拒。建议从一开始就按 udev 规则或 sepolicy 的方式做稳定标识,避免后期返工。

硬件流控和软件流控该选哪个?

数据是二进制格式、波特率较高、或现场干扰较大时,选硬件流控。WK2168-IQPG 支持硬件流控,同时主接口内置 RS485 自动收发控制,可以一并解决半双工方向切换的时序问题。如果四路都是低速的短距单端设备,软件流控足够,还能省下 GPIO。

相关产品方案