Reputation: 3
I'm creating a python program on my RPi3 that changes the frequency of a GPIO pin depending on a Tkinter scale.
Here is my code:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
from Tkinter import *
import time
freq = 1.0
master = Tk()
def update():
period = 1.0/float(freq)
GPIO.output(8, GPIO.HIGH)
time.sleep(period/2.0)
GPIO.output(8, GPIO.LOW)
time.sleep(period/2.0)
master.after(0, update)
scale = Scale(master, from_=1, to=20000, orient=HORIZONTAL, variable=freq)
scale.pack()
GPIO.setup(8, GPIO.OUT)
master.after(0, update)
master.mainloop()
GPIO.cleanup()
For some reason, master.after(0, update)
runs forever and master.mainloop()
never runs. I can tell because the scale never shows up and pin 8 turns on for half a second, then turns off for half a second, and the cycle repeats.
If I press Ctrl+C then master.after(0, update)
stops running and master.mainloop()
starts running, the scale appears, but nothing happens when I drag the slider left and right.
I ran the program by typing sudo python tone.py
in the terminal then pressing enter.
Fix/Alternative?
Upvotes: 0
Views: 374
Reputation: 385910
You are making two somewhat common mistakes: you shouldn't do after(0, ...)
, and you shouldn't call sleep
.
after(0, ...)
means that every time you process the event, you immediately add another event. The event loop never has a chance to process other events in the queue, including events to handle the slider, screen updates, etc.
When you call sleep
, the GUI does exactly that: it sleeps. While it is sleeping it can't process any events.
The solution is to use only after
with a reasonable time span, and not call sleep
at all.
For example:
def update():
...
# set the pin high
GPIO.output(8, GPIO.HIGH)
# set the pin low in half the period
master.after(period/2, GPIO.output, 8, GPIO.LOW)
# do this again based on the period
master.after(period, update)
Another way, if you continually want to toggle the pin every half second, would be this:
def update(value=GPIO.HIGH):
GPIO.output(8, value)
next_value = GPIO.LOW if value == GPIO.HIGH else GPIO.HIGH
master.after(500, update, next_value)
When you use the variable
attribute of the slider, the variable must be an instance of one of the special tkinter variables, such as IntVar
. You will then need to call get
or set
to get or set the value.
For example:
freq = IntVar()
freq.set(1)
def update(value=GPIO.HIGH):
period = 1.0/float(freq.get())
...
Upvotes: 1
Reputation: 1554
mainloop()
is running, but Tkinter
won't update views unless it is idle. KeyboardInterrupt
will tell it to cleanup, at which point it finishes its event queue (which includes updating interface) and exits.
Solution: Give mainloop time to be idle- you really just need to change your after(0, update)
to have some milliseconds inside OR tell master
to .update_idletasks()
to update the GUI.
A slightly better solution would be to make your high/low parts into their own functions which call each after the needed delay- sleep
ing in the mainloop gui is a bad idea, as your GUI cannot update whatsoever if the program is sleeping. (nor can it take input et al.) You would have millisecond frames to change input before it updated again, while using two functions that call each other after
the chosen milliseconds would let you adjust the timings et al while it's waiting to flip to the other on/off.
Upvotes: 0