【Linux驱动开发】Linux UART 通信详解:从硬件到驱动再到应用
Linux UART 通信详解:从硬件到驱动再到应用
UART(Universal Asynchronous Receiver/Transmitter)是嵌入式与 PC 领域最常见、最基础的异步串行通信接口。Linux 内核将其抽象为 TTY/串口子系统,为上层提供统一、可移植的访问方式。本文从硬件原理、协议帧格式、底层驱动框架、内核与用户态接口、实际应用到调试方法,系统梳理 Linux UART 通信的全景。
1. 硬件原理
1.1 物理层
- 信号线:TX(发送)、RX(接收)、GND(地)即可通信;可选 RTS/CTS(硬件流控)、DTR/DSR、RI/DCD(调制解调器状态)。
- 电平标准:
- TTL(3.3 V / 5 V)直接接 MCU/SoC;
- RS-232:±3~15 V,需电平转换芯片(MAX3232 等);
- RS-485:差分、半双工、抗干扰,需收发器(MAX3485 等)。
- 波特率:常见 9600、115200、921600、3 M、4 Mbps(受芯片与时钟限制)。
1.2 UART 控制器内部框图

- 发送端:TX FIFO → 并串转换 → 发送移位寄存器 → TX 引脚;
- 接收端:RX 引脚 → 接收移位寄存器 → 并串转换 → RX FIFO;
- 波特率生成器:基于输入时钟分频(
div = clk / (baud × oversample),通常 16× 过采样); - 中断/DMA:TX FIFO 空、RX FIFO 触发阈值、帧错误、Break、Overrun 等;
- 流控:RTS/CTS 自动拉低/拉高,或软件流控 XON/XOFF。
2. 协议帧格式
UART 是异步字节流协议,无共享时钟,靠“起始位”同步。

| 字段 | 位数 | 说明 |
|---|---|---|
| 起始位 | 1 | 低电平 |
| 数据位 | 5~9 | LSB 在前,常见 8 位 |
| 校验位 | 0/1 | 可选奇/偶/标记/空间 |
| 停止位 | 1/1.5/2 | 高电平,常见 1 位 |
空闲位:线路保持高电平;Break:故意拉低 > 一帧时间,用于唤醒或信号。
3. Linux 内核驱动框架
Linux 把 UART 纳入 TTY 子系统,分层:

