【问题标题】:How do I implement a stop button with Tkinter for a stepper motor system?如何使用 Tkinter 为步进电机系统实现停止按钮?
【发布时间】:2021-10-01 14:23:04
【问题描述】:

我有一个关于在Tkinter 中使用停止按钮的问题。

对于一个实验,我必须设置一个使用两个步进电机工作的 X/Y 平台。 arduino 程序完美运行。唯一的问题是,当我激活将舞台驱动到各种坐标的启动功能时,它会冻结。现在的问题是它必须连续运行数周,并且需要一个停止按钮来应对紧急情况并停止步进电机。停止按钮必须做两件事:它必须停止步进驱动电机,它必须打破tkinter.after 循环。但是,由于冻结,无法点击按钮。

这是我的代码:

import tkinter as tk
import serial

ser = serial.Serial('COM5', 115200)

running = False

def quit():
    """Function that closes the serial port and destroys the root of the GUI"""
    global root
    ser.close()
    root.destroy()
    
def route():
    """Writes coordinates to the arduino, which in return drives the stepper motors"""
    if running == True:
        # The g line stands for go to!
        ser.write(b'g115000\r\n')
        root.after(50)
        ser.write(b'g225000\r\n')
        root.after(30000)
        ser.write(b'g1400\r\n')
        root.after(50)
        ser.write(b'g2500\r\n')
        
    root.after(12000,route())

    
def zeroing():
    """Zeros the program, this is necessary for the stage to 
    calibrate it's boundary conditions"""
    #zeros the stage so that it is ready to use!
    varLabel.set("zeroing, please move away from the stage")
    #the z command zeros the motors for boundary business
    ser.write(b'z\r\n')
    
def run_program():
    """Runs the function Route and sets running to True (not a good start/stop system)"""
    #starts the program, but only after you zero the stage
    global running
    running = True
    varLabel.set("Program running")
    route()

def stop_program():
    """Sets the running flag to False and sends a stop command to the arduino"""
    #stops the program immediately
    global running
    running = False
    varLabel.set("Program stopped,please zero before continuing")
    #the s byte is a command that stops the stepper motors
    ser.write(b's\r\n')
    

if __name__== "__main__":
    root = tk.Tk()

    canvas1 = tk.Canvas(root, width=800, height=400)
    canvas1.pack()

    root.title('XY-stage controller')

    #instructions
    instructions = tk.Label(root,text='Enter the amount of hours you want your measurements to last in the text box.'
                            '\n Click on run program to start a measurement session.'
                            '\n Click on stop incase of an emergency or if it is wanted to stop the program.',
                            font = "Raleway")
                        
    instructions.pack(side='bottom')

    # initialize active labels
    varLabel = tk.IntVar()
    tkLabel = tk.Label(textvariable=varLabel,)
    tkLabel.pack(side='top')


    # Buttons for initializing a bunch of good functions

    zerobutton = tk.IntVar()
    tkrunprogram= tk.Button(
        root,
        text='Zero', 
        command = zeroing,
        height = 4,
        fg = "black",
        width = 10,
        bg = 'gray',
        bd = 5,
        activebackground = 'green'
        )
    tkrunprogram.pack(side='top')

    runprogbutton = tk.IntVar()
    tkrunprogram= tk.Button(
        root,
        text='Run Program', 
        command = run_program,
        height = 4,
        fg = "black",
        width = 10,
        bg = 'gray',
        bd = 5,
        activebackground = 'green'
        )
    tkrunprogram.pack(side='top')
    
    stopbutton = tk.IntVar()
    tkstopprog= tk.Button(
        root,
        text='Stop Program', 
        command = stop_program,
        height = 4,
        fg = "black",
        width = 10,
        bg = 'gray',
        bd = 5,
        activebackground = 'red'
        )
    tkstopprog.pack(side='top')

    Buttonquit = tk.IntVar()
    tkButtonQuit = tk.Button(
        root,
        text='Quit', 
        command = quit,
        height = 4,
        fg = "black",
        width = 10,
        bg = 'yellow',
        bd = 5
        )

    # initialize an entry box
    entry1 = tk.Entry(root)
    durbox = canvas1.create_window(400, 200, window=entry1)
    tkButtonQuit.pack(side='top')
    
    root.mainloop()

最后的 after 命令将引入 60 分钟的暂停,这将使程序冻结 60 分钟。希望有一个简单的解决方案来中断该功能!

提前谢谢你!

【问题讨论】:

    标签: python tkinter pyserial motordriver


    【解决方案1】:

    您可以使用多线程。在单独的线程中进行所有通信,并确保不要在子线程中更新 GUI 组件。

    这是一个最小的例子:

    import serial
    import tkinter as tk
    from threading import Thread
    import time
    
    
    def start():
        global running
        stop()
        btn.config(text="Stop", command=stop)
        running = True
        info_label["text"] = "Starting..."
    
        thread = Thread(target=run, daemon=True)
        thread.start()
    
    def run():
        ser = serial.Serial("COM5", 115200, timeout=2)
    
        while running:
            ser.write(b'g115000\r\n')
            time.sleep(50)
            ser.write(b'g225000\r\n')
            time.sleep(30000)
            ser.write(b'g1400\r\n')
            time.sleep(50)
            ser.write(b'g2500\r\n')
        
        ser.write(b's\r\n')
        ser.close()
    
    def stop():
        global running
        running = False
        info_label["text"] = "Stopped"
        btn.config(text="Start", command=start)
    
    
    root = tk.Tk()
    running = False
    
    info_label = tk.Label(root, text="INFO:")
    info_label.pack()
    
    btn = tk.Button(root, text="Start", command=start)
    btn.pack()
    
    root.mainloop()
    

    【讨论】:

    • 谢谢!如果 Bryan 的方法不起作用,那么我一定会尝试线程!
    • 这实际上工作得很好,当然有一些变化!非常感谢!
    【解决方案2】:

    after(x000) 实际上与time.sleep(x) 相同 - 它使整个应用程序进入睡眠状态。作为一般经验法则,您永远不应该在与 GUI 相同的线程中执行此操作。然而,这并不意味着您需要使用线程。

    tkinter 的after 方法让您可以安排命令在未来运行。如果您正在运行的命令很快,例如通过串行连接发送几个字节,那么这确实是您所需要的。与使用线程相比,它更简单,开销也更少。

    例如,您的route 函数可能可以这样写:

    def route():
        if running == True:
            # immediately write this:
            ser.write(b'g115000\r\n')
    
            # after 50ms, write this:
            root.after(50, ser.write, b'g225000')
     
            # after 30 more seconds, write this
            root.after(50+30000, ser.write, b'g1400\r\n')
    
            # and then after 50ms more, write this
            root.after(50+30000+50, ser.write, b'g2500\r\n')
    
            # and finally, after 12 seconds, do it all again
            root.after(50+30000+50+12000,route)
    

    一旦你调用了这个,你就不需要再调用它,你也不需要在一个线程中调用它。它只是将一些工作放在一个队列中,这些工作将在未来的某个时间得到处理。

    由于每次调用root.after都会返回一个id,你可以保存这些id,以便在想要停止一切的情况下,你可以在每个保存的id上调用after_cancel

    另一种方法是将作业定义为一系列延迟,然后是要写入的字节。例如:

    job = (
        (0, b'g115000\r\n'),
        (50, b'g225000'),
        (30000, b'g1400\r\n'),
        (50, b'g2500\r\n'),
    )
    

    然后,您的 route 函数可能看起来像这样(未经测试,但这非常接近)

    def route(job):
        global after_id
        delay = 0
        for (delta, data) in job:
            delay += delta
            root.after(delay, ser.write, data)
        delay += 12000
        root.after(delay, route, job)
    

    该主题有很多变体。例如,您可以创建一个实现此逻辑的Job 类,或者job 可以包含命令而不是数据。重点是,您可以定义一个数据结构来定义要完成的工作,然后使用after 来安排该工作。

    【讨论】:

    • 非常感谢!我会试一试:)
    猜你喜欢
    • 2017-01-26
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 2021-06-16
    • 2011-11-19
    • 2020-05-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多