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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:10:27