在本教程中,我们将使用Python和PyQt库来开发一个简单的串口数据实时绘图应用。该应用能够实时接收串口数据并绘制成动态曲线,同时保存数据到本地CSV文件。
首先,确保你已经在Windows上安装了Python。接着,通过以下命令安装必要的库:
pip install PyQt5 pyserial matplotlib
让我们一步步来分析这个应用的代码:
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
的串口通信线程类SerialThread
。SerialPlotter
类中的setup_ui
方法用于创建界面,包括串口选择、参数设置和实时绘图区域。
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
库打开串口,并创建一个用于串口通信的线程。
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
发送数据到主线程。
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图表。在这个方法中,我们使用AutoLocator
和AutoMinorLocator
自动调整横纵坐标的精度。同时,我们调用save_to_csv
方法将最新接收到的数据保存到CSV文件中。
if __name__ == '__main__':
app = QApplication(sys.argv)
window = SerialPlotter()
window.show()
sys.exit(app.exec_())
在主程序中,我们创建了一个QApplication
实例,实例化了SerialPlotter
窗口,并通过show
方法显示窗口。最后,通过sys.exit(app.exec_())
来确保程序在关闭窗口时能够正常退出。
这个应用的整体功能是通过一个简洁的界面实时绘制串口数据。用户可以选择不同的串口和参数,点击“开始接收”按钮后,应用将实时接收串口数据并在界面上绘制成动态曲线。同时,接收到的数据将以CSV格式保存到本地文件。
serial_data.csv
文件中。界面效果:
数据保存效果:
通过这个实例,我们了解了如何使用PyQt、serial和Matplotlib库开发一个串口数据实时绘图应用。这个项目结合了串口通信、GUI开发和数据可视化的知识点,是一个不错的学习材料。