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

选择串口时如何更新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_() 

问题分析

  1. 阻塞事件循环:index_changed里的while True死循环会卡住Qt的UI事件循环,导致绘图无法刷新,整个界面失去响应。
  2. 变量作用域错误:update_plot_data里重新定义了局部变量hour和temperature,没有修改全局或实例级别的列表,数据根本没被存储。
  3. 时间数据不更新:date_now是程序启动时的时间,每次读取数据时不会重新获取当前时间,导致x轴数据始终不变。
  4. 无绘图引用:初始化时调用plot()后没有保存曲线对象,无法后续更新数据,只能不断新增曲线。
  5. 串口读取方式错误:阻塞式读取串口会阻断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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 11:45:57