简体   繁体   English

如何为步进电机系统实现带有 Tkinter 的停止按钮?

[英]How do I implement a stop button with Tkinter for a stepper motor system?

I have a question regarding the use of a stop button in Tkinter .我对Tkinter中停止按钮的使用有疑问。

For an experiment, I have to set up and X/Y stage that works by using two stepper motors.对于实验,我必须设置 X/Y 平台,该平台使用两个步进电机工作。 The arduino program works perfectly. arduino 程序完美运行。 The only problem is that when I activate the start function, which drives the stage to various coordinates, it freezes.唯一的问题是,当我激活 start function 时,它将舞台驱动到各种坐标,它冻结了。 Now the problem is that it has to run for weeks on end and it needs a stop button for emergencies and stopping the stepper motor in general.现在的问题是它必须连续运行数周,并且需要一个停止按钮以应对紧急情况并通常停止步进电机。 The stop button has to do two things: it has to stop the stepper driver motors, and it has to break the tkinter.after loop.停止按钮必须做两件事:它必须停止步进驱动电机,它必须中断tkinter.after循环。 However, due to the freezing, it is impossible to click on the button.但是,由于冻结,无法单击按钮。

Here is my code:这是我的代码:

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()

The after commands in the end will introduce pauses of 60 minutes, which would make the program freeze for 60 minutes.最后的 after 命令将引入 60 分钟的暂停,这将使程序冻结 60 分钟。 Hopefully there is an easy solution to interrupting the function!希望有一个简单的解决方案来中断该功能!

Thank you in advance!先感谢您!

You can make use of multithreading.您可以使用多线程。 Make all the communication in a separate thread and also make sure you don't update the GUI components in the child thread.在单独的线程中进行所有通信,并确保不在子线程中更新 GUI 组件。

Here is a minimal example:这是一个最小的例子:

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()

after(x000) is effectively the same as time.sleep(x) - it puts the whole app to sleep. after(x000)实际上与time.sleep(x)相同 - 它使整个应用程序进入睡眠状态。 As a general rule of thumb, you should never do this in the same thread as the GUI.作为一般经验法则,您永远不应在与 GUI 相同的线程中执行此操作。 That doesn't mean you need to use threads, however.然而,这并不意味着您需要使用线程。

tkinter's after method lets you schedule commands to run in the future. tkinter 的after方法可让您安排命令在未来运行。 If the commands you are running are fast such as sending a few bytes down a serial connection, this is really all you need.如果您正在运行的命令速度很快,例如通过串行连接发送几个字节,那么这就是您所需要的。 It is less complex and has less overhead than using threads.与使用线程相比,它更简单并且开销更少。

For example, your route function can probably be written something like this:比如你的route function大概可以这样写:

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)

Once you call this once, you don't need to call it again, and you don't need to call it in a thread.一旦你调用一次这个,你就不需要再次调用它,你也不需要在线程中调用它。 It simply places some work on a queue that gets picked up some time in the future.它只是将一些工作放在一个队列中,在未来的某个时间被拾取。

Since each call to root.after returns an id, you can save these ids so that in the case of wanting to stop everything, you can call after_cancel on each saved id.由于每次调用root.after返回一个 id,您可以保存这些 id,以便在想要停止一切的情况下,您可以对每个保存的 id 调用after_cancel

Another way is to define a job as a series of delays and then bytes to write.另一种方法是将作业定义为一系列延迟,然后是要写入的字节。 For example:例如:

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

Then, your route function can look something like this (untested, but this is pretty close)然后,您的route function 可能看起来像这样(未经测试,但这非常接近)

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)

There are many variations of that theme.该主题有很多变体。 For example, you could create a Job class that implements this logic, or the job could contain the commands rather than the data.例如,您可以创建一个实现此逻辑的Job class,或者job可以包含命令而不是数据。 The point being, you can define a data structure that defines work to be done, and then use after to schedule that work.关键是,您可以定义一个数据结构来定义要完成的工作,然后使用after来安排该工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM