When you attach hardware buttons to a Raspberry Pi's GPIO pin,
reading the button's value at any given instant is easy with
GPIO.input(). But what if you want to watch for
button changes? And how do you do that from a GUI program where
the main loop is buried in some library?
Here are some examples of ways to read buttons from a Pi. For this example, I have one side of my button wired to the Raspberry Pi's GPIO 18 and the other side wired to the Pi's 3.3v pin. I'll use the Pi's internal pulldown resistor rather than adding external resistors.
The simplest way: Polling
The obvious way to monitor a button is in a loop, checking the button's value each time:
import RPi.GPIO as GPIO import time button_pin = 18 GPIO.setmode(GPIO.BCM) GPIO.setup(button_pin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN) try: while True: if GPIO.input(button_pin): print("ON") else: print("OFF") time.sleep(1) except KeyboardInterrupt: print("Cleaning up") GPIO.cleanup()
But if you want to be doing something else while you're waiting, instead of just sleeping for a second, it's better to use edge detection.
will call you back whenever it sees the pin's value change.
I'll define a button_handler function that prints out
the value of the pin whenever it gets called:
import RPi.GPIO as GPIO import time def button_handler(pin): print("pin %s's value is %s" % (pin, GPIO.input(pin))) if __name__ == '__main__': button_pin = 18 GPIO.setmode(GPIO.BCM) GPIO.setup(button_pin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN) # events can be GPIO.RISING, GPIO.FALLING, or GPIO.BOTH GPIO.add_event_detect(button_pin, GPIO.BOTH, callback=button_handler, bouncetime=300) try: time.sleep(1000) except KeyboardInterrupt: GPIO.cleanup()
Pretty nifty. But if you try it, you'll probably find that sometimes the value is wrong. You release the switch but it says the value is 1 rather than 0. What's up?
Debounce and Delays
The problem seems to be in the way RPi.GPIO handles that
The bouncetime is there because hardware switches are noisy. As you
move the switch from ON to OFF, it doesn't go cleanly all at once
from 3.3 volts to 0 volts. Most switches will flicker back
and forth between the two values before settling down. To see bounce
in action, try the program above without the
There are ways of fixing bounce in hardware, by adding a capacitor or
a Schmitt trigger to the circuit; or you can "debounce" the button
in software, by waiting a while after you see a change before
acting on it. That's what the bouncetime parameter is for.
But apparently RPi.GPIO, when it handles bouncetime, doesn't always wait quite long enough before calling its event function. It sometimes calls button_handler while the switch is still bouncing, and the value you read might be the wrong one. Increasing bouncetime doesn't help. This seems to be a bug in the RPi.GPIO library.
You'll get more reliable results if you wait a little while before reading the pin's value:
def button_handler(pin): time.sleep(.01) # Wait a while for the pin to settle print("pin %s's value is %s" % (pin, GPIO.input(pin)))
Why .01 seconds? Because when I tried it, .001 wasn't enough, and if I used the full bounce time, .3 seconds (corresponding to 300 millisecond bouncetime), I found that the button handler sometimes got called multiple times with the wrong value. I wish I had a better answer for the right amount of time to wait.
Incidentally, the choice of 300 milliseconds for bouncetime is arbitrary and the best value depends on the circuit. You can play around with different values (after commenting out the .01-second sleep) and see how they work with your own circuit and switch.
You might think you could solve the problem by using two handlers:
GPIO.add_event_detect(button_pin, GPIO.RISING, callback=button_on, bouncetime=bouncetime) GPIO.add_event_detect(button_pin, GPIO.FALLING, callback=button_off, bouncetime=bouncetime)but that apparently isn't allowed:
RuntimeError: Conflicting edge detection already enabled for this GPIO channel.
Even if you look just for
still get some bogus calls, because there are both rising and falling
edges as the switch bounces. Detecting
a short time and checking the pin's value is the only reliable method
Edge Detection from a GUI Program
And now, the main inspiration for all of this: when you're running a program with a graphical user interface, you don't have control over the event loop. Fortunately, edge detection works fine from a GUI program. For instance, here's a simple TkInter program that monitors a button and shows its state.
import Tkinter from RPi import GPIO import time class ButtonWindow: def __init__(self, button_pin): self.tkroot = Tkinter.Tk() self.tkroot.geometry("100x60") self.label = Tkinter.Label(self.tkroot, text="????", bg="black", fg="white") self.label.pack(padx=5, pady=10, side=Tkinter.LEFT) self.button_pin = button_pin GPIO.setmode(GPIO.BCM) GPIO.setup(self.button_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(self.button_pin, GPIO.BOTH, callback=self.button_handler, bouncetime=300) def button_handler(self, channel): time.sleep(.01) if GPIO.input(channel): self.label.config(text="ON") self.label.configure(bg="red") else: self.label.config(text="OFF") self.label.configure(bg="blue") if __name__ == '__main__': win = ButtonWindow(18) win.tkroot.mainloop()
You can see slightly longer versions of these programs in my GitHub Pi Zero Book repository.
[ 11:32 Jan 21, 2018 More hardware | permalink to this entry | ]