1. 项目背景与问题描述
在Linux系统开发中,串口通信是最基础也最常用的外设接口之一。最近我在调试一个嵌入式Linux项目时,遇到了一个奇怪的现象:通过虚拟串口传输特定字节时,接收端会出现数据丢失或异常。具体表现为当发送0x1A(SUB字符)时,接收端要么完全收不到这个字节,要么会触发一些意外的终端控制行为。
这个问题看似简单,实则涉及Linux串口驱动、终端设备控制、特殊字符处理等多个技术层面。经过一周的排查和实验,我终于找到了问题的根源和解决方案,在此将完整的技术分析和处理过程分享给大家。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Linux串口通信基础
2.1 物理串口与虚拟串口
在Linux系统中,串口设备通常以/dev/ttyS*(物理串口)或/dev/ttyUSB*(USB转串口)的形式存在。而虚拟串口则通过伪终端(pty)实现,设备节点通常为/dev/pts/*。两者在应用层的操作接口基本一致,但底层实现机制有所不同。
虚拟串口对的创建方法:
bash复制# 创建虚拟串口对
socat -d -d pty,raw,echo=0 pty,raw,echo=0
执行后会输出两个伪终端设备路径,如/dev/pts/2和/dev/pts/3,这两个端口会自动连接。
2.2 串口的特殊控制字符
Linux终端设备定义了若干特殊控制字符,当这些字符出现在数据流中时,会触发特定的终端行为。常见的控制字符包括:
| 十六进制 | 字符名 | 作用 |
|---|---|---|
| 0x03 | ETX | 中断进程 |
| 0x04 | EOT | 文件结束 |
| 0x1A | SUB | 替换字符/挂起 |
| 0x1B | ESC | 转义序列开始 |
这些字符在终端交互时很有用,但在纯数据传输场景下可能造成干扰。
3. 问题分析与定位
3.1 现象重现
使用以下Python脚本模拟问题场景:
发送端代码:
python复制import serial
ser = serial.Serial('/dev/pts/2', 115200, timeout=1)
ser.write(b'\x1Ahello\x1A') # 发送包含0x1A的数据
ser.close()
接收端代码:
python复制import serial
ser = serial.Serial('/dev/pts/3', 115200, timeout=1)
while True:
data = ser.read(10)
if data:
print(f"Received: {data.hex()}")
预期应该收到完整的"1a68656c6c6f1a",但实际输出可能缺失0x1A或显示异常。
3.2 原因分析
通过strace跟踪接收进程的系统调用,发现0x1A被终端驱动特殊处理了。根本原因是:
- Linux默认将串口作为终端设备处理
- 终端驱动会对特定控制字符进行解释
- 0x1A(SUB)在某些配置下会被当作流控字符丢弃
使用以下命令可以查看当前终端的特殊字符设置:
bash复制stty -a < /dev/pts/3
输出中会包含类似"eof = ^D; susp = ^Z; ..."的行,其中的"susp"就是挂起字符设置。
4. 解决方案与实现
4.1 方案一:禁用终端特性
最彻底的解决方案是配置串口为原始模式,禁用所有终端处理:
python复制import serial
ser = serial.Serial('/dev/pts/3', 115200, timeout=1)
# 获取当前配置
attrs = ser.get_settings()
# 修改关键参数
attrs['ignbrk'] = True
attrs['brkint'] = False
attrs['parmrk'] = False
attrs['istrip'] = False
attrs['inlcr'] = False
attrs['igncr'] = False
attrs['icrnl'] = False
attrs['ixon'] = False
attrs[
