【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 注册流程(简化)

  1. 驱动 module_init()uart_register_driver()
  2. 设备树/ACPI 或平台代码探测到端口 → uart_add_one_port()
  3. TTY 层自动创建设备节点,用户空间 open(/dev/ttyS0) 即关联到该端口;
  4. 线路规程(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

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 基本操作

bash 复制代码
# 查看串口设备
$ 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 编程

c 复制代码
#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 手册与驱动为准。

相关推荐
赖small强2 小时前
【Linux驱动开发】Linux 设备驱动中的阻塞与非阻塞 I/O:机制、源码与示例
linux·驱动开发·阻塞与非阻塞
yolo_guo2 小时前
opencv 学习: QA_02 什么是图像中的高频成分和低频成分
linux·c++·opencv·计算机视觉
q***56383 小时前
在 Ubuntu 22.04 上安装和配置 Nginx 的完整指南
linux·nginx·ubuntu
大聪明-PLUS3 小时前
Linux 中的 CPU。文章 1. 利用率
linux·嵌入式·arm·smarc
热爱编程的OP4 小时前
Linux进程池与管道通信详解:从原理到实现
linux·开发语言·c++
想学好C++的oMen5 小时前
文件基础IO
linux·服务器
福旺旺9 小时前
Linux——解压缩各类文件
linux
MasterLi802311 小时前
我的读书清单
android·linux·学习
ha204289419411 小时前
Linux操作系统学习之---初识网络
linux·网络·学习