Tkinter-Arduino数据采集GUI报错修复与实时绘图实现问题
问题解决与代码修正
1. 文件名无效错误修复
Windows系统文件名不允许包含冒号(:),将时间戳中的冒号替换为连字符(-)即可解决:
NAME = name + now.strftime("%d-%m-%Y-%H-%M-%S") + ".csv"
2. 实时绘图实现要点
- 使用线程处理Arduino数据读取,避免阻塞GUI主线程
- 利用Tkinter的
after()方法定期更新绘图 - 确保Matplotlib使用Tkinter兼容的后端(
TkAgg)
3. 其他关键问题修复
- 修正变量作用域问题:将
NAME、is_collecting设为全局变量,确保数据在函数间正确传递 - 移除无效的返回值接收:
CV函数不返回数据,无需赋值 - 优化串口处理:添加初始化和自动关闭逻辑,捕获连接异常
- 修复绘图逻辑:使用全局数据列表动态更新图表
完整修正代码
import time import serial from datetime import datetime import sys import csv import os import tkinter as tk import threading import matplotlib from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure # 使用Tkinter兼容的Matplotlib后端 matplotlib.use("TkAgg") # 全局变量存储数据和串口状态 ser = None Time = [] V = [] I = [] NAME = "" is_collecting = False def init_serial(): """初始化串口连接""" global ser try: ser = serial.Serial('COM4', 115200, timeout=0.1) print("串口连接成功") except Exception as e: print(f"串口连接失败: {e}") sys.exit() def CV(name, input1, input2, input3, input4, input5, input6, input7): global NAME # 生成合法文件名:替换冒号为连字符 now = datetime.now() NAME = name + now.strftime("%d-%m-%Y-%H-%M-%S") + ".csv" # 写入CSV表头 with open(NAME, 'w', newline='') as file: writer = csv.writer(file) writer.writerow(["Time", "Voltage", "Current"]) # 计算并发送参数到Arduino dac_start = round(input1 * .819) dac_end = round(input2 * .819) up_limit = round(input3 * .819) lo_limit = round(input4 * .819) inc = round(input5 * .819) interval = input5 * 1000 / input6 no_of_cycle = input6 relay = input7 param = f"<{dac_start},{dac_end},{up_limit},{lo_limit},{inc},{interval},{no_of_cycle},{relay}>" print(f"发送参数: {param}") ser.write(param.encode()) def read_from_arduino(): global is_collecting, Time, V, I e = 0 # 等待Arduino确认开始采集 while True: if ser.inWaiting() > 0: response = ser.readline().decode("utf-8").strip('\r\n') if response == '<': print("数据采集开始") break else: e += 1 time.sleep(0.1) if e > 100: print("未收到Arduino响应") is_collecting = False return # 持续读取数据直到结束 is_collecting = True e = 0 while is_collecting: try: if ser.inWaiting() > 0: response = ser.readline().decode("utf-8").strip('\r\n') if not response: continue if response == '>': print("数据采集完成") is_collecting = False break values = response.split(',') T = float(values[0]) / 1000 voltage = -(float(values[1]) + .012) current = (float(values[2]) + .015) print(f"Time: {T:.3f} Voltage: {voltage:.3f} Current: {current:.3f}", end='\r') # 写入CSV文件 with open(NAME, 'a', newline='') as file: writer = csv.writer(file) writer.writerow([T, voltage, current]) # 更新全局数据列表 Time.append(T) V.append(voltage) I.append(current) except IndexError: continue except ValueError: e += 1 if e > 1000: print("数据解析错误次数过多") is_collecting = False return except KeyboardInterrupt: ser.write("222".encode()) is_collecting = False return def update_plot(canvas1, canvas2, subplot1, subplot2): """定期更新绘图内容""" if is_collecting or len(Time) > 0: # 更新电压-电流散点图 subplot1.clear() subplot1.scatter(V, I, s=5) subplot1.set_title('Voltage vs Current') subplot1.set_xlabel('Voltage (V)') subplot1.set_ylabel('Current (mA)') canvas1.draw() # 更新时间-电压折线图 subplot2.clear() subplot2.plot(Time, V, linewidth=1) subplot2.set_title('Time vs Voltage') subplot2.set_xlabel('Time (sec)') subplot2.set_ylabel('Voltage (V)') canvas2.draw() # 每100ms更新一次,直到采集结束 if is_collecting: root.after(100, update_plot, canvas1, canvas2, subplot1, subplot2) def start_collection(): """启动数据采集和绘图流程""" global is_collecting, Time, V, I # 重置数据列表 Time.clear() V.clear() I.clear() try: # 获取用户输入参数 name = name_entry.get().strip() or "data" input1 = float(input1_entry.get()) input2 = float(input2_entry.get()) input3 = float(input3_entry.get()) input4 = float(input4_entry.get()) input5 = float(input5_entry.get()) input6 = float(input6_entry.get()) input7 = int(input7_entry.get()) # 发送参数到Arduino CV(name, input1, input2, input3, input4, input5, input6, input7) # 启动数据读取线程 threading.Thread(target=read_from_arduino, daemon=True).start() # 启动实时绘图更新 update_plot(canvas1, canvas2, subplot1, subplot2) except ValueError: print("请输入有效的数值参数") except Exception as e: print(f"启动失败: {e}") # 创建GUI窗口 root = tk.Tk() root.title("DIY_VERSION1") root.geometry('900x500') # 扩大窗口容纳图表 # 输入参数区域 name_label = tk.Label(root, text="Name: ") name_label.grid(row=0, column=0, padx=5, pady=5) name_entry = tk.Entry(root) name_entry.grid(row=0, column=1, padx=5, pady=5) input_labels = ["INPUT1", "INPUT2", "INPUT3", "INPUT4", "INPUT5", "INPUT6", "INPUT7"] input_entries = [] for i, label_text in enumerate(input_labels, start=1): label = tk.Label(root, text=label_text) label.grid(row=i, column=0, padx=5, pady=5) entry = tk.Entry(root) entry.grid(row=i, column=1, padx=5, pady=5) input_entries.append(entry) # 解包输入框变量 input1_entry, input2_entry, input3_entry, input4_entry, input5_entry, input6_entry, input7_entry = input_entries # 启动按钮 start_btn = tk.Button(root, text="START", command=start_collection, bg="#4CAF50", fg="white") start_btn.grid(row=8, column=0, columnspan=2, padx=5, pady=10) # 绘图区域 # 图表1:Voltage vs Current figure1 = Figure(figsize=(5, 3), dpi=100) subplot1 = figure1.add_subplot(111) subplot1.set_title('Voltage vs Current') subplot1.set_xlabel('Voltage (V)') subplot1.set_ylabel('Current (mA)') canvas1 = FigureCanvasTkAgg(figure1, root) canvas1.get_tk_widget().grid(row=0, column=2, rowspan=5, padx=10, pady=5) # 图表2:Time vs Voltage figure2 = Figure(figsize=(5, 3), dpi=100) subplot2 = figure2.add_subplot(111) subplot2.set_title('Time vs Voltage') subplot2.set_xlabel('Time (sec)') subplot2.set_ylabel('Voltage (V)') canvas2 = FigureCanvasTkAgg(figure2, root) canvas2.get_tk_widget().grid(row=5, column=2, rowspan=4, padx=10, pady=5) # 初始化串口连接 init_serial() # 启动GUI主循环 root.mainloop() # 程序结束时关闭串口 if ser and ser.is_open: ser.close()
内容的提问来源于stack exchange,提问作者Vikash Agarwal
相关产品推荐
相关产品推荐

