6

I'm using RPi.GPIO to handle events from a simple pushbutton. Pushing the button connects the associated GPIO pin (4) to ground. I would like to trigger an event when the button is released (e.g., when the value read from the GPIO pin goes from 0 to 1).

With the following code:

>>> import RPi.GPIO as GPIO
>>> GPIO.setmode(GPIO.BCM)
>>> GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP)
>>> def cb(*args):
...   print('callback was called')
... 
>>> GPIO.add_event_detect(4, edge=GPIO.RISING, callback=cb, bouncetime=200)

A single button push and release results in:

callback was called
callback was called

I can avoid getting multiple events by cranking up the value of bouncetime, but I'm still getting them when the button is pressed rather than when the button is released.

I guess this is expected behavior; I assume that the culprit is noise when the button is pushed so that a spurious logic change triggers the rising edge detection. I've tried to resolve this by adding logic to the callback to explicitly check the pin value...

def cb(pin):
    val = GPIO.input(pin)
    if val == 1:
        print('callback was called')

...but even with this logic in place I am still, albeit less frequently, triggering on button push instead of button release.

Is there anything else I can do to ensure that my code only reacts to the button release?

3 Answers 3

9

As @joan points out, the bouncetime parameter in RPi.GPIO does almost exactly the wrong thing: it means when your switch bounces, you'll get the very first event, but that may not actually be representative of the correct state of the switch.

While trying to come up with a more robust solution, I also realized that RPi.GPIO calls interrupt handlers synchronously. That is, if your code receives, for example, three interrupts in 1 second, and your interrupt handler takes 3 seconds to execute...it will still get called three times.

That means that to properly debounce a switch, your interrupt handler needs to return quickly if you don't want to artificially extend the time between events received by your handler.

With this in mind, I ended up with:

import RPi.GPIO as GPIO
import threading

class ButtonHandler(threading.Thread):
    def __init__(self, pin, func, edge='both', bouncetime=200):
        super().__init__(daemon=True)

        self.edge = edge
        self.func = func
        self.pin = pin
        self.bouncetime = float(bouncetime)/1000

        self.lastpinval = GPIO.input(self.pin)
        self.lock = threading.Lock()

    def __call__(self, *args):
        if not self.lock.acquire(blocking=False):
            return

        t = threading.Timer(self.bouncetime, self.read, args=args)
        t.start()

    def read(self, *args):
        pinval = GPIO.input(self.pin)

        if (
                ((pinval == 0 and self.lastpinval == 1) and
                 (self.edge in ['falling', 'both'])) or
                ((pinval == 1 and self.lastpinval == 0) and
                 (self.edge in ['rising', 'both']))
        ):
            self.func(*args)

        self.lastpinval = pinval
        self.lock.release()

You use it like this:

GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP)
cb = ButtonHandler(4, real_cb, edge='rising', bouncetime=100)
cb.start()
GPIO.add_event_detect(4, GPIO.RISING, callback=cb)

The way it works:

  • The actual interrupt handler is the __call__ method. When an interrupt is triggered, this method will acquire a lock and then spawn a timer that calls the read method in bouncetime milliseconds. If it can't acquire the lock (because it is already handling an event), it does nothing. In either case, the method returns very quickly.

  • The timer calls the read method after giving the switch time to settle. We read the pin value, and then compare it to the last known value of the button in order to determine if this is a rising or falling edge.

  • The read method preserves the state of the switch and releases the lock.

1
5

The RPi.GPIO module does not really debounce switches. The parameter is used to ignore all subsequent changes for the bouncetime after the first change is reported. A correct implementation would ignore all changes which were within bouncetime of each other (which would mean the first change would not be reported either).

You will have to implement your own logic if you wish to debounce switches.

3

you can debounce buttons in software as described above. If you have an oscilloscope you can find the settling time and wait to take your measurement of button state. However, this can seriously limit your ability to respond quickly in interrupt driven implementations. If you need a faster response it is possible to debounce in hardware using a simple RC circuit. There are many tutorials online about how to do this.

This link will explain both approaches to debouncing with oscilloscope traces to help illustrate the behavior.

1
  • This should have been accepted as an answer. A simple 0.1uF ceramic capacitor between the GPIO and ground (assuming your switch pulls the IO low when pressed) works great and is so much simpler than doing it in code. Great info, @Inflexionist! Thanks! Commented Jan 3, 2019 at 21:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.