3

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?

5

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.

  • May I ask what is "global name 'real_cb' is not defined" when I just copy and pasted the above mentioned program? – user94302 Nov 22 '18 at 17:55
  • That is whichever function you actually want to handle the callback. – larsks Nov 23 '18 at 2:29
3

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.

  • 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! – Matt Redmond Jan 3 at 21:14

protected by goldilocks Jan 3 at 21:21

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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