我试图将从串口(从arduino发送)获得的数据显示在图表上。我尝试了很多不同的东西,但它似乎没有按预期工作。我试图将 x 轴作为程序启动以来的时间,将 y 轴作为从 arduino 发送的串行端口获取的数据。我尝试过调试,但没有出现错误。该代码似乎在 update_graphs() 函数中的某个位置停止,但我不知道是什么导致它停止。我很迷茫,不知道现在该尝试什么
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import serial
import threading
import queue
import time
# Function to read data from the serial port
def read_serial_data(serial_port, data_queue):
while True:
try:
line = serial_port.readline().decode().strip()
data_queue.put(line)
except UnicodeDecodeError:
pass
# Function to update the graphs
def update_graphs():
start_time = time.time()
global x_data
while True:
try:
data = data_queue.get_nowait()
# Calculate the time since the program started
current_time = time.time() - start_time
# Update the graphs with the new data and time
x_data.append(current_time)
# Attempt to add data to y_data as a float
try:
y_data.append(float(data))
except ValueError:
print(f"Error converting data to float: {data}")
x_data = x_data[-len(y_data):] #limits data in the list
ax1.clear()
ax1.plot(x_data, y_data, label='Sensor Data 1')
ax1.set_xlabel('Time (seconds)')
ax1.set_ylabel('Sensor Data 1')
ax1.legend()
canvas.draw()
except queue.Empty:
pass
root.after(100, update_graphs)
# GUI setup
root = tk.Tk()
root.title("Sensor Data Graphs")
# Serial port setup
ser = serial.Serial("COM5", baudrate=9600)
# Data queue for inter-thread communication
data_queue = queue.Queue()
# Create and start a thread to read data from the serial port
serial_thread = threading.Thread(target=read_serial_data, args=(ser, data_queue), daemon=True)
serial_thread.start()
# Create and start a thread to update the graphs
update_thread = threading.Thread(target=update_graphs, daemon=True)
update_thread.start()
# Create multiple graphs
fig, ax1 = plt.subplots()
x_data, y_data = [], [] # Lists to store data and corresponding time
# Create Tkinter canvas for embedding Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# Start the Tkinter main loop
root.mainloop()
Arduino代码
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(random(0, 50));
delay(200);
}
我尝试减慢 Arduino 代码的速度,认为它发送数据的速度太快,Python 脚本无法读取,但它不起作用
更新
非常肯定这与计时有关,因为当我在 vscode 中使用调试并逐行进行时,一切正常,但当我让它自行运行时,它只是冻结了并且不会执行任何操作。
Arduino 代码保持不变
更新代码:
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import serial
import threading
import queue
import time
# Function to read data from the serial port
def read_serial_data(serial_port, data_queue):
while True:
try:
line = serial_port.readline().decode().strip()
data_queue.put(line)
except UnicodeDecodeError:
pass
# Function to update the graphs
def update_graphs(start_time):
while True:
try:
data = data_queue.get(timeout=1)
# Calculate the time since the program started
current_time = time.time() - start_time
# Update the graphs with the new data and time
x_data.append(current_time)
y_data.append(float(data))
ax1.clear()
ax1.plot(x_data, y_data, label='Sensor Data 1')
ax1.set_xlabel('Time (seconds)')
ax1.set_ylabel('Sensor Data 1')
ax1.legend()
canvas.draw()
except queue.Empty:
pass
except Exception as e:
print(f"Error in update_graphs: {e}")
# GUI setup
root = tk.Tk()
root.title("Sensor Data Graphs")
# Serial port setup
ser = serial.Serial("COM5", baudrate=9600)
# Data queue for inter-thread communication
data_queue = queue.Queue()
# Create and start a thread to read data from the serial port
serial_thread = threading.Thread(target=read_serial_data, args=(ser, data_queue), daemon=True)
serial_thread.start()
# Create multiple graphs
fig, ax1 = plt.subplots()
x_data, y_data = [], [] # Lists to store data and corresponding time
# Create Tkinter canvas for embedding Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# Get the start time
start_time = time.time()
# Schedule the update_graphs function to run every 100 milliseconds
root.after(200, update_graphs, start_time)
# Start the Tkinter main loop
root.mainloop()
更新
图表似乎仅在我使用 vscode 中的调试功能暂停和取消暂停程序时更新
当数据发生变化时,可以使用
matplotlib.animation.FuncAnimation
更新 matplotlib 图形。这个答案提供了一个基于这种方法的演示(基于问题的想法)。
以下
gif
显示了演示程序的输出。
import threading, time, random
import matplotlib.pyplot as plt
import queue
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.animation import FuncAnimation
# Initialising objects to handle data
dataQueue = queue.Queue()
sensor_data = []
time_data = []
# This function mimics data generated from an arduino for this demo program
# Replace this with actual arduino-data-reading function
# This function is generating some random data every 0.2 seconds
def arduino():
while(True):
dataQueue.put(random.randint(1,10))
time.sleep(0.2)
# Initialising the arduino function in separate thread, so, it generates data
# independently and in parallel to our plotting logic
arduinoThread = threading.Thread(target = arduino, daemon= True)
arduinoThread.start()
# Initialising the matplotlib figure and axis objects
fig, ax = plt.subplots()
# This function reads data from data-queue, adds time-data and updates the
# graphs according to the new data. If there is no data, exception occurs
# and the function skips the update.
def updateGraph(frame_number):
try:
sensor_data.append(dataQueue.get(timeout=1))
time_data.append(time.time())
ax.clear()
ax.plot(time_data, sensor_data, color = 'orange')
ax.set_ylabel('Sensor Data')
ax.set_xlabel('Time')
except queue.Empty:
pass
# Creating the tkinter root window
root = tk.Tk()
root.title('Sensor Data')
# Setting a text label (optional) in tkinter window
tk.Label(master = root, text = 'Sensor 1 Data', font = ('Courier', 15, 'bold')).pack(side = 'top', pady = 10)
# Create Tkinter canvas for embedding Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# Initialising the matplotlib FuncAnimation which calls the updateGraph function every 500 milli-second
anim = FuncAnimation(fig, updateGraph, interval = 500) # interval between each update (or frame) in milli-seconds
# Launching the tkinter window
root.mainloop()
我已将理解代码所需的大部分辅助信息放在代码内的注释中。基本上,我们为 matplotlib 图形创建了一个
matplotlib.animation.FuncAnimation
对象,即 fig
,它会在 updateGraph
参数中指定的时间后定期调用 interval
函数。
updateGraph
尝试从 dataQueue
读取数据。如果找到数据,它会从 dataQueue
中读取一个数据值并将其添加到 sensor_data
中,然后将当前时间添加到 time_data
中,最后更新图形 (fig
)。如果 dataQueue
为空,则会发生异常,并且会跳过更新迭代的数据和图形。
注意:由于我们没有用于此演示的 Arduino,因此我创建了一个名为
arduino
的函数,该函数每 0.2 秒生成一些随机数据,并通过 arduinoThread
与绘图程序并行运行以模仿传感器的行为由真正的 Arduino 生成的数据。