选择串口时如何更新PyQtGraph以绘制串口采集的数据?
问题:PyQtGraph无法更新串口数据绘图
我正在编写脚本,实现通过ComboBox选择串口后绘制该串口的数据,使用hour和temperature两个变量存储数据,但PyQtGraph无法完成绘图更新。尝试过创建定时器清空数据并重启,但未取得成功。
原代码
from PySide2.QtWidgets import QApplication, QMainWindow, QComboBox, QVBoxLayout, QWidget from PySide2.QtCore import QSize, QTimer from pyqtgraph import PlotWidget, plot import pyqtgraph as pg import sys import serial.tools.list_ports from datetime import datetime comlist = serial.tools.list_ports.comports() connected = [] for element in comlist: connected.append(element.device) now = datetime.now() date_now = now.strftime("%H:%M:%S") hour = [] temperature = [] class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle("Raspberry Pico Monitor") self.setFixedSize(QSize(600, 500)) layout = QVBoxLayout() self.cmbox = QComboBox() self.cmbox.addItems(connected) self.graphWidget = pg.PlotWidget() layout.addWidget(self.cmbox) layout.addWidget(self.graphWidget) styles = {"color": "#E1AF67", "font-size": "12px"} self.graphWidget.setLabel("left", "Temperature (°C)", **styles) self.graphWidget.setLabel("bottom", "Hour (H)", **styles) self.graphWidget.setBackground("#1F2832") self.graphWidget.addLegend() self.graphWidget.setLabel('left', 'Temperature', units='y') self.graphWidget.setLabel('bottom', 'hour', units='s') self.graphWidget.showGrid(x=True, y=True) pen = pg.mkPen(color=(255,175,113)) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.cmbox.currentIndexChanged.connect(self.index_changed) self.graphWidget.plot(hour, temperature, name = "Raspberry Pico", pen = pen, color = 'black', symbol='t1', symbolSize=12, symbolBrush=('b')) def index_changed(self): port = self.cmbox.currentText() if port in connected: print(port + " is available to read data ...") baud_rate = 115200 ser = serial.Serial("{}".format(port), baud_rate) if (ser.isOpen()): print("This port is already open") while True: self.data = ser.readline() self.update_plot_data() else: print("Check the available ports and try again") print(connected) def update_plot_data(self): hour = date_now temperature = float(self.data) print(temperature, hour) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
问题分析
- 阻塞事件循环:
index_changed里的while True死循环会卡住Qt的UI事件循环,导致绘图无法刷新,整个界面失去响应。 - 变量作用域错误:
update_plot_data里重新定义了局部变量hour和temperature,没有修改全局或实例级别的列表,数据根本没被存储。 - 时间数据不更新:
date_now是程序启动时的时间,每次读取数据时不会重新获取当前时间,导致x轴数据始终不变。 - 无绘图引用:初始化时调用
plot()后没有保存曲线对象,无法后续更新数据,只能不断新增曲线。 - 串口读取方式错误:阻塞式读取串口会阻断UI线程,应该使用非阻塞方式结合定时器或信号槽。
修复后的代码
from PySide2.QtWidgets import QApplication, QMainWindow, QComboBox, QVBoxLayout, QWidget from PySide2.QtCore import QSize, QTimer, Slot from pyqtgraph import PlotWidget, plot import pyqtgraph as pg import sys import serial.tools.list_ports from datetime import datetime import serial class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle("Raspberry Pico Monitor") self.setFixedSize(QSize(600, 500)) # 实例化数据列表,避免全局变量问题 self.hour = [] self.temperature = [] self.ser = None self.plot_curve = None layout = QVBoxLayout() self.cmbox = QComboBox() # 初始化串口列表 comlist = serial.tools.list_ports.comports() connected = [element.device for element in comlist] self.cmbox.addItems(connected) self.graphWidget = pg.PlotWidget() layout.addWidget(self.cmbox) layout.addWidget(self.graphWidget) styles = {"color": "#E1AF67", "font-size": "12px"} self.graphWidget.setLabel("left", "Temperature (°C)", **styles) self.graphWidget.setLabel("bottom", "Time (HH:MM:SS)", **styles) self.graphWidget.setBackground("#1F2832") self.graphWidget.addLegend() self.graphWidget.showGrid(x=True, y=True) pen = pg.mkPen(color=(255,175,113)) # 初始化曲线并保存引用 self.plot_curve = self.graphWidget.plot(self.hour, self.temperature, name="Raspberry Pico", pen=pen, symbol='t1', symbolSize=12, symbolBrush=('b')) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.cmbox.currentIndexChanged.connect(self.index_changed) # 创建定时器用于读取串口数据,间隔100ms self.timer = QTimer(self) self.timer.timeout.connect(self.read_serial_data) def index_changed(self): port = self.cmbox.currentText() if self.ser and self.ser.isOpen(): self.ser.close() self.timer.stop() # 清空数据并更新绘图 self.hour.clear() self.temperature.clear() self.plot_curve.setData(self.hour, self.temperature) try: self.ser = serial.Serial(port, 115200, timeout=0.1) print(f"{port} 已打开,开始读取数据...") self.timer.start(100) # 启动定时器 except Exception as e: print(f"打开串口失败: {e}") print("可用串口列表:", [element.device for element in serial.tools.list_ports.comports()]) @Slot() def read_serial_data(self): if not self.ser or not self.ser.isOpen(): return try: data = self.ser.readline().decode('utf-8').strip() if data: temp = float(data) current_time = datetime.now().strftime("%H:%M:%S") self.hour.append(current_time) self.temperature.append(temp) # 限制数据长度,避免绘图卡顿 if len(self.hour) > 50: self.hour.pop(0) self.temperature.pop(0) # 更新绘图数据 self.plot_curve.setData(self.hour, self.temperature) print(f"温度: {temp}°C, 时间: {current_time}") except ValueError: print("收到无效数据,跳过") except Exception as e: print(f"读取串口数据出错: {e}") self.timer.stop() if self.ser: self.ser.close() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
修复说明
- 用QTimer替代阻塞循环:定时器每隔100ms读取一次串口数据,不会阻塞UI事件循环,保证界面响应。
- 实例化数据列表:把
hour和temperature改为类的实例变量,确保数据能被正确存储和更新。 - 保存曲线引用:初始化时保存
plot()返回的曲线对象,后续通过setData()更新数据,避免重复创建曲线。 - 实时获取时间:每次读取数据时重新获取当前时间,保证x轴数据实时更新。
- 串口管理优化:切换串口时关闭原有串口、清空数据、重启定时器,避免资源冲突。
- 数据长度限制:当数据超过50条时移除最早的数据,防止绘图数据过多导致卡顿。
内容的提问来源于stack exchange,提问作者LeoMouraIOT
相关产品推荐
相关产品推荐

