第 55 天:嵌入式 Linux 核心进阶 ——UART 串口驱动与 TTY 子系统

📅 2026/6/29 19:53:40
第 55 天:嵌入式 Linux 核心进阶 ——UART 串口驱动与 TTY 子系统
核心目标从UART 异步串行通信本质出发拆解 Linux TTY 终端子系统与串口驱动的分层架构掌握UART 设备树配置、用户态串口编程、内核态串口外设驱动三大核心技能通过完整的收发实战解决 “串口无法打开、数据乱码、丢包、流控异常” 等嵌入式高频问题完成从同步串行总线SPI/I2C到异步串行总线的能力拓展吃透嵌入式最通用的调试接口与板间通信方案。一、本质追问UART 串口的核心价值与 Linux 实现在嵌入式系统的所有串行总线中UART通用异步收发传输器是最基础、应用最广的通信接口 —— 它既是系统调试的核心入口调试串口也是板间设备、外接模块的标准通信方式GPS、蓝牙、4G、工控板卡均支持 UART是嵌入式开发者必须精通的 “基础中的基础”。1.1 UART 与 SPI/I2C 的核心区别SPI/I2C 属于板内芯片间的同步串行总线依赖时钟信号同步收发主要用于 PCB 板上的芯片互联而 UART 属于异步串行总线无需时钟线通过约定的波特率实现收发同步主要用于板与板之间、设备与设备之间的中低速通信二者的核心差异如下表格对比维度UARTSPII2C通信方式异步全双工无时钟线靠波特率同步同步全双工靠 SCLK 时钟同步同步半双工靠 SCL 时钟同步信号线最少 2 根TX/RXGND可选流控CTS/RTS4 根SCLK/MOSI/MISO/CS多设备加 CS 线2 根SDA/SCL传输速率中低速常见 9600~115200bps最高几 Mbps高速几十上百 MHz低速100k~400kbps寻址方式无地址点对点通信一条总线对应一个设备独立片选引脚多设备靠 CS 区分7/10 位从机地址总线多设备典型场景调试串口、GPS / 蓝牙 / 4G 模块、板间通信Flash、显示屏、高速 ADC传感器、RTC、EEPROM核心本质UART 是点对点的异步串行协议无需主机时钟收发双方必须提前约定完全一致的通信参数波特率、数据位、停止位、校验位否则会出现数据乱码、通信失败。1.2 串口通信核心参数必须完全匹配UART 异步通信的前提是收发双方参数完全一致这 5 个参数是串口开发的核心也是 90% 串口乱码问题的根源波特率Baud Rate每秒传输的比特数单位 bps嵌入式常用值9600、19200、38400、115200、921600数据位Data Bits每个字节的数据位数通常为 8 位也有 5/6/7 位停止位Stop Bits字节结束的标志位通常 1 位也有 1.5/2 位校验位Parity数据校验方式常用无校验None也有奇校验Odd、偶校验Even流控Flow Control防止数据溢出的机制分为无流控、硬件流控RTS/CTS、软件流控XON/XOFF嵌入式常用无流控。常用组合嵌入式最通用的配置是115200 8N1波特率 1152008 位数据位无校验1 位停止位无流控调试串口、绝大多数模块默认都是该配置。1.3 裸机 UART 开发的致命痛点裸机开发中直接操作 UART 寄存器虽然简单但在复杂嵌入式系统中存在诸多问题硬件强耦合不同芯片的 UART 控制器寄存器完全不同代码可移植性差更换芯片需重写全部串口逻辑无缓冲区管理裸机 UART 通常只有 1~2 字节的硬件 FIFO大数据量收发时极易丢包需手动实现环形缓冲区无标准化接口每个项目的串口操作函数都不一样上层应用无法复用无流控与错误处理需手动处理溢出、帧错误、奇偶校验错误代码繁琐且易出错。1.4 Linux TTY 子系统的核心使命Linux 内核通过TTYTeletypewriter终端子系统统一管理所有串行终端设备包括硬件串口、虚拟串口、USB 转串口其核心使命是屏蔽硬件差异提供标准化的终端操作接口实现缓冲区管理、线路规程、流控等通用能力具体价值如下硬件抽象统一接口所有串口最终都暴露为/dev/ttySx硬件串口、/dev/ttyUSBxUSB 转串口等标准设备文件应用通过统一的termios接口配置参数、收发数据无需关心底层硬件分层架构职责分离分为 TTY 核心层、线路规程层、UART 驱动层开发者只需关注业务逻辑底层驱动、缓冲区、流控均由内核实现完善的缓冲区机制内核为每个串口分配独立的收发环形缓冲区几十 KB大幅降低丢包概率支持中断 DMA 收发CPU 占用极低标准化线路规程内置行规程行编辑、回显、特殊字符处理默认适配终端交互也可切换为原始模式Raw用于数据传输。1.5 嵌入式 Linux 串口的两种开发角色和 SPI/I2C 类似串口开发分为两类角色对应不同的开发内容嵌入式开发者 90% 的工作集中在第二类UART 控制器驱动操作芯片的 UART 寄存器实现底层数据收发由芯片厂商瑞芯微、NXP、ST提供已集成到内核开发者只需通过设备树启用即可串口外设应用 / 驱动基于内核提供的标准串口设备开发外接模块GPS、蓝牙、4G的通信逻辑分为用户态应用开发最常用通过文件操作串口和内核态驱动开发特殊场景封装为专用字符设备。二、内核基础Linux TTY 子系统架构与串口开发核心Linux TTY 子系统采用经典的三层分层架构从上到下分别是TTY 字符设备层、线路规程层、UART 硬件驱动层每层职责明确解耦性极强。2.1 TTY 子系统三层架构plaintext用户态应用程序 → 操作/dev/ttySx设备文件 ↓ TTY字符设备层统一的字符设备接口管理TTY设备号、设备节点 ↓ 线路规程层Line Discipline数据加工层默认N_TTY终端行规程可切换为原始模式 ↓ UART核心层serial core串口核心框架管理UART控制器与端口 ↓ UART驱动层具体芯片的UART控制器驱动厂商提供操作硬件寄存器 ↓ 硬件层UART控制器 外接串口设备TTY 字符设备层核心作用向用户态暴露标准字符设备接口所有 TTY 设备共享主设备号 4次设备号区分不同终端如 ttyS0 次设备号 0ttyS1 次设备号 1关键能力实现open/read/write/ioctl等标准文件接口应用无需关心底层是硬件串口还是虚拟串口。线路规程层Line Discipline核心作用TTY 子系统的 “数据加工层”对收发的数据进行预处理是 TTY 区别于普通字符设备的核心默认规程N_TTY实现终端行编辑退格、删除、回显、特殊字符处理CtrlC 中断适合调试串口、shell 终端原始模式关闭行编辑和回显数据原样收发适合模块通信、数据传输是嵌入式串口开发的常用模式可动态切换通过ioctl命令切换线路规程无需修改驱动。UART 核心层与驱动层UART 核心层提供标准化的 UART 驱动框架定义通用的数据结构和操作接口屏蔽不同控制器的差异UART 驱动层由芯片厂商实现操作具体的 UART 控制器寄存器实现数据收发、中断处理、DMA 配置核心抽象struct uart_driverUART 驱动、struct uart_port一个 UART 端口对应一个硬件串口、struct uart_ops端口操作函数集。2.2 UART 驱动核心数据结构2.2.1struct uart_driverUART 驱动整体描述描述一个 UART 控制器驱动的全局信息包含驱动名、设备号、端口数量等注册后内核会为其分配 TTY 设备资源。c运行struct uart_driver { struct module *owner; const char *driver_name; // 驱动名 const char *dev_name; // 设备名前缀如ttyS int major; // 主设备号0表示动态分配 int minor; // 起始次设备号 int nr; // 支持的端口数量 struct uart_state *state; // 端口状态数组 struct tty_driver *tty_driver; // 关联的TTY驱动 };2.2.2struct uart_port单个串口端口描述对应一个物理串口包含端口的硬件信息寄存器地址、中断号、时钟、波特率、运行状态、操作函数集是 UART 驱动的核心实体。c运行struct uart_port { void __iomem *membase; // 寄存器虚拟地址 unsigned int irq; // 中断号 unsigned int uartclk; // 串口时钟频率 unsigned int baudrate; // 当前波特率 unsigned char fifosize; // 硬件FIFO大小 const struct uart_ops *ops; // 端口操作函数集 struct device *dev; // 设备指针 // 状态、锁、缓冲区等其他字段 };2.2.3struct uart_ops端口操作函数集UART 驱动需要实现的硬件操作函数包括发送、接收、设置波特率、启动停止等是控制器驱动的核心。c运行struct uart_ops { unsigned int (*tx_empty)(struct uart_port *); void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); // 设置串口参数 void (*start_tx)(struct uart_port *); // 启动发送 void (*stop_tx)(struct uart_port *); // 停止发送 void (*start_rx)(struct uart_port *); // 启动接收 void (*stop_rx)(struct uart_port *); // 停止接收 int (*startup)(struct uart_port *); // 初始化端口 void (*shutdown)(struct uart_port *); // 关闭端口 // 其他操作函数 };嵌入式开发提示以上结构体均由芯片厂商在控制器驱动中实现开发者无需手动编写只需通过设备树配置端口参数、启用端口即可。2.3 UART 设备树配置规范UART 控制器驱动通常已集成在内核中开发者的核心工作是通过设备树启用指定 UART 端口、配置引脚复用、设置默认参数这是串口可用的前提。2.3.1 UART 控制器节点配置启用端口以 RK3328 的 UART2 为例芯片厂商已提供控制器节点开发者只需修改status为okay配置引脚复用即可dtsuart2 { status okay; // 启用UART2对应设备/dev/ttyS2 pinctrl-names default; pinctrl-0 uart2m0_xfer; // 引脚复用TX/RX引脚配置为UART功能 // 可选硬件流控引脚配置 // rts-gpios gpio1 22 GPIO_ACTIVE_LOW; // cts-gpios gpio1 23 GPIO_ACTIVE_LOW; };2.3.2 引脚复用配置关键常被忽略UART 的 TX/RX 引脚默认可能是 GPIO 功能必须在设备树中配置为 UART 功能否则串口无法通信这是新手最容易踩的坑dts// 引脚复用节点配置GPIO1_24为UART2_TXGPIO1_25为UART2_RX uart2m0_xfer: uart2m0-xfer { rockchip,pins 1 24 RK_FUNC_2 pcfg_pull_up, // TX引脚复用为UART2功能上拉 1 25 RK_FUNC_2 pcfg_pull_up; // RX引脚复用为UART2功能上拉 };2.3.3 串口外设节点serdev 总线如果要编写内核态的串口外设驱动如 GPS 模块现代内核推荐使用serdev 总线串行设备总线和 I2C/SPI 总线类似在设备树中添加外设节点驱动通过 compatible 匹配dtsuart2 { status okay; pinctrl-0 uart2m0_xfer; // 挂载GPS模块到UART2 gps0 { compatible neo-6m,gps; status okay; }; };2.4 用户态串口开发核心termios 接口嵌入式开发中90% 以上的串口应用都在用户态实现如 GPS 数据解析、蓝牙 AT 指令、4G 拨号通过标准文件接口操作/dev/ttySx核心是通过termios结构体配置串口参数。2.4.1 核心头文件与结构体c运行#include termios.h #include fcntl.h #include unistd.h #include sys/ioctl.h // termios核心结构体精简 struct termios { tcflag_t c_iflag; // 输入模式标志 tcflag_t c_oflag; // 输出模式标志 tcflag_t c_cflag; // 控制模式标志波特率、数据位、停止位、校验位 tcflag_t c_lflag; // 本地模式标志行编辑、回显 cc_t c_cc[NCCS]; // 特殊字符数组 };2.4.2 核心配置函数c运行// 获取当前串口参数 int tcgetattr(int fd, struct termios *termios_p); // 设置串口参数TCSANOW立即生效 int tcsetattr(int fd, int optional_actions, const struct termios *termios_p); // 设置输入波特率 int cfsetispeed(struct termios *termios_p, speed_t speed); // 设置输出波特率 int cfsetospeed(struct termios *termios_p, speed_t speed); // 刷新缓冲区TCIFLUSH清输入TCOFLUSH清输出TCIOFLUSH都清 int tcflush(int fd, int queue_selector);2.4.3 原始模式配置数据传输必备用于模块通信、数据传输的串口必须配置为原始模式Raw关闭行编辑、回显、特殊字符处理确保数据原样收发c运行// 配置为原始模式 cfmakeraw(termios);该宏会自动关闭所有终端处理逻辑适合纯数据传输场景。2.5 内核态串口开发serdev 总线接口如果需要在内核态操作串口如实现专用字符设备、内核态数据解析推荐使用serdev 总线这是 Linux 4.11 之后引入的标准串口外设总线用法和 I2C/SPI 驱动高度一致。核心 APIc运行// 注册/注销serdev驱动 int serdev_device_driver_register(struct serdev_device_driver *drv); void serdev_device_driver_unregister(struct serdev_device_driver *drv); // 串口收发 int serdev_device_write(struct serdev_device *serdev, const u8 *buf, size_t count); int serdev_device_read(struct serdev_device *serdev, u8 *buf, size_t count); // 设置波特率 int serdev_device_set_baudrate(struct serdev_device *serdev, unsigned int speed); // 设置接收回调 void serdev_device_set_client_ops(struct serdev_device *serdev, const struct serdev_device_ops *ops);三、实战一UART 设备树配置与基础调试理论落地第一步通过设备树启用串口使用调试工具验证串口硬件和驱动正常这是所有串口开发的基础。3.1 硬件连接准备串口接线规则交叉连接板卡的 TX 接另一设备的 RX板卡的 RX 接另一设备的 TX必须共地两个设备的 GND 必须连接在一起否则电平基准不一致通信失败电平匹配注意串口电平是 3.3V TTL 还是 RS232嵌入式板卡通常是 3.3V TTL接 RS232 需要电平转换芯片。调试工具USB 转 TTL 模块CH340/PL2303一端接电脑 USB一端接板卡 UART 的 TX/RX/GND。3.2 设备树配置以 IMX6ULL UART3 为例找到内核设备树中的 UART3 控制器节点启用并配置引脚dtsuart3 { pinctrl-names default; pinctrl-0 pinctrl_uart3; status okay; // 启用UART3对应/dev/ttymxc2IMX平台命名规则 }; // 引脚复用配置 pinctrl_uart3: uart3grp { fsl,pins MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1 // TX引脚 MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1 // RX引脚 ; };重新编译设备树烧录到板卡并重启。3.3 板卡端验证串口设备重启后执行以下命令确认串口设备节点已生成bash运行# 查看所有串口设备 ls /dev/tty* | grep tty # 查看特定串口根据平台命名不同RK是ttySxIMX是ttymxcx ls -l /dev/ttymxc2成功标志存在对应设备节点如/dev/ttymxc2主设备号 207次设备号 16。3.4 串口回环测试验证硬件与驱动最可靠的硬件验证方式是回环测试将板卡串口的 TX 和 RX 短接自发自收验证收发通路。硬件操作用杜邦线将 UART3 的 TX 和 RX 引脚短接板卡端执行回环测试bash运行# 安装串口测试工具 apt-get install setserial # 配置串口为115200 8N1原始模式 stty -F /dev/ttymxc2 115200 cs8 -cstopb -parenb raw -echo # 后台读取串口数据写入到文件 cat /dev/ttymxc2 /tmp/uart_recv.txt # 向串口写入测试数据 echo UART Loopback Test! /dev/ttymxc2 # 等待1秒查看接收文件 sleep 1 cat /tmp/uart_recv.txt成功标志接收文件中显示写入的测试字符串说明串口的发送、接收通路都正常硬件和驱动无问题。3.5 常用串口调试工具使用3.5.1 stty命令行配置串口参数bash运行# 查看串口当前参数 stty -F /dev/ttymxc2 -a # 配置为115200 8N1原始模式关闭回显 stty -F /dev/ttymxc2 115200 cs8 -cstopb -parenb raw -echo3.5.2 minicom交互式串口终端安装apt-get install minicom配置minicom -s进入 Serial port setup设置串口设备、波特率、关闭硬件流控运行minicom -D /dev/ttymxc2即可和串口设备交互退出按CtrlA再按X。3.5.3 screen轻量级串口终端bash运行# 打开串口115200波特率 screen /dev/ttymxc2 115200 # 退出按CtrlA再按\确认退出四、实战二用户态串口应用开发完整可运行代码用户态串口编程是嵌入式开发者的核心技能本节实现一个通用串口工具支持配置参数、发送数据、循环接收数据可直接用于模块调试和二次开发。4.1 开发需求实现一个通用串口应用满足以下需求支持指定串口设备、波特率、数据位、停止位、校验位支持原始模式关闭回显和行编辑适合模块通信支持发送指定字符串循环接收并打印串口数据代码可移植可在所有 Linux 平台运行。4.2 完整代码uart_tool.cc运行/************************************************************************* * 通用串口工具嵌入式Linux用户态串口开发实战 * 用法./uart_tool /dev/ttymxc2 115200 * 功能配置串口为8N1原始模式循环接收并打印数据输入内容可发送 *************************************************************************/ #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include unistd.h #include termios.h #include errno.h #include sys/select.h #define BUF_SIZE 1024 /** * brief 设置串口参数 * param fd: 串口文件描述符 * param baudrate: 波特率 * param databits: 数据位5/6/7/8 * param stopbits: 停止位1/2 * param parity: 校验位n无o奇e偶 * return 成功返回0失败返回-1 */ int uart_set_param(int fd, int baudrate, int databits, int stopbits, char parity) { struct termios options; speed_t speed; // 获取当前串口配置 if (tcgetattr(fd, options) ! 0) { perror(tcgetattr failed); return -1; } // 1. 设置波特率 switch (baudrate) { case 9600: speed B9600; break; case 19200: speed B19200; break; case 38400: speed B38400; break; case 57600: speed B57600; break; case 115200: speed B115200; break; case 230400: speed B230400; break; case 460800: speed B460800; break; case 921600: speed B921600; break; default: printf(Unsupported baudrate: %d\n, baudrate); return -1; } cfsetispeed(options, speed); cfsetospeed(options, speed); // 2. 设置数据位 options.c_cflag ~CSIZE; // 清除数据位掩码 switch (databits) { case 5: options.c_cflag | CS5; break; case 6: options.c_cflag | CS6; break; case 7: options.c_cflag | CS7; break; case 8: options.c_cflag | CS8; break; default: printf(Unsupported databits: %d\n, databits); return -1; } // 3. 设置停止位 switch (stopbits) { case 1: options.c_cflag ~CSTOPB; break; // 1位停止位 case 2: options.c_cflag | CSTOPB; break; // 2位停止位 default: printf(Unsupported stopbits: %d\n, stopbits); return -1; } // 4. 设置校验位 switch (parity) { case n: case N: // 无校验 options.c_cflag ~PARENB; options.c_iflag ~INPCK; break; case o: case O: // 奇校验 options.c_cflag | PARENB; options.c_cflag | PARODD; options.c_iflag | INPCK; break; case e: case E: // 偶校验 options.c_cflag | PARENB; options.c_cflag ~PARODD; options.c_iflag | INPCK; break; default: printf(Unsupported parity: %c\n, parity); return -1; } // 5. 配置为原始模式核心关闭终端处理数据原样收发 cfmakeraw(options); // 6. 其他配置启用接收忽略调制解调器状态 options.c_cflag | CREAD | CLOCAL; // 关闭软件流控 options.c_iflag ~(IXON | IXOFF | IXANY); // 关闭输出处理 options.c_oflag ~OPOST; // 7. 设置超时非阻塞模式read立即返回 options.c_cc[VMIN] 0; // 最少读取0字节 options.c_cc[VTIME] 1; // 超时100ms // 8. 刷新缓冲区立即生效 tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, options) ! 0) { perror(tcsetattr failed); return -1; } printf(UART config: baud%d, databits%d, stopbits%d, parity%c\n, baudrate, databits, stopbits, parity); return 0; } int main(int argc, char *argv[]) { int fd; char *dev_path; int baudrate; char buf[BUF_SIZE]; fd_set readfds; int ret; // 参数检查 if (argc 3) { printf(Usage: %s uart_dev baudrate\n, argv[0]); printf(Example: %s /dev/ttymxc2 115200\n, argv[0]); return -1; } dev_path argv[1]; baudrate atoi(argv[2]); // 1. 打开串口设备读写模式不成为控制终端 fd open(dev_path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd 0) { perror(open uart failed); return -1; } printf(Open %s success, fd%d\n, dev_path, fd); // 2. 配置串口参数115200 8N1 原始模式 if (uart_set_param(fd, baudrate, 8, 1, n) ! 0) { close(fd); return -1; } printf(UART tool running, press CtrlC to exit\n); printf(----------------------------------------\n); // 3. 主循环监听串口和标准输入实现收发交互 while (1) { FD_ZERO(readfds); FD_SET(fd, readfds); // 监听串口可读 FD_SET(STDIN_FILENO, readfds); // 监听键盘输入 // select多路复用等待可读事件 ret select(fd 1, readfds, NULL, NULL, NULL); if (ret 0) { perror(select failed); break; } // 串口有数据读取并打印 if (FD_ISSET(fd, readfds)) { memset(buf, 0, BUF_SIZE); ret read(fd, buf, BUF_SIZE - 1); if (ret 0) { printf([Recv %d bytes]: %s, ret, buf); fflush(stdout); } else if (ret 0) { perror(read uart failed); break; } } // 键盘有输入读取并发送到串口 if (FD_ISSET(STDIN_FILENO, readfds)) { memset(buf, 0, BUF_SIZE); fgets(buf, BUF_SIZE, stdin); ret write(fd, buf, strlen(buf)); if (ret 0) { perror(write uart failed); break; } printf([Send %d bytes]: %s, ret, buf); } } // 关闭串口 close(fd); printf(\nUART tool exit\n); return 0; }4.3 代码核心亮点通用可移植标准 POSIX 接口所有 Linux 平台通用可直接用于 ARM、x86 等架构参数完整支持常用波特率、数据位、停止位、校验位配置满足绝大多数场景原始模式通过cfmakeraw配置为原始模式数据原样收发适合模块通信多路复用使用select同时监听串口和键盘输入实现交互式收发无需多线程非阻塞超时配置读取超时避免 read 阻塞卡死稳定性强。4.4 交叉编译与运行测试交叉编译ARM 架构bash运行arm-linux-gnueabihf-gcc uart_tool.c -o uart_tool拷贝到板卡bash运行scp uart_tool root192.168.1.100:/opt/app/运行测试bash运行# 赋予执行权限 chmod x uart_tool # 运行指定串口和波特率 ./uart_tool /dev/ttymxc2 115200测试方法接 USB 转 TTL 到电脑电脑端打开串口助手波特率 115200电脑端发送数据板卡终端会打印接收内容板卡终端输入内容回车电脑端串口助手会收到数据。五、串口开发常见问题排查串口开发的问题 90% 集中在硬件、配置、参数匹配三个方面以下是嵌入式开发中最高频的问题及排查方案。5.1 问题 1串口设备节点不存在现象ls /dev/ttySx无对应设备打开串口提示No such file or directory。排查方案检查设备树中 UART 节点的status是否为okay设备树是否重新烧录生效检查 UART 控制器驱动是否编译进内核或加载为模块lsmod | grep uart确认串口命名规则RK 平台为ttySxIMX 平台为ttymxcx全志为ttySx查看内核启动日志dmesg | grep uart看是否有串口初始化失败的报错。5.2 问题 2接收数据乱码发送数据对方识别不了现象能收到数据但都是乱码、方块、无意义字符。核心原因收发双方波特率 / 参数不匹配这是最常见原因。排查方案严格核对收发双方的 5 个参数波特率、数据位、停止位、校验位、流控必须完全一致检查串口时钟配置设备树中 UART 的时钟源是否正确时钟频率偏差会导致波特率不准检查电平匹配确认是 TTL 电平还是 RS232 电平错接会导致乱码甚至烧毁芯片降低波特率测试先降到 9600 测试排除高速下的硬件干扰问题。5.3 问题 3只能发不能收或只能收不能发现象一方发送正常另一方收不到单向通信异常。排查方案检查 TX/RX 是否接反板卡 TX 接对方 RX板卡 RX 接对方 TX交叉连接检查引脚复用配置确认 TX/RX 引脚都配置为 UART 功能不是 GPIO 模式回环测试验证短接自身 TX/RX自发自收判断是发送通路还是接收通路问题检查硬件引脚用万用表测量 TX 引脚电平空闲时应为高电平发送时有电平变化。5.4 问题 4大数据量传输丢包现象少量数据正常连续发送大数据时出现丢字节。排查方案检查应用读取是否及时用户态处理太慢导致内核缓冲区溢出可增大读取频率或使用多线程开启硬件流控如果支持 RTS/CTS开启硬件流控防止溢出降低波特率波特率过高时处理不及时容易丢包查看内核缓冲区通过stty -a查看缓冲区大小必要时调整内核参数增大缓冲区。5.5 问题 5打开串口权限不足现象打开串口提示Permission denied。解决方案临时以 root 用户运行或修改设备权限chmod 666 /dev/ttymxc2永久添加 udev 规则在/etc/udev/rules.d/下创建99-uart.rulesplaintextKERNELttymxc[0-9]*, MODE0666重启 udevudevadm control --reload-rules udevadm trigger。