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

使用Serial模块接收Arduino数据时Tkinter界面卡顿问题求助

Arduino数据在Tkinter Canvas展示卡顿问题修复

我让Arduino以100ms的间隔循环发送数据,尝试通过Tkinter的Canvas组件接收并展示,但出现数据卡顿。已经尝试用Queue和Thread解决,问题仍存在。程序通过“Enter”按钮获取用户输入的端口号,传入fetch_data函数,相关代码如下:

from queue import Queue
from threading import Thread
import serial
import tkinter as tk

queue = Queue()
port_no = 0


def fetch_data(queue):
    global port_no
    while True:
        data = serial.Serial(port=F"com{port_no}", baudrate=250000)
        while data.in_waiting == 0:
            pass
        data = data.readline()
        data = str(data, "UTF-8")
        queue.put(data)


# function for after loop
def display_data():

    data = queue.get()
    my_canvas.itemconfig(my_text, text=f"{data}")
    window.after(100, display_data)


def get_data():
    global port_no
    port_no = comp_entry.get()
    Thread(target=fetch_data, args=(queue,), daemon=True).start()


window = tk.Tk()
window.title("ISS Position")
window.config(padx=50, pady=50)
window.minsize(300, 300)

my_canvas = tk.Canvas(height=300, width=300, highlightthickness=0)
my_canvas.grid(column=0, row=0, padx=10, columnspan=2)
my_text = my_canvas.create_text(150, 150, text="Arduino data", font=("arial", 16, "bold"))

comp_entry = tk.Entry(width=6, font=("arial", 16, "bold"))
comp_entry.grid(column=0, row=1, pady=10)

comp_text = tk.Label(text="Enter Comp port", font=("arial", 16, "bold"))
comp_text.grid(column=1, row=1)

start_button = tk.Button(text="Start", command=display_data, font=("arial", 16, "bold"))
start_button.grid(column=0, row=2, pady=10)

connect_button = tk.Button(text="Enter", command=get_data, font=("arial", 16, "bold"))
connect_button.grid(column=1, row=2)

window.mainloop()

问题分析

  • 串口频繁初始化:fetch_data的循环中每次都重新创建Serial对象,反复打开关闭串口会导致数据读取中断、延迟,直接引发卡顿。
  • UI线程阻塞:display_data里用queue.get()是阻塞调用,队列空时UI线程会被卡住,无法响应其他操作。
  • 全局变量风险:port_no作为全局变量,多线程环境下可能被意外修改,代码耦合性高。
  • 缓冲区残留数据:首次打开串口时,缓冲区可能有旧数据,导致初始读取内容不准确。

修复后代码

from queue import Queue, Empty
from threading import Thread
import serial
import tkinter as tk

queue = Queue()


def fetch_data(queue, port):
    # 仅初始化一次串口
    try:
        ser = serial.Serial(port=f"com{port}", baudrate=250000, timeout=0.1)
        ser.flushInput()  # 清空缓冲区旧数据
        while True:
            data = ser.readline()
            if data:
                data_str = data.decode("UTF-8").strip()  # 去除换行和多余空格
                queue.put(data_str)
    except serial.SerialException as e:
        print(f"串口错误: {e}")
    finally:
        if 'ser' in locals() and ser.is_open:
            ser.close()


def display_data():
    # 非阻塞读取队列,处理所有积压数据只保留最新值
    try:
        latest_data = None
        while not queue.empty():
            latest_data = queue.get_nowait()
        if latest_data:
            my_canvas.itemconfig(my_text, text=latest_data)
    except Empty:
        pass  # 队列为空时不更新,避免覆盖现有内容
    window.after(50, display_data)  # 缩短更新间隔,提升响应速度


def get_data():
    port = comp_entry.get().strip()
    if port:
        Thread(target=fetch_data, args=(queue, port), daemon=True).start()
        connect_button.config(state=tk.DISABLED)  # 防止重复点击创建线程


window = tk.Tk()
window.title("ISS Position")
window.config(padx=50, pady=50)
window.minsize(300, 300)

my_canvas = tk.Canvas(height=300, width=300, highlightthickness=0)
my_canvas.grid(column=0, row=0, padx=10, columnspan=2)
my_text = my_canvas.create_text(150, 150, text="Arduino data", font=("arial", 16, "bold"))

comp_entry = tk.Entry(width=6, font=("arial", 16, "bold"))
comp_entry.grid(column=0, row=1, pady=10)

comp_text = tk.Label(text="Enter Comp port", font=("arial", 16, "bold"))
comp_text.grid(column=1, row=1)

start_button = tk.Button(text="Start", command=display_data, font=("arial", 16, "bold"))
start_button.grid(column=0, row=2, pady=10)

connect_button = tk.Button(text="Enter", command=get_data, font=("arial", 16, "bold"))
connect_button.grid(column=1, row=2)

window.mainloop()

关键修改说明

  1. 串口初始化优化:将Serial对象创建移到外层,仅初始化一次,避免反复开关串口。
  2. 非阻塞队列读取:用queue.get_nowait()配合异常捕获,避免UI线程阻塞;同时处理队列所有积压数据,只展示最新值,防止延迟。
  3. 移除全局变量:直接将端口号作为参数传递给fetch_data,避免线程安全问题。
  4. 缓冲区处理:初始化串口后清空缓冲区,确保读取最新数据。
  5. 按钮状态控制:点击Enter后禁用按钮,防止重复创建线程导致串口冲突。
  6. 缩短UI更新间隔:将after(100)改为after(50),提升界面响应速度,匹配Arduino的发送频率。

内容的提问来源于stack exchange,提问作者abhishek pandit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:43:13