You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3.11串口通信数据接收延迟及界面滚动异常求助

串口通信程序问题解决方案

问题1:串口数据延迟/滞留的修复

问题原因

串口缓冲区存在未读取的遗留数据,连接后未清空导致首次按键输出旧数据;另外readline()依赖换行符,若设备返回数据无正确换行,也会引发数据堆积。

修复方案

  1. 打开串口后立即清空输入缓冲区
  2. 给串口设置超时,避免读取操作长期阻塞线程
  3. 只发送非空数据,过滤无效空行

问题2:TextEdit自动滚动失效的修复

问题原因

moveCursor(QTextCursor.End)受UI刷新时机影响,或用户手动滚动后append无法自动触发滚动,导致无法定位到最新内容。

修复方案

直接调用滚动条的setValue方法,强制将滚动条拉到最大值位置,确保每次添加数据后自动滚动到底部。


修改后的完整代码

import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import serial
from serial.tools import list_ports
from serial.serialutil import SerialException
import re

class SerialThread(QThread):
    received_data = pyqtSignal(str)

    def __init__(self, port, baudrate):
        super().__init__()
        self.port = port
        self.baudrate = baudrate

    def run(self):
        self.running = True
        try:
            self.ser = serial.Serial(port=self.port,
                            baudrate=self.baudrate,
                            bytesize=serial.EIGHTBITS,
                            parity=serial.PARITY_NONE,
                            stopbits=serial.STOPBITS_ONE,
                            rtscts=False, xonxoff=False,
                            dsrdtr=False,
                            timeout=0.1)  # 添加超时,避免线程阻塞
            # 打开串口后立即清空缓冲区,清除遗留数据
            self.ser.reset_input_buffer()
            while self.running:
                if self.ser.in_waiting > 0:
                    data = self.ser.readline().decode('latin-1', errors='replace').strip()
                    if data:  # 仅发送非空数据
                        self.received_data.emit(data)
        except serial.SerialException as e:
            print(f"Serial port error: {e}")

    def stop(self):
        self.running = False
        if self.ser and self.ser.is_open:
            self.ser.close()

    def send_data(self, data):
        if self.ser and self.ser.is_open:
            self.ser.write(data.encode('latin-1'))

class Main_GridLayout(QWidget):
    send_data_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ccid_value = ""
        self.msisdn_value = ""
        self.imei_value = ""
        self.serial_thread = None  # 初始化线程变量
        self.build_widgets()
        self.build_layout()

    def build_widgets(self):
        self.com_ports = [port.device for port in list_ports.comports()]
        self.com_ports_combo = QComboBox()
        self.com_ports_combo.setFixedSize(100, 25)
        self.com_ports_combo.addItem("Select COM")
        self.com_ports_combo.addItems(self.com_ports)

        self.btn_mainconnect = QPushButton(text="Connect")
        self.commactive = False
        self.btn_mainconnect.clicked.connect(self.left_connect)
        self.btn_mainconnect.setFixedSize(100, 25)

        self.text_area = QTextEdit()
        self.text_area.setReadOnly(True)
        self.text_area.setFixedSize(600, 600)
        self.text_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        self.label_msisdn = QLabel("MSISDN: ")
        self.current_msisdn = QLineEdit()
        self.current_msisdn.setStyleSheet("background: transparent; border: 0px;")
        self.current_msisdn.setText(str(self.msisdn_value))
        self.current_msisdn.setReadOnly(True)
        self.label_ccid = QLabel("CCID: ")
        self.current_ccid = QLineEdit()
        self.current_ccid.setStyleSheet("background: transparent; border: 0px;")
        self.current_ccid.setText(str(self.ccid_value))
        self.current_ccid.setReadOnly(True)
        self.label_imei = QLabel("IMEI: ")
        self.current_imei = QLineEdit()
        self.current_imei.setStyleSheet("background: transparent; border: 0px;")
        self.current_imei.setText(str(self.imei_value))
        self.current_imei.setReadOnly(True)

        self.bottomcon = QWidget()
        self.bottomcon_layout = QHBoxLayout()
        self.bottomcon.setLayout(self.bottomcon_layout)
        self.bottomcon_layout.addWidget(self.label_msisdn)
        self.bottomcon_layout.addWidget(self.current_msisdn)
        self.bottomcon_layout.addWidget(self.label_ccid)
        self.bottomcon_layout.addWidget(self.current_ccid)
        self.bottomcon_layout.addWidget(self.label_imei)
        self.bottomcon_layout.addWidget(self.current_imei)

    def build_layout(self):
        self.container = QFrame()
        grid_layout = QGridLayout(self.container)

        grid_layout.addWidget(self.com_ports_combo, 1, 0, 1, 1)
        grid_layout.addWidget(self.btn_mainconnect, 2, 0, 1, 1)
        grid_layout.addWidget(self.text_area, 1, 1, 20, 1)
        grid_layout.addWidget(self.bottomcon, 21, 1, 1, 1)
        lay = QVBoxLayout(self)
        lay.addWidget(self.container)
        self.container.setFixedHeight(self.container.sizeHint().height())

    def left_connect(self):
        selected_port = self.com_ports_combo.currentText()

        if self.commactive:
            self.commactive = False
            self.btn_mainconnect.setText("Connect")
            self.stop_communication()
            return

        if selected_port == "Select COM":
            QMessageBox.critical(self, "COM PORT ERROR", "Select COM Port and click the connect button.")
            return

        try:
            ser = serial.Serial(selected_port)
            ser.close()
        except SerialException:
            QMessageBox.critical(self, "Error", f"Port {selected_port} is already open")
            return

        self.commactive = True
        self.btn_mainconnect.setText("Disconnect")
        self.start_communication()

    def start_communication(self):
        selected_port = self.com_ports_combo.currentText()
        selected_baud_rate = 115200
        self.serial_thread = SerialThread(selected_port, selected_baud_rate)
        self.serial_thread.received_data.connect(self.update_text_area)
        self.send_data_signal.connect(self.serial_thread.send_data)
        self.serial_thread.start()

    def stop_communication(self):
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()
            self.serial_thread.wait()  # 等待线程完全结束,避免资源泄漏

    def update_text_area(self, data):
        self.text_area.append(data)
        # 强制滚动到底部
        scroll_bar = self.text_area.verticalScrollBar()
        scroll_bar.setValue(scroll_bar.maximum())

def main():
    app = QApplication(sys.argv)
    print("default-style: ", app.style().metaObject().className())
    app.setStyle("fusion")
    mainwindow = Main_GridLayout()
    mainwindow.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

关键修改点说明

  1. 清空串口缓冲区:打开串口后调用self.ser.reset_input_buffer(),清除连接前的遗留数据
  2. 添加串口超时:初始化串口时设置timeout=0.1,避免readline()长期阻塞线程
  3. 过滤空数据:仅当读取到非空内容时才发送信号,避免无效空行干扰
  4. 强制滚动:通过滚动条的setValue方法直接拉到底部,确保自动滚动生效
  5. 线程安全关闭:停止线程时调用wait(),确保线程完全释放资源

内容的提问来源于stack exchange,提问作者Umi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 00:15:53