[英]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.