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

