Python串口通信必须用第三方库吗?标准库支持与替代方案
深入解答Python串口通信是否必须使用第三方库,全面解析标准库在Linux/POSIX与Windows下的实现方式、底层驱动差异,以及免库替代方案和技术选型建议。
os、io 和 termios 即可完整实现波特率设置、流控与双向读写;在 Windows 下也可通过标准库 ctypes 调用 Win32 原生 API。然而,由于跨平台硬件抽象差异与开发维护成本,第三方库 pyserial 仍是目前绝大多数生产场景中的事实标准。
一、为什么 Python 标准库没有内置专门的 serial 模块?
初学者在查阅 Python 官方文档时,常会疑惑:Python 自带“电池”(Batteries Included),连 FTP、HTTP、SQLite 甚至音效处理都有内置模块,为何偏偏没有一个开箱即用的 import serial?
这主要是由串口通信在不同操作系统底层的架构异构性与硬件强依赖性决定的:
-
POSIX 体系的 TTY 模型:在 Linux 和 macOS 中,串口被归为 TTY 终端设备,以字符设备节点存在(例如
/dev/ttyUSB0、/dev/ttyS0)。其参数控制遵循 POSIX 标准中的termios接口,由内核行规程(Line Discipline)解析。 -
Windows 体系的句柄与 DCB 模型:Windows 不存在
termios体系。串口被抽象为命名通信资源(如COM1或大端口号\\.\COM10),读写依赖CreateFile、ReadFile,而波特率、校验位与流控则通过设备控制块(DCB 结构体)及COMMTIMEOUTS来配置。 -
维护与分发成本:若在 Python 标准库内置统一的
serial顶层抽象,需要跟随 CPython 解释器核心同步维护两套截然不同的操作系统通信驱动,且无法针对各厂商 USB 转串口芯片(如 FTDI、CH340、CP2102)的偶发驱动边缘行为快速热修复。相比之下,由社区成熟维护的pyserial库更新灵活、生态完备,自然成为了行业通用的解决方案。
二、Linux / macOS 下的纯标准库实现方案
在无外网环境、精简版 Linux 固件(如 OpenWrt 路由器、嵌入式工控板)或禁止引入外部依赖的宿主机上,我们可以直接利用标准库的 os 与 termios 模块操控串口。
1. 核心实现原理
在 POSIX 系统中,“一切皆文件”。串口通信的本质是对字符设备文件的低级文件描述符(File Descriptor)进行读写,其核心流程可分为六个阶段:
- 设备定位与只读/读写打开:使用
os.open(),传入os.O_RDWR | os.O_NOCTTY标志,防止该设备成为当前进程的控制终端。 - 备份属性:通过
termios.tcgetattr(fd)获取包含 7 项属性的终端配置列表。 - 配置原始模式(Raw Mode):禁用终端的回显(ECHO)、规范输入处理(ICANON)及信号字符生成(ISIG),防止特殊字节(如回车换行或 0x03 等二进制控制码)被内核终端层拦截修改。
- 波特率与帧格式设定:使用
termios.B9600等常量设定输入输出速度,并设置 8 位数据位(CS8)、无奇偶校验、1 位停止位。 - 配置超时特性:利用
attrs[6][termios.VMIN]与attrs[6][termios.VTIME]组合定义非规范模式下的读取阻塞与超时行为。 - 数据交互与安全释放:通过
os.write()发送字节串,使用os.read()读取,并在finally块中还原属性并关闭描述符。
2. 完整标准库示例代码(Linux / macOS)
以下是一段不依赖任何外部 pip 包的纯 Python 3 串口通信脚本。示例打开 /dev/ttyUSB0,配置波特率为 9600,发送一段探测指令并等待接收回显数据:
import os
import termios
def open_raw_serial(device_path: str, baudrate=termios.B9600, timeout_decisec=10):
# 1. 打开串口字符设备,O_NOCTTY 保证设备不会抢占为控制终端
fd = os.open(device_path, os.O_RDWR | os.O_NOCTTY)
# 2. 读取当前串口终端属性列表 [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
old_attrs = termios.tcgetattr(fd)
new_attrs = termios.tcgetattr(fd)
# 3. 设置输入模式:关闭软件流控(IXON/IXOFF)、关闭回车换行映射
new_attrs[0] &= ~(termios.IXON | termios.IXOFF | termios.IXANY | termios.ICRNL | termios.INLCR)
# 4. 设置输出模式:关闭一切后续处理,保持原始输出
new_attrs[1] &= ~termios.OPOST
# 5. 设置控制模式:8数据位、使能接收(CREAD)、忽略控制线(CLOCAL)、无校验、1停止位
new_attrs[2] &= ~(termios.PARENB | termios.CSTOPB | termios.CSIZE)
new_attrs[2] |= (termios.CS8 | termios.CREAD | termios.CLOCAL)
# 6. 设置本地模式:关闭回显(ECHO)、关闭规范模式(ICANON)、关闭信号拦截(ISIG)
new_attrs[3] &= ~(termios.ICANON | termios.ECHO | termios.ECHOE | termios.ISIG)
# 7. 设置输入波特率与输出波特率
new_attrs[4] = baudrate
new_attrs[5] = baudrate
# 8. 设置超时参数:VMIN=0, VTIME=timeout (单位: 0.1秒)
# 此时 read() 在收到数据或超时达到后返回,若无数据返回空 bytes
new_attrs[6][termios.VMIN] = 0
new_attrs[6][termios.VTIME] = timeout_decisec
# 9. 立即应用配置
termios.tcsetattr(fd, termios.TCSANOW, new_attrs)
return fd, old_attrs
def main():
dev = "/dev/ttyUSB0"
try:
fd, old_settings = open_raw_serial(dev, termios.B9600, timeout_decisec=10)
print(f"成功打开串口: {dev}")
# 发送测试数据 (注意必须是 bytes 格式)
send_bytes = b"AT
"
os.write(fd, send_bytes)
print(f"已发送: {send_bytes}")
# 读取响应数据 (设置的 1 秒超时生效)
recv_data = os.read(fd, 1024)
if recv_data:
print(f"接收到数据: {recv_data}")
else:
print("读取超时,未收到数据。")
except FileNotFoundError:
print(f"错误:未找到串口设备 {dev},请检查接线或驱动。")
except PermissionError:
print(f"错误:权限不足,请确认当前用户已加入 dialout 组。")
finally:
if 'fd' in locals():
# 恢复原有属性并关闭描述符
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
os.close(fd)
print("串口已安全释放并关闭。")
if __name__ == "__main__":
main()
PermissionError: [Errno 13] Permission denied,是由于当前普通用户没有对应串口设备文件的读写权限。请在终端执行 sudo usermod -aG dialout $USER(在 Arch/Manjaro 下为 uucp 组),随后重新登录系统即可生效。
三、Windows 下的纯标准库实现方案
Windows 系统不包含 termios 模块。在 Windows 上如果强制不能使用第三方库,可以通过 Python 标准库内置的 ctypes 模块直接与操作系统的 kernel32.dll 进行动态链接。
1. 核心机制与 Win32 通信 API
Windows 内核处理串口本质上与处理磁盘文件使用相同的文件对象概念,主要调用以下 API:
CreateFileW:打开串口。注意当端口号大于或等于 10 时(如COM10),设备名称必须加上命名空间前缀\\.\COM10,否则会报错。GetCommState与SetCommState:读取并写入DCB(Device Control Block)结构体,设置 BaudRate、ByteSize、Parity、StopBits。SetCommTimeouts:填充COMMTIMEOUTS结构体设置读取/写入的毫秒级超时。ReadFile与WriteFile:执行缓冲区数据的收发。CloseHandle:关闭设备句柄。
2. ctypes 核心代码结构概览
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
# 定义 Windows DCB (Device Control Block) 结构体
class DCB(ctypes.Structure):
_fields_ = [
("DCBlength", wintypes.DWORD),
("BaudRate", wintypes.DWORD),
("flags", wintypes.DWORD),
("wReserved", wintypes.WORD),
("XonLim", wintypes.WORD),
("XoffLim", wintypes.WORD),
("ByteSize", wintypes.BYTE),
("Parity", wintypes.BYTE),
("StopBits", wintypes.BYTE),
# 其余字段按 Win32 API 规范补齐...
]
# 打开 COM3 设备
handle = kernel32.CreateFileW(
"\\.\COM3",
0xC0000000, # GENERIC_READ | GENERIC_WRITE
0, None,
3, # OPEN_EXISTING
0, None
)
# 随后调用 SetCommState 及 ReadFile / WriteFile...
方案评估:尽管 ctypes 能够实现 100% 免外部库安装,但由于 DCB 包含位域(Bitfields)与对齐规则,手动声明结构体极易出错,代码冗长且不易维护。因此在 Windows 平台下,手写底层调用的成本远高于直接使用成熟方案。
四、其他无需额外安装 Python 库的替代方案
除了直接用 termios 或 ctypes 裸写底层接口外,在实际工程中还有另外两种轻量、免装 Python 库的替代思路:
1. 网络串口转发(标准库 socket + ser2net / socat)
这是一种在工控、物联网和自动化测试中极其推崇的高级解耦方案。在连接了串口硬件的系统上,借助系统级开源工具(如 Linux 自带的 ser2net 或 socat),一行命令即可将物理串口映射为本机的 TCP 端口:
# 系统终端执行:将 /dev/ttyUSB0 映射到本机的 8899 端口 (波特率 9600, 8N1)
socat TCP-LISTEN:8899,reuseaddr,fork FILE:/dev/ttyUSB0,b9600,raw,echo=0
此时,Python 脚本端只需使用最基础的内置 socket 库即可完成串口读写,完全脱离了平台差异和底层 TTY 参数配置:
import socket
# 纯标准库 socket 连接本地或远程串口代理
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2.0)
s.connect(("127.0.0.1", 8899))
s.sendall(b"HELLO_MCU
")
response = s.recv(1024)
print(f"收到响应: {response}")
s.close()
方案优势:代码完全跨平台;不仅免除了在 Python 中处理波特率的繁琐代码,还自然获得了“远程串口调试”与多客户端共享的能力。
2. 命令行工具预置配置(subprocess + stty / mode)
在简单的自动化测试场景中,可以在 Python 启动前或者通过 subprocess 先调用系统自带的命令行配置工具(Linux 下为 stty -F /dev/ttyUSB0 9600 raw -echo,Windows 下为 mode com3: baud=9600 parity=n data=8 stop=1),配置完成后,直接使用 Python 内置的 open('/dev/ttyUSB0', 'rb+') 按常规文件方式读写。
五、综合选型建议与决策决策树
为了帮助你在实际开发中做出最合理的架构抉择,以下对各方案的技术指标进行了综合对比:
| 方案类型 | 依赖要求 | 跨平台支持 | 代码复杂度 | 典型适用场景 |
|---|---|---|---|---|
| pyserial (第三方库) | 需 pip install |
极佳 (全平台一致) | 极低 (1~2 行代码) | 常规软件开发、工业自动化测试、生产系统 |
| os + termios (标准库) | 0 依赖 (Python 内置) | 仅 Linux / macOS | 中等 (需约 40 行配置) | 嵌入式 Linux 固件、离线受限主机、定制单板 |
| ctypes + Win32 (标准库) | 0 依赖 (Python 内置) | 仅 Windows | 高 (需声明 C 结构体) | 极度苛刻、严禁安装任何 wheel 包的 Windows 宿主 |
| socket + 串口中继 | 仅需系统装 socat | 极佳 (TCP 跨平台) | 极低 (标准 socket) | 边缘设备远程采集、分布式测试系统、网络化串口 |
六、总结
回到最初的问题:Python 串口通信必须用第三方库吗?
答案很明确:在技术实现上不是必须的。现代操作系统为底层通信提供了完整的系统调用,在 POSIX 环境下通过标准库的 os 与 termios 可以非常干净地实现串口控制;同时配合 socket 代理模式也能优雅解决无库环境下的通信需求。
但在工程实践中,如非环境严格受限,强烈建议优先使用 pyserial。它替开发者屏蔽了复杂的底层位掩码操作、跨操作系统的驱动细节以及流控时序陷阱,能让你将宝贵的时间聚焦在核心业务逻辑与通信协议解析上。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













