如何在Tkinter中每200ms更新显示ModbusTCP读取的PLC模拟输入值
ModbusTCP + Tkinter 实时模拟值显示与数字输出控制修复方案
关键问题分析
get_analog函数中的while True+time.sleep会阻塞Tkinter主线程,导致GUI无法响应操作。- 未使用Tkinter专用变量(
StringVar/IntVar)绑定控件,普通变量无法触发GUI自动更新。 - 按钮命令绑定的
partial提前获取输入框值,点击时无法读取最新输入;脉冲函数中的time.sleep会阻塞主线程,导致GUI假死。 - 控件命名冲突(如
x_lvdt同时用于输入框和标签),导致控件被覆盖。 get_analog函数存在字符串拼接语法错误。
修正后的完整代码
import time from threading import Thread, Lock from pyModbusTCP.client import ModbusClient from tkinter import * # 初始化Modbus连接 c = ModbusClient(host='192.168.1.50', port=502, auto_open=True, debug=False) window = Tk() window.title('Stage Control') window.geometry("600x400+10+20") # 全局变量与锁 regs = [] regs_lock = Lock() # 用于绑定模拟值显示的Tkinter变量 analog_x_var = StringVar(value="0") analog_y_var = StringVar(value="0") def polling_thread(): """后台线程定期读取Modbus模拟输入""" global regs, regs_lock while True: reg_list = c.read_holding_registers(0, 2) # 读取两个寄存器对应X、Y轴模拟值 if reg_list: with regs_lock: regs = list(reg_list) time.sleep(1) # 启动后台读取线程 tp = Thread(target=polling_thread, daemon=True) tp.start() def update_analog_display(): """定期更新GUI上的模拟值显示""" with regs_lock: if len(regs) >= 1: analog_x_var.set(str(regs[0])) if len(regs) >= 2: analog_y_var.set(str(regs[1])) # 100ms后再次调用自身,实现循环更新 window.after(100, update_analog_display) def run_pulse(coil_num, duration): """线程执行脉冲输出,避免阻塞主线程""" try: duration = float(duration) c.write_single_coil(coil_num, True) time.sleep(duration) c.write_single_coil(coil_num, False) print(f"Coil {coil_num} pulse duration: {duration}s") except ValueError: print("输入的脉冲时长不是有效数字") # 数字输出控制函数 def xreverse(): Thread(target=run_pulse, args=(2, x_pulse_duration.get()), daemon=True).start() def x(): Thread(target=run_pulse, args=(2, x_pulse_duration.get()), daemon=True).start() def yreverse(): Thread(target=run_pulse, args=(0, y_pulse_duration.get()), daemon=True).start() def y(): Thread(target=run_pulse, args=(0, y_pulse_duration.get()), daemon=True).start() # 控件创建 # 输入框:脉冲时长 x_pulse_duration = Entry(window, width=3, font=('Arial 24')) x_pulse_duration.place(x=195, y=150) x_pulse_duration.insert(0, "0.5") # 设置默认值 y_pulse_duration = Entry(window, width=3, font=('Arial 24')) y_pulse_duration.place(x=335, y=150) y_pulse_duration.insert(0, "0.5") # 设置默认值 # 模拟值显示输入框(只读) x_lvdt_display = Entry(window, width=5, font=('Arial 16'), textvariable=analog_x_var, state="readonly") x_lvdt_display.place(x=190, y=250) y_lvdt_display = Entry(window, width=5, font=('Arial 16'), textvariable=analog_y_var, state="readonly") y_lvdt_display.place(x=330, y=250) # 按钮 move_x = Button(window, text="<", fg='blue', command=xreverse) move_x.place(x=180, y=200) move_x_reverse = Button(window, text=">", fg='blue', command=x) move_x_reverse.place(x=220, y=200) move_y = Button(window, text="<", fg='blue', command=yreverse) move_y.place(x=320, y=200) move_y_reverse = Button(window, text=">", fg='blue', command=y) move_y_reverse.place(x=360, y=200) # 标签 lbl = Label(window, text="pulse duration", fg='blue', font=("ARIAL", 10)) lbl.place(x=252, y=160) xaxis = Label(window, text="X-AXIS", fg='blue', font=("ARIAL", 24)) xaxis.place(x=175, y=95) yaxis = Label(window, text="Y-AXIS", fg='blue', font=("ARIAL", 24)) yaxis.place(x=325, y=95) x_lvdt_label = Label(window, text="X LVDT", fg='blue', font=("ARIAL", 16)) x_lvdt_label.place(x=189, y=270) y_lvdt_label = Label(window, text="Y LVDT", fg='blue', font=("ARIAL", 16)) y_lvdt_label.place(x=327, y=270) # 启动模拟值更新循环 update_analog_display() window.mainloop()
核心修改说明
- 保留后台读取线程:继续用独立线程读取Modbus寄存器,避免阻塞主线程。
- GUI更新改用
after机制:移除阻塞循环,用window.after定期触发更新函数,确保GUI响应性。 - Tkinter变量绑定:使用
StringVar绑定模拟值显示控件,值更新时自动刷新GUI。 - 脉冲输出线程化:将脉冲逻辑放到独立线程执行,避免
time.sleep导致GUI假死;按钮直接绑定函数,点击时实时读取输入框最新值。 - 修复控件命名冲突:区分模拟值显示控件和说明标签的变量名,避免覆盖。
- 添加默认值与错误处理:给输入框设置默认脉冲时长,捕获数字格式错误,提升易用性。
内容的提问来源于stack exchange,提问作者jeffg
相关产品推荐
相关产品推荐

