Python使用pyserial实现串口通信基础教程
一、串口通信基本链路与核心概念
串口(Serial Port)通常指串行通信接口。现代计算机大多不再配备原生的DB9物理串口,而是通过USB转串口芯片(如CH340、CP2102、FT232)与外部设备的UART引脚相连。
在软件层面,操作系统会把接入的USB串口设备识别为一个虚拟串口节点。Python应用程序通过pyserial与系统底层串口驱动对接,向其输入或读取连续的二进制字节流(bytes)。硬件连接中,通常要求发送端(TX)连接接收端(RX),接收端(RX)连接发送端(TX),并且两端必须共地(GND连接GND)以维持统一电平基准。
二、环境安装与可用端口探测
在编写串口读写逻辑前,首先需要安装第三方库并确定目标设备连接在哪一个端口。
1. 安装正确安装包
在终端使用pip进行安装。请注意,包名必须是pyserial,不能误装为serial。如果以前误装了serial,应先卸载它,否则会引起命名空间覆盖和方法缺失报错。
pip install pyserial
PyPI上存在名为serial的历史弃用包。代码中虽然书写import serial,但安装命令必须严格执行pip install pyserial。若出现AttributeError: module 'serial' has no attribute 'Serial',说明安装了错误的包。
2. 自动枚举系统可用端口
硬编码端口名(例如直接写死"COM3")在不同电脑或设备重新插拔后容易失效。借助serial.tools.list_ports模块,可以遍历当前系统在线的所有串口设备及其硬件ID信息。
import serial.tools.list_ports
def scan_serial_ports():
ports = serial.tools.list_ports.comports()
if not ports:
print("未检测到可用串口设备,请检查数据线连接与驱动状态。")
return []
available_ports = []
print(f"检测到 {len(ports)} 个串口设备:")
for port, desc, hwid in sorted(ports):
print(f" - 端口: {port}")
print(f" 描述: {desc}")
print(f" 硬件ID: {hwid}")
available_ports.append(port)
return available_ports
if __name__ == "__main__":
scan_serial_ports()
检测到 2 个串口设备:
- 端口: COM3
描述: USB-SERIAL CH340 (COM3)
硬件ID: USB VID:PID=1A86:7523 SER=5 LOCATION=1-1
- 端口: COM5
描述: Silicon Labs CP210x USB to UART Bridge (COM5)
硬件ID: USB VID:PID=10C4:EA60 SER=0001 LOCATION=1-2
三、串口通信标准流程
使用pyserial进行开发时,应遵循固定的操作生命周期:发现端口、参数配置、建立连接、交互收发、安全关闭。
四、核心参数配置与帧结构
串口通信属于异步串行通信,收发双方没有共享的时钟信号线。为了保证双方正确还原每一个位(Bit),收发双方必须预先约定完全相同的通信波特率与数据帧格式。
在上述参数中,timeout(读超时)是最容易被忽略却至关重要的参数:
timeout=None(默认值):完全阻塞模式。调用read()或readline()时,如果下位机没有发送足够的数据或换行符,程序将永久卡死在当前调用处。timeout=0:非阻塞模式。无论缓冲区是否有数据,调用读操作立即返回,缓冲区为空则返回b''。timeout=1.0(推荐值):设置浮点数秒数。程序最多等待设定的秒数,一旦收到足够数据或超时立即返回,兼顾等待响应与防止程序死锁。
五、数据收发实战与代码实现
串口在物理层传输的是连续的原始字节。在Python 3中,所有发送和接收的数据都必须显式使用bytes类型,而不能直接传递str对象。
1. 基本发送与接收模式
以下是一个使用with上下文管理器的完整示例。使用上下文管理器可以确保无论程序正常运行还是出现异常,串口对象都会被安全关闭。
import time
import serial
PORT = "COM3" # Windows上形如 "COM3",Linux上形如 "/dev/ttyUSB0"
BAUDRATE = 115200
def send_and_receive():
try:
# 打开串口,设置读写超时均为 1.0 秒
with serial.Serial(port=PORT, baudrate=BAUDRATE, timeout=1.0, write_timeout=1.0) as ser:
print(f"成功打开串口: {ser.name}")
# 建议给硬件留出一定的上电初始化时间 (特别是Arduino等带有DTR复位机制的板卡)
time.sleep(1.5)
# 清空历史残留缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()
# 准备发送指令:必须将 str 编码为 bytes,通常追加换行符 \n 作为帧结尾
command_str = "GET_SENSOR_DATA\n"
ser.write(command_str.encode("utf-8"))
print(f"已发送指令: {command_str.strip()}")
# 接收响应:readline() 持续读取直到遇见 \n 或超时
response_bytes = ser.readline()
if response_bytes:
# 将接收到的原始 bytes 解码为可读文本
response_str = response_bytes.decode("utf-8", errors="replace").strip()
print(f"收到下位机回复: {response_str}")
else:
print("在设定的 1.0 秒超时内未收到回复。")
except serial.SerialException as err:
print(f"串口通信错误: {err}")
if __name__ == "__main__":
send_and_receive()
2. 持续循环监听模式
在许多实际应用(如采集传感器遥测数据)中,下位机会主动以固定频率不断向主机推送数据。主机端通常采用循环监听方式读取:
import serial
def continuous_monitor(port="COM3", baudrate=115200):
with serial.Serial(port=port, baudrate=baudrate, timeout=0.5) as ser:
print(f"开始监听 {port},按 Ctrl+C 退出...")
ser.reset_input_buffer()
while True:
try:
# in_waiting 属性表示当前硬件接收缓冲区中已到达的未读字节数
if ser.in_waiting > 0:
raw_data = ser.readline()
text = raw_data.decode("utf-8", errors="ignore").strip()
if text:
print(f"[RX] {text}")
except KeyboardInterrupt:
print("\n用户手动中断,退出监听。")
break
六、常见问题排查与工程实践总结
串口编程涉及硬件与操作系统的交互,容易受到驱动、权限、硬件引脚等多方面因素影响。掌握以下典型问题的排查路径,可大幅减少调试成本。
| 现象 / 报错信息 | 根本原因 | 解决方法 |
|---|---|---|
PermissionError: [Errno 13] Permission denied (Linux) |
当前操作系统用户不在 dialout 用户组中,无权读写串口设备节点。 |
在终端执行 sudo usermod -a -G dialout $USER,注销重新登录生效。 |
SerialException: could not open port 'COMx': Access is denied (Win) |
串口已经被其他软件(如串口调试助手、Arduino IDE串口监视器、另一个Python进程)占用。 | 关闭冲突软件;在任务管理器中结束占用COM口的残留Python进程。 |
读取出来全是乱码或 \xff\xfe |
波特率不匹配,或通信双方的校验位/数据位设置不一致。 | 核查下位机代码中的 Serial.begin(...) 数值,确保与Python端配置严格一致。 |
| Python脚本一连接,下位机立刻重启 | USB转串口芯片的 DTR/RTS 引脚在串口打开时会拉低,触发单片机硬件复位。 | 在打开串口后设置 ser.dtr = False,或在硬件复位电容处加跳线旁路。 |
| 程序莫名卡住不向下执行 | 未配置 timeout,或下位机发送的数据末尾缺少换行符导致 readline() 无法结束。 |
显式传递 timeout=1.0;检查下位机发送是否带上了 \n。 |
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