3.1 核心数据结构
struct uart_driver:一个芯片厂商驱动实例,注册到内核;struct uart_port:描述一组寄存器/IO 映射、IRQ、FIFO 深度等;struct uart_ops:底层回调(startup/shutdown/tx_empty/set_mctrl/…);struct tty_port:与 TTY 层交互,管理缓冲、流量控制、线路规程;struct tty_driver:TTY 层向上提供字符设备(/dev/ttyS*,/dev/ttyAMA*,/dev/ttyUSB*等)。
3.2 注册流程(简化)
- 驱动
module_init()中uart_register_driver(); - 设备树/ACPI 或平台代码探测到端口 →
uart_add_one_port(); - TTY 层自动创建设备节点,用户空间
open(/dev/ttyS0)即关联到该端口; - 线路规程(N_TTY 默认,或 N_PPS/N_IRDA/…)负责把字节流加工成行缓冲/包。
3.3 数据收发
- 轮询(早期):
uart_console_write()在启动阶段; - 中断:RX 触发 →
serial_in()读 FIFO →tty_insert_flip_char()→tty_flip_buffer_push()→ 用户read(); - DMA:
- 散聚 DMA(
dmaengine)把 UART FIFO 与内存环形缓冲绑定; - 减少 CPU 介入,115200×8×N 路同时收发无压力;
- 需要驱动实现
dma_rx_push/dma_tx_submit。
- 散聚 DMA(
3.4 流控实现
- 软件流控 (XON/XOFF):线路规程识别
^S/^Q,驱动自动停/启 TX; - 硬件流控 (RTS/CTS):
- 驱动
set_mctrl()拉高/拉低 RTS; - RX FIFO 快满 → 拉低 RTS;CTS 为低则硬件自动停 TX;
- 与 RS-485 半双工联动:发数据前拉高 DE(Driver Enable),发完产生 TX-Done 中断 → 拉低 DE,避免总线冲突。
- 驱动
3.5 控制台与早期打印
console_initcall()注册struct console;- 内核启动早期
printk()→uart_console_write()直接写 TX 寄存器,无中断; - 后期
console_on_rootfs→ 切换到 TTY 层,支持getty/login。
4. 用户态访问与配置
4.1 基本操作
# 查看串口设备
$ dmesg | grep tty
$ ls -l /dev/ttyS* /dev/ttyAMA* /dev/ttyUSB*# 配置波特率 115200 8N1,无流控
$ stty -F /dev/ttyS0 115200 cs8 -cstopb -parenb -ixon -ixoff# 收发数据
$ cat /dev/ttyS0 &
$ echo "hello" > /dev/ttyS0
4.2 termios 编程
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>int open_uart(const char *dev) {int fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK);struct termios tio;tcgetattr(fd, &tio);cfsetispeed(&tio, B115200);cfsetospeed(&tio, B115200);tio.c_cflag &= ~PARENB; // 无校验tio.c_cflag &= ~CSTOPB; // 1 停止位tio.c_cflag &= ~CSIZE;tio.c_cflag |= CS8; // 8 数据位tio.c_cflag |= CREAD | CLOCAL;tcsetattr(fd, TCSANOW, &tio);return fd;
}
4.3 高级:RAW 模式、非阻塞、select/epoll
cfmakeraw()关闭行缓冲、回显;VMIN=0 VTIME=0实现非阻塞立即返回;select/poll/epoll监听可读/可写,配合环形缓冲实现高吞吐。
5. 实际应用场景
| 场景 | 典型芯片/接口 | 注意要点 |
|---|---|---|
| 启动控制台 | x86 /dev/ttyS0, ARM /dev/ttyAMA0 | 波特率 115200,需在内核 console=ttyS0,115200 |
| GPS 模块 | TTL 3.3 V,9600/115200 | 注意电平匹配,用 PPS 线路授时 |
| RS-485 传感器网络 | 半双工,MAX3485 | 需 GPIO 控制 DE/RE,驱动里 rs485conf |
| 蓝牙 HCI | UART H:4 | 需硬件流控,波特率 921600 |
| 固件升级 | bootloader UART | 通常 8N1,115200,XMODEM/YMODEM |
6. 调试与故障排查
6.1 信号测量
- 示波器/逻辑分析仪:查看 TX/RX 波形,确认波特率、起始/停止位、抖动;
- 差分探头测 RS-485 A/B 波形,检查反射/终端匹配(120 Ω)。
6.2 软件跟踪
dmesg查看 Overrun/Framing Error;cat /proc/tty/driver/serial显示内部计数;stty -F /dev/ttyS0 -a查看当前配置;setserial -a /dev/ttyS0查看 UART 类型、FIFO 深度、端口/IRQ;irqtop/perf监测中断频率,判断是否频繁 Overrun。
6.3 常见故障
| 现象 | 可能原因 | 排查/解决 |
|---|---|---|
| 乱码 | 波特率/格式不匹配 | 双方 stty 对比,示波器测位宽 |
| 丢字节 | Overrun,RX FIFO 溢出 | 提高波特率容错,启用 DMA,降低中断延迟 |
| 无响应 | TX/RX 反接 | 交叉线 TX→RX,RX→TX;RS-485 A/B 接反 |
| 高误码 | 电缆过长/无屏蔽 | 降波特率,用 RS-485/422 差分,加终端电阻 |
| 流控失效 | 线路未接/极性错 | 万用表量 RTS/CTS 电平,驱动里 autocts |
7. 性能优化要点
- DMA 环形缓冲:减少每字节中断,CPU 占用 <1% @ 4 Mbps;
- 中断亲和:把 UART IRQ 绑定到单独核,避免任务迁移;
- FIFO 阈值调优:RX 触发 1/2 FIFO,平衡延迟与吞吐;
- 内核抢占/实时:PREEMPT_RT 下,中断线程化,抖动 < 100 µs;
- RS-485 切换延迟:DE→TX 使能后 ≥ 1 字符时间再发数据,避免首字节截断;
- 用户态环形缓冲 +
mmap:部分驱动支持mmap()直接访问 flip buffer,零拷贝。
8. 小结
UART 看似简单,却贯穿“物理层→帧格式→控制器→驱动→TTY 子系统→用户 API”完整链条。理解硬件过采样、FIFO/中断/DMA、线路规程与流控,是排查乱码/丢包/延迟的关键。Linux 提供标准化 TTY 接口,让同一套 termios/poll 代码可复用到 ttyS/ttyAMA/ttyUSB/ttyXR 等不同芯片。掌握上述原理与调试方法,可在嵌入式、工控、物联网、蓝牙/RS-485 多节点网络中快速定位问题并榨干最后一滴带宽。
附录:参考文档
- Linux Kernel:
Documentation/serial/,drivers/tty/serial/8250.c,serial_core.c - RS-232/485 标准:TIA/EIA-232-F, TIA-485-A
- 芯片手册:TI MAX3232, MAX3485, CP2102, FT232RL, PL2303
- LDD3 Chapter 18: “TTY Drivers”
- 书籍:《Linux 设备驱动开发详解》《嵌入式 Linux 开发教程》
本文档示例基于 Linux 4.4.94,不同版本内核接口基本一致,DMA/RS-485 配置细节请以实际 SoC 手册与驱动为准。
