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

如何通过命令行交互控制pyqtgraph启停Arduino串口数据绘图?

解决PyQtGraph串口绘图交互式控制的崩溃问题

看起来你遇到的核心问题有两个:Qt事件循环被命令行输入阻塞,以及定时器生命周期管理不当,这两个问题叠加起来导致了窗口启动就崩溃。我来一步步帮你解决:

问题根源分析

  1. 命令行输入阻塞GUI事件循环:Qt的GUI线程是单线程的,如果在主线程里用input()等待用户输入,整个GUI会完全卡住,甚至因为事件循环无法处理系统消息而崩溃。
  2. 定时器的错误管理:如果把定时器临时创建在if语句块里,代码块执行完毕后,定时器对象会被Python的垃圾回收机制销毁,同时Qt的事件系统也无法正确跟踪这个定时器,直接引发崩溃。

解决方案:线程分离+正确的定时器管理

我们需要把命令行输入放到单独的线程里,避免阻塞GUI;同时把定时器作为窗口类的成员变量,让它的生命周期和窗口绑定,方便启动/停止控制。

完整示例代码

import sys
import threading
from collections import deque
import serial
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

class SerialPlotWindow(QtGui.QMainWindow):
    def __init__(self, serial_port='COM3', baud_rate=9600):
        super().__init__()
        # 初始化GUI部件
        self.setWindowTitle("Arduino Serial Plotter")
        self.central_widget = QtGui.QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QtGui.QVBoxLayout(self.central_widget)
        
        # 创建绘图部件
        self.plot_widget = pg.PlotWidget()
        self.layout.addWidget(self.plot_widget)
        self.curve = self.plot_widget.plot(pen='y')
        
        # 数据缓存(避免内存溢出)
        self.data_buffer = deque(maxlen=100)
        
        # 串口初始化
        self.serial_port = serial.Serial(serial_port, baud_rate, timeout=0.1)
        self.serial_running = False
        
        # 定时器(作为类成员,保证生命周期)
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update_plot)
        
    def start_plotting(self):
        if not self.timer.isActive():
            self.serial_running = True
            self.timer.start(50)  # 每50ms更新一次绘图
            print("绘图已启动,输入'B'停止")
    
    def stop_plotting(self):
        if self.timer.isActive():
            self.timer.stop()
            self.serial_running = False
            print("绘图已停止,输入'A'启动")
    
    def update_plot(self):
        # 读取串口数据(如果串口读取慢,建议用单独线程读取后存到队列)
        if self.serial_running and self.serial_port.is_open:
            try:
                data = self.serial_port.readline().decode().strip()
                if data:
                    value = float(data)
                    self.data_buffer.append(value)
                    self.curve.setData(list(self.data_buffer))
            except ValueError:
                # 处理无效数据,比如串口传输的干扰字符
                pass
    
    def closeEvent(self, event):
        # 关闭窗口时清理资源
        if self.serial_port.is_open:
            self.serial_port.close()
        self.timer.stop()
        event.accept()

def command_line_input(window):
    """单独线程处理命令行输入,避免阻塞GUI事件循环"""
    while True:
        user_input = input("请输入'A'启动绘图,'B'停止绘图:").strip().upper()
        if user_input == 'A':
            # 用Qt的信号槽跨线程调用GUI方法,避免线程安全问题
            QtCore.QMetaObject.invokeMethod(window, "start_plotting")
        elif user_input == 'B':
            QtCore.QMetaObject.invokeMethod(window, "stop_plotting")
        else:
            print("无效输入,请输入'A'或'B'")

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    # 根据你的Arduino实际串口修改端口和波特率
    window = SerialPlotWindow(serial_port='COM3', baud_rate=9600)
    window.show()
    
    # 启动命令行输入线程,设置为守护线程随主程序退出
    input_thread = threading.Thread(target=command_line_input, args=(window,), daemon=True)
    input_thread.start()
    
    sys.exit(app.exec_())

关键细节说明

  1. 线程分离:command_line_input函数在单独的线程里运行,用input()等待用户输入,不会阻塞Qt的GUI事件循环,保证窗口始终响应。
  2. 跨线程安全调用:用QtCore.QMetaObject.invokeMethod调用GUI类的方法,这是Qt官方推荐的跨线程操作GUI的方式,避免线程冲突导致的崩溃。
  3. 定时器管理:定时器作为类的成员变量self.timer,启动/停止都是通过调用start()和stop()方法,生命周期和窗口绑定,不会被提前回收。
  4. 数据缓存:用deque设置最大长度,避免随着时间推移内存占用过高。
  5. 资源清理:重写closeEvent方法,在窗口关闭时关闭串口、停止定时器,避免资源泄漏。

你需要调整的地方

  • 修改SerialPlotWindow初始化时的serial_port参数,改成你的Arduino实际串口(Windows通常是COMx,Linux是/dev/ttyACM0)。
  • 调整波特率baud_rate,和Arduino代码里的波特率保持一致。
  • 如果Arduino发送的数据格式不是纯数字,需要修改update_plot里的数据解析逻辑。

内容的提问来源于stack exchange,提问作者Mario Andres Gomez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:30:40