簡體   English   中英

如何為步進電機系統實現帶有 Tkinter 的停止按鈕?

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

我對Tkinter中停止按鈕的使用有疑問。

對於實驗,我必須設置 X/Y 平台,該平台使用兩個步進電機工作。 arduino 程序完美運行。 唯一的問題是,當我激活 start function 時,它將舞台驅動到各種坐標,它凍結了。 現在的問題是它必須連續運行數周,並且需要一個停止按鈕以應對緊急情況並通常停止步進電機。 停止按鈕必須做兩件事:它必須停止步進驅動電機,它必須中斷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 分鍾。 希望有一個簡單的解決方案來中斷該功能!

先感謝您!

您可以使用多線程。 在單獨的線程中進行所有通信,並確保不在子線程中更新 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()

after(x000)實際上與time.sleep(x)相同 - 它使整個應用程序進入睡眠狀態。 作為一般經驗法則,您永遠不應在與 GUI 相同的線程中執行此操作。 然而,這並不意味着您需要使用線程。

tkinter 的after方法可讓您安排命令在未來運行。 如果您正在運行的命令速度很快,例如通過串行連接發送幾個字節,那么這就是您所需要的。 與使用線程相比,它更簡單並且開銷更少。

比如你的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)

一旦你調用一次這個,你就不需要再次調用它,你也不需要在線程中調用它。 它只是將一些工作放在一個隊列中,在未來的某個時間被拾取。

由於每次調用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 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)

該主題有很多變體。 例如,您可以創建一個實現此邏輯的Job class,或者job可以包含命令而不是數據。 關鍵是,您可以定義一個數據結構來定義要完成的工作,然后使用after來安排該工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM