基于Python的Qt开发之Pyside6 QtSerialPort库的使用
一、简介
QtSerialPort 是 PySide6 用于实现串口异步通信的核心模块,专门用于与串口设备(如单片机、传感器、蓝牙模块、串口打印机等)进行数据交互。
二、QtSerialPort库的核心内容
QtSerialPort 库的核心功能由两个关键类提供,核心操作类QSerialPort和辅助信息类QSerialPortInfo, 所有串口操作均围绕这两个类展开:
1、核心操作类:QSerialPort
QSerialPort是实现串口通信的核心类,负责串口的配置、打开 / 关闭、数据读写、错误处理等核心操作,它的核心特性包括:
- 支持串口参数配置(波特率、数据位、校验位、停止位、流控)
- 提供异步读写接口(基于信号与槽,无阻塞)
- 支持串口状态检测和错误反馈
- 支持数据缓冲区操作
QSerialPort 常用属性(串口参数)
串口通信的参数必须与外设保持一致,否则无法正常通信,常用参数如下:
| 参数类型 | 示例 | 说明 |
| 波特率 | QSerialPort.Baud9600,QSerialPort.Baud115200 | 串口通信速率,最常用 9600、115200 |
| 数据位 | QSerialPort.Data8,QSerialPort.Data7 | 每帧数据的位数,最常用 8 位 |
| 校验位 | QSerialPort.NoParity、QSerialPort.EvenParity | 奇偶校验位,最常用无校验(NoParity) |
| 停止位 | QSerialPort.OneStop、QSerialPort.TwoStop | 数据帧结束标志,最常用 1 位 |
| 流控 | QSerialPort.NoFlowControl、QSerialPort.HardwareControl | 流量控制,最常用无流控 |
QSerialPort 常用信号
由于 PySide6 采用信号与槽机制,QSerialPort 的异步操作通过以下核心信号通知上层逻辑:
readyRead():当串口接收缓冲区有新数据到达时触发(读取数据的核心信号)errorOccurred(error):当串口发生错误时触发(如端口不存在、权限不足)bytesWritten(bytes):当数据成功写入串口发送缓冲区时触发
QSerialPort 常用方法
setPortName(portName):设置串口名(如 "COM3"(Windows)、"/dev/ttyUSB0"(Linux))open(openMode):打开串口,打开模式常用QSerialPort.ReadWrite(读写模式)close():关闭串口,释放串口资源write(data):向串口写入数据(需传入字节类型bytes,而非字符串)readAll():读取接收缓冲区中的所有可用数据(返回QByteArray,可转换为 Python 字节 / 字符串)read(maxSize):读取指定长度的字节数据isOpen():判断串口是否已成功打开clear():清空接收 / 发送缓冲区
2. 辅助信息类:QSerialPortInfo
QSerialPortInfo 是辅助类,用于枚举系统中可用的串口设备,获取串口的详细信息,无需打开串口即可使用,核心作用是帮助用户选择有效的串口。
QSerialPortInfo 常用方法
availablePorts():静态方法,返回系统中所有可用串口的QSerialPortInfo列表portName():获取串口名(如 "COM3"、"/dev/ttyUSB0")description():获取串口设备描述(如 "USB-SERIAL CH340")manufacturer():获取串口设备制造商信息isValid():判断当前QSerialPortInfo对应的串口是否有效
三、QtSerialPort 基本使用流程
使用 QtSerialPort 实现串口通信的核心流程分为 5 步,全程基于信号与槽实现异步无阻塞操作:
- 导入核心类
- 枚举可用串口(可选,方便用户选择)
- 配置串口参数并打开串口
- 绑定信号与槽(处理数据接收、错误反馈)
- 发送数据 / 关闭串口
四、完整可运行示例
下面实现一个简单的串口通信工具,支持枚举串口、打开 / 关闭串口、发送数据、接收数据并显示,可直接运行测试(需安装 PySide6 和对应串口驱动)。
from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLineEdit, QTextEdit ) from PySide6.QtCore import Qt from PySide6.QtSerialPort import QSerialPort, QSerialPortInfo import sys class SerialPortTool(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("QtSerialPort 串口通信示例") self.setFixedSize(600, 400) # 1. 初始化串口对象(核心) self.serial_port = QSerialPort() # 2. 构建界面 self._init_ui() # 3. 绑定信号与槽 self._bind_signals() # 4. 初始化时枚举可用串口 self.refresh_serial_ports() def _init_ui(self): """构建界面""" central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 串口选择区域 port_layout = QHBoxLayout() self.port_combo = QComboBox() self.refresh_btn = QPushButton("刷新串口") self.open_btn = QPushButton("打开串口") self.close_btn = QPushButton("关闭串口") self.close_btn.setEnabled(False) port_layout.addWidget(self.port_combo) port_layout.addWidget(self.refresh_btn) port_layout.addWidget(self.open_btn) port_layout.addWidget(self.close_btn) # 数据发送区域 send_layout = QHBoxLayout() self.send_input = QLineEdit() self.send_input.setPlaceholderText("输入要发送的数据,按回车或点击发送按钮") self.send_btn = QPushButton("发送数据") send_layout.addWidget(self.send_input) send_layout.addWidget(self.send_btn) # 数据接收区域 self.recv_text = QTextEdit() self.recv_text.setPlaceholderText("接收的数据将显示在这里...") self.recv_text.setReadOnly(True) # 添加到主布局 main_layout.addLayout(port_layout) main_layout.addLayout(send_layout) main_layout.addWidget(self.recv_text) def _bind_signals(self): """绑定信号与槽""" # 界面按钮信号 self.refresh_btn.clicked.connect(self.refresh_serial_ports) self.open_btn.clicked.connect(self.open_serial_port) self.close_btn.clicked.connect(self.close_serial_port) self.send_btn.clicked.connect(self.send_data) self.send_input.returnPressed.connect(self.send_data) # 串口核心信号 self.serial_port.readyRead.connect(self.read_serial_data) # 接收数据 self.serial_port.errorOccurred.connect(self.handle_serial_error) # 错误处理 def refresh_serial_ports(self): """枚举并刷新可用串口""" self.port_combo.clear() # 获取所有可用串口 available_ports = QSerialPortInfo.availablePorts() for port_info in available_ports: # 显示格式:串口名 - 设备描述 port_text = f"{port_info.portName()} - {port_info.description()}" self.port_combo.addItem(port_text, port_info.portName()) def open_serial_port(self): """配置并打开串口""" if self.port_combo.count() == 0: self._append_recv_text("错误:无可用串口!") return # 获取选中的串口名 selected_port = self.port_combo.currentData() if not selected_port: self._append_recv_text("错误:无效的串口!") return # 1. 配置串口参数 self.serial_port.setPortName(selected_port) self.serial_port.setBaudRate(QSerialPort.Baud9600) # 波特率 9600 self.serial_port.setDataBits(QSerialPort.Data8) # 数据位 8 self.serial_port.setParity(QSerialPort.NoParity) # 无校验 self.serial_port.setStopBits(QSerialPort.OneStop) # 停止位 1 self.serial_port.setFlowControl(QSerialPort.NoFlowControl) # 无流控 # 2. 打开串口(读写模式) if self.serial_port.open(QSerialPort.ReadWrite): self._append_recv_text(f"成功:打开串口 {selected_port}") self.open_btn.setEnabled(False) self.close_btn.setEnabled(True) self.port_combo.setEnabled(False) self.refresh_btn.setEnabled(False) else: self._append_recv_text(f"错误:打开串口 {selected_port} 失败(权限不足/端口被占用)") def close_serial_port(self): """关闭串口""" if self.serial_port.isOpen(): self.serial_port.close() self._append_recv_text(f"信息:串口 {self.serial_port.portName()} 已关闭") self.open_btn.setEnabled(True) self.close_btn.setEnabled(False) self.port_combo.setEnabled(True) self.refresh_btn.setEnabled(True) def send_data(self): """向串口发送数据""" if not self.serial_manager.serial_port.isOpen(): self._append_recv_text("错误:串口未打开!") return # 获取输入框数据并转换为字节类型(必须传入 bytes,字符串需 encode) input_text = self.send_input.text().strip() if not input_text: return # 判断是否为十六进制格式(去除空格后检查是否全为十六进制字符) clean_text = input_text.replace(' ', '').replace('\t', '').replace('\n', '') # 如果是十六进制字符串,则转换为字节数组 if all(c in '0123456789ABCDEFabcdef' for c in clean_text) and len(clean_text) % 2 == 0: try: # 将十六进制字符串转换为字节数组 send_bytes = bytes.fromhex(clean_text) except ValueError: self._append_recv_text("错误:十六进制数据格式不正确!") return else: # 按普通字符串处理 send_bytes = input_text.encode("utf-8") # 写入串口(encode 转换为 UTF-8 字节流,外设需对应编码) written_bytes = self.serial_manager.serial_port.write(send_bytes) if written_bytes > 0: # 显示发送的十六进制数据 send_hex = send_bytes.hex().upper() self._append_recv_text(f"发送:{send_hex}(字节数:{written_bytes})") # self.send_input.clear() def read_serial_data(self): """读取串口接收的数据(响应 readyRead 信号)""" if not self.serial_port.isOpen(): return # 读取所有可用数据(QByteArray -> bytes -> str) recv_data = self.serial_port.readAll() # 转换为 Python 字节类型,再解码为字符串(与发送端编码一致) recv_str = bytes(recv_data).decode("utf-8", errors="ignore") if recv_str: self._append_recv_text(f"接收:{recv_str}") def handle_serial_error(self, error): """处理串口错误(响应 errorOccurred 信号)""" # 在 PySide6 中,使用 QSerialPort.SerialPortError 枚举值 if error == QSerialPort.SerialPortError.NoError: error_msg = "信息:无错误" elif error == QSerialPort.SerialPortError.DeviceNotFoundError: error_msg = "错误:串口端口不存在" elif error == QSerialPort.SerialPortError.PermissionError: error_msg = "错误:串口权限不足" elif error == QSerialPort.SerialPortError.OpenError: error_msg = "错误:无法打开串口(可能已被其他程序占用)" elif error == QSerialPort.SerialPortError.NotOpenError: error_msg = "错误:串口未打开" elif error == QSerialPort.SerialPortError.ResourceError: error_msg = "错误:串口资源被占用或断开连接" elif error == QSerialPort.SerialPortError.UnsupportedOperationError: error_msg = "错误:不支持的操作" elif error == QSerialPort.SerialPortError.TimeoutError: error_msg = "错误:操作超时" elif error == QSerialPort.SerialPortError.UnknownError: error_msg = "错误:未知串口错误" else: error_msg = f"错误:未知串口错误(代码:{error})" self._append_recv_text(error_msg) # 错误发生时自动关闭串口 self.close_serial_port() def _append_recv_text(self, text): """向接收区域追加文本并自动滚动""" self.recv_text.append(text) # 自动滚动到最下方 scroll_bar = self.recv_text.verticalScrollBar() scroll_bar.setValue(scroll_bar.maximum()) def closeEvent(self, event): """窗口关闭时自动关闭串口""" if self.serial_port.isOpen(): self.serial_port.close() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) window = SerialPortTool() window.show() sys.exit(app.exec())五、运行与使用说明
- 运行效果:启动程序后,会自动枚举系统中的可用串口,显示在下拉框中。
- 操作步骤:
- 选择需要连接的串口(如 "COM3 - USB-SERIAL CH340")
- 点击「打开串口」(成功后按钮状态切换)
- 在输入框中输入数据,点击「发送数据」或按回车发送
- 外设返回数据后,会自动显示在下方接收区域
- 结束通信后,点击「关闭串口」释放资源
六、关键注意事项
- 数据格式:
QSerialPort.write()仅接受字节类型(bytes),字符串必须通过encode()转换;读取数据时,readAll()返回QByteArray,需先转换为bytes再decode()为字符串。 - 串口参数一致性:波特率、数据位等参数必须与外设完全一致,否则会出现乱码或无法通信。
- 异步无阻塞:QtSerialPort 采用异步通信,
readyRead()信号仅在有新数据到达时触发,无需轮询(轮询会导致阻塞,影响界面响应)。 - 资源释放:串口使用完毕后必须调用
close()关闭,否则会导致端口被占用,下次无法打开。 - 编码一致性:收发数据的编码格式(如 UTF-8、GBK)必须与外设保持一致,否则会出现乱码。
七、总结
- QtSerialPort 库的核心是
QSerialPort(串口操作)和QSerialPortInfo(串口枚举)。 - 串口通信的核心流程是「配置参数→打开串口→绑定信号槽→读写数据→关闭串口」。
- 异步通信依赖
readyRead()信号接收数据,无需轮询,保证界面响应流畅。 - 数据格式转换(字符串 ↔ 字节)和串口参数一致性是串口通信成功的关键。