PyQt5 + pyserial + matplotlib实现串口数据实时绘图及保存应用开发教程

2023-12-29 21:19:03

串口数据实时绘图应用开发教程

1. 简介

在本教程中,我们将使用Python和PyQt库来开发一个简单的串口数据实时绘图应用。该应用能够实时接收串口数据并绘制成动态曲线,同时保存数据到本地CSV文件。

2. 开发环境搭建

首先,确保你已经在Windows上安装了Python。接着,通过以下命令安装必要的库:

pip install PyQt5 pyserial matplotlib

3. 代码分析

让我们一步步来分析这个应用的代码:

3.1 创建PyQt窗口
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QLabel, QPushButton, QComboBox, QWidget
from PyQt5.QtCore import QTimer, QThread, pyqtSignal
from serial.tools import list_ports
import serial
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import csv
from datetime import datetime
from matplotlib.ticker import AutoLocator, AutoMinorLocator

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

    def __init__(self, serial_port, parent=None):
        super(SerialThread, self).__init__(parent)
        self.serial_port = serial_port

    def run(self):
        while self.serial_port.isOpen():
            try:
                data = self.serial_port.readline().decode().strip()
                if data:
                    self.data_received.emit(data)
            except ValueError as e:
                print(f"Error converting data to float: {e}")

class SerialPlotter(QMainWindow):
    def __init__(self):
        super().__init__()

        # ...(省略其他初始化代码)

        self.setup_ui()

    def setup_ui(self):
        central_widget = QWidget(self)
        layout = QVBoxLayout(central_widget)

        # 添加串口选择、参数设置和按钮等控件
        # ...

        self.plot_canvas = FigureCanvas(plt.Figure())

        # 添加控件到布局
        # ...

        self.setCentralWidget(central_widget)

在这一部分,我们创建了一个继承自QMainWindow的主窗口类SerialPlotter,以及一个继承自QThread的串口通信线程类SerialThreadSerialPlotter类中的setup_ui方法用于创建界面,包括串口选择、参数设置和实时绘图区域。

3.2 配置串口参数
    def refresh_serial_ports(self):
        # 更新可用串口列表
        port_list = [p.device for p in list_ports.comports()]
        self.serial_combo.clear()
        self.serial_combo.addItems(port_list)

    def toggle_serial(self):
        if self.serial_open:
            self.stop_serial()
        else:
            self.start_serial()

    def start_serial(self):
        port_name = self.serial_combo.currentText()
        self.baud_rate = int(self.baud_combo.currentText())
        data_bits_text = self.data_bits_combo.currentText()
        self.data_bits = serial.EIGHTBITS if data_bits_text == "8" else \
            serial.SEVENBITS if data_bits_text == "7" else serial.EIGHTBITS
        stop_bits_text = self.stop_bits_combo.currentText()
        self.stop_bits = serial.STOPBITS_ONE if stop_bits_text == "1" else \
            serial.STOPBITS_ONE_POINT_FIVE if stop_bits_text == "1.5" else \
            serial.STOPBITS_TWO if stop_bits_text == "2" else serial.STOPBITS_ONE

        try:
            self.serial_port = serial.Serial(port=port_name, baudrate=self.baud_rate, timeout=0.1)
            self.serial_port.bytesize = self.data_bits
            self.serial_port.stopbits = self.stop_bits
            self.serial_thread = SerialThread(self.serial_port)
            self.serial_thread.data_received.connect(self.handle_data_received)
            self.serial_thread.start()
            self.timer.start(1000)
            self.start_button.setText("停止接收")
            self.serial_open = True
        except serial.SerialException as e:
            print(f"Error opening serial port: {e}")

在这一部分,我们定义了一些方法用于刷新可用串口列表和控制串口的开启和关闭。start_serial方法中,我们获取了用户在界面上选择的串口和参数,然后通过serial库打开串口,并创建一个用于串口通信的线程。

3.3 创建串口通信线程
class SerialThread(QThread):
    data_received = pyqtSignal(str)

    def __init__(self, serial_port, parent=None):
        super(SerialThread, self).__init__(parent)
        self.serial_port = serial_port

    def run(self):
        while self.serial_port.isOpen():
            try:
                data = self.serial_port.readline().decode().strip()
                if data:
                    self.data_received.emit(data)
            except ValueError as e:
                print(f"Error converting data to float: {e}")

SerialThread类中,我们重载了run方法,该方法会在线程启动后执行。在这个方法中,我们使用一个无限循环来实时接收串口数据,并通过pyqtSignal发送数据到主线程。

3.4 实时绘图和数据保存
    def update_plot(self):
        if self.timestamps and self.data:
            ax = self.plot_canvas.figure.add_subplot(111)
            ax.clear()


            ax.plot(self.timestamps, self.data, marker='o')
            ax.set_xlabel('时间')
            ax.set_ylabel('数据值')
            ax.xaxis.set_major_locator(AutoLocator())
            ax.yaxis.set_major_locator(AutoLocator())
            ax.xaxis.set_minor_locator(AutoMinorLocator())
            ax.yaxis.set_minor_locator(AutoMinorLocator())
            self.plot_canvas.draw()
            self.save_to_csv()

    def save_to_csv(self):
        with open('serial_data.csv', 'a', newline='') as csvfile:
            csv_writer = csv.writer(csvfile)
            csv_writer.writerow([self.timestamps[-1], self.data[-1]])

update_plot方法用于更新Matplotlib图表。在这个方法中,我们使用AutoLocatorAutoMinorLocator自动调整横纵坐标的精度。同时,我们调用save_to_csv方法将最新接收到的数据保存到CSV文件中。

3.5 整体功能
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = SerialPlotter()
    window.show()
    sys.exit(app.exec_())

在主程序中,我们创建了一个QApplication实例,实例化了SerialPlotter窗口,并通过show方法显示窗口。最后,通过sys.exit(app.exec_())来确保程序在关闭窗口时能够正常退出。

4. 整体功能

这个应用的整体功能是通过一个简洁的界面实时绘制串口数据。用户可以选择不同的串口和参数,点击“开始接收”按钮后,应用将实时接收串口数据并在界面上绘制成动态曲线。同时,接收到的数据将以CSV格式保存到本地文件。

5. 程序使用方法

  1. 运行Python脚本。
  2. 在界面上选择串口、波特率、数据位和停止位。
  3. 点击“开始接收”按钮。
  4. 查看动态绘制的曲线。
  5. 点击“停止接收”按钮以停止接收数据。
  6. 数据保存在serial_data.csv文件中。

界面效果:
在这里插入图片描述
数据保存效果:
在这里插入图片描述

通过这个实例,我们了解了如何使用PyQt、serial和Matplotlib库开发一个串口数据实时绘图应用。这个项目结合了串口通信、GUI开发和数据可视化的知识点,是一个不错的学习材料。

文章来源:https://blog.csdn.net/qq_37037348/article/details/135241987
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。