Python3.11串口通信数据接收延迟及界面滚动异常求助
串口通信程序问题解决方案
问题1:串口数据延迟/滞留的修复
问题原因
串口缓冲区存在未读取的遗留数据,连接后未清空导致首次按键输出旧数据;另外readline()依赖换行符,若设备返回数据无正确换行,也会引发数据堆积。
修复方案
- 打开串口后立即清空输入缓冲区
- 给串口设置超时,避免读取操作长期阻塞线程
- 只发送非空数据,过滤无效空行
问题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()
关键修改点说明
- 清空串口缓冲区:打开串口后调用
self.ser.reset_input_buffer(),清除连接前的遗留数据 - 添加串口超时:初始化串口时设置
timeout=0.1,避免readline()长期阻塞线程 - 过滤空数据:仅当读取到非空内容时才发送信号,避免无效空行干扰
- 强制滚动:通过滚动条的
setValue方法直接拉到底部,确保自动滚动生效 - 线程安全关闭:停止线程时调用
wait(),确保线程完全释放资源
内容的提问来源于stack exchange,提问作者Umi
相关产品推荐
相关产品推荐

