Debouncing with IRQ

Questions and discussion about The WiPy 1.0 board and CC3200 boards.
Target audience: Users with a WiPy 1.0 or CC3200 board.
User avatar
marcelstoer
Posts: 10
Joined: Sun Nov 08, 2015 9:42 pm
Contact:

Debouncing with IRQ

Post by marcelstoer » Fri May 20, 2016 7:10 pm

I finally sat down and started tinkering with the WiPY I helped fund on Kickstarter last year. (Micro)Python is all new to me.

There's documented debounce function for the pyboard that can easily be adapted for use on the WiPy. http://www.eng.utah.edu/~cs5780/debouncing.pdf#page=19 isn't really fond of that counting algorithm but for starters I'm ok with that.

However, the 'while True: wait' section bothers me a little and I want to do that with an IRQ instead. So, I use something like this

Code: Select all

def toggle_led(pin):
    # counting algorithm here
    ..
    while active < 20:
    ...
    led.toggle()

pin_int.irq(trigger=Pin.IRQ_RISING, handler=toggle_led)
Question 1: that 'while active < 20' loop has terminated once the LED is toggled, right? It isn't somehow continuing in the background?
Question 2: I don't like it that debounce is tied to the actual interrupt action (toggling the LED), how can this be fixed? It'd be nice to have a generic debounce that executes an action when done, something like 'debounce(pin, toggle_led)'.
Last edited by marcelstoer on Fri May 20, 2016 10:22 pm, edited 1 time in total.

User avatar
dhylands
Posts: 3118
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Debouncing with IRQ

Post by dhylands » Fri May 20, 2016 10:12 pm

To debounce with an IRQ, the normal flow would go something like this:

1 - Setup pin to generate IRQ on an approriate edge
2 - In IRQ handler, disable the IRQ and start a debounce timer
3 - When the timer expires, examine the state of the pin, re-enable the edge IRQ and then do whatever callbacks are required.

You want to disable the IRQ because one switch transition will often generate multiple edges.

The disadvantge of the IRQ method is that the callback is executed in IRQ context.

I often have a main loop that runs on regular intervals (i.e. every 10 msec) so I tend to use the count method. The counting method could introduce some latency (up to 10 msec if you're running in realtime, more if you're not), but I've never really needed the "instant" reaction that requires an interrupt to be used. I'm happy to implement it when needed, but it adds considerable complexity, which means more potential failure points in your code, etc.

User avatar
marcelstoer
Posts: 10
Joined: Sun Nov 08, 2015 9:42 pm
Contact:

Re: Debouncing with IRQ

Post by marcelstoer » Fri May 20, 2016 10:30 pm

I appreciate your feedback, thanks. So far "IoT" primarily meant ESP8266+NodeMCU for me. So your world doesn't feel familiar yet :P

Isn't (one of) the issue with that main loop that prototyping in the REPL is a dead end? You can't edit anything anymore once the loop has been set in motion.

User avatar
dhylands
Posts: 3118
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Debouncing with IRQ

Post by dhylands » Fri May 20, 2016 11:44 pm

Right - but you just hit Control-C to break out of the program and that puts you back in the REPL. You're either running your program or using the REPL, not both at the same time (at least not yet).

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: Debouncing with IRQ

Post by on4aa » Sun Nov 26, 2017 2:53 am

dhylands wrote:
Fri May 20, 2016 10:12 pm
To debounce with an IRQ, the normal flow would go something like this:

1 - Setup pin to generate IRQ on an approriate edge
2 - In IRQ handler, disable the IRQ and start a debounce timer
3 - When the timer expires, examine the state of the pin, re-enable the edge IRQ and then do whatever callbacks are required.
I had a similar bounce problem with the USR switch on the Pyboard.
The procedure described by dhylands inspired me to write the following solution:

Code: Select all

import pyb

def toggle_led():
    pyb.disable_irq()
    pyb.delay(100)
    if sw.value(): pyb.LED(3).toggle()
    pyb.enable_irq()

sw = pyb.Switch()
sw.callback(toggle_led)
A slight difference is that I reenable the IRQ only at the very end.
Last edited by on4aa on Sun Nov 26, 2017 10:21 pm, edited 1 time in total.

SpotlightKid
Posts: 377
Joined: Wed Apr 08, 2015 5:19 am

Re: Debouncing with IRQ

Post by SpotlightKid » Sun Nov 26, 2017 9:25 pm

Here's the method suggested by @dhylands packed neatly into a class, with the additional use of micropython.schedule() to run the switch callback outside of the interrupt context.

Code: Select all

import micropython

try:
    from machine import Timer
    timer_init = lambda t, p, cb: t.init(period=p, callback=cb)
except ImportError:
    from pyb import Timer
    timer_init = lambda t, p, cb: t.init(freq=1000 // p, callback=cb)

# uncomment when debugging callback problems
#micropython.alloc_emergency_exception_buf(100)


class DebouncedSwitch:
    def __init__(self, sw, cb, arg=None, delay=50, tid=4):
        self.sw = sw
        # Create references to bound methods beforehand
        # http://docs.micropython.org/en/latest/pyboard/library/micropython.html#micropython.schedule
        self._sw_cb = self.sw_cb
        self._tim_cb = self.tim_cb
        self._set_cb = getattr(self.sw, 'callback', None) or self.sw.irq
        self.delay = delay
        self.tim = Timer(tid)
        self.callback(cb, arg)

    def sw_cb(self, pin=None):
        self._set_cb(None)
        timer_init(self.tim, self.delay, self._tim_cb)

    def tim_cb(self, tim):
        tim.deinit()
        if self.sw():
            micropython.schedule(self.cb, self.arg)
        self._set_cb(self._sw_cb if self.cb else None)

    def callback(self, cb, arg=None):
        self.tim.deinit()
        self.cb = cb
        self.arg = arg
        self._set_cb(self._sw_cb if cb else None)


def test_pyb(ledno=1):
    import pyb
    sw = pyb.Switch()
    led = pyb.LED(ledno)
    return DebouncedSwitch(sw, lambda l: l.toggle(), led)


def test_machine(swpin=4, ledpin=16):
    from machine import Pin
    sw = Pin(swpin, Pin.IN)
    led = Pin(ledpin, Pin.OUT)
    return DebouncedSwitch(sw, lambda l: l.value(not l.value()), led)
See also here: https://gist.github.com/SpotlightKid/0a ... 116423c94f

SpotlightKid
Posts: 377
Joined: Wed Apr 08, 2015 5:19 am

Re: Debouncing with IRQ

Post by SpotlightKid » Mon Nov 27, 2017 12:37 am

I've updated the code above to work with either the pyb or the machine module, and tested that it works correctly on a stm32 and esp8622 board.

User avatar
pythoncoder
Posts: 3324
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Debouncing with IRQ

Post by pythoncoder » Mon Nov 27, 2017 5:48 am

To add to the comments of @dhylands above, another way to avoid interrupts is to use uasyncio. This comes into its own if you have a number of switches, or if you want to detect events like pushbutton double-clicks and long presses.

This article (originally pointed out by @dhylands) is well worth reading for anyone interested in the general topic of switch bounce http://www.ganssle.com/debouncing.htm.
Peter Hinch

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: Debouncing with IRQ

Post by on4aa » Wed Dec 20, 2017 3:27 pm

Thanks for uasyncio, Peter. I am going to delve into this module pretty soon; also for other purposes.

For the readership, here is a link to the buttons example in Peter's excellent uasyncio tutorial.
Serge

dubaleeiro
Posts: 6
Joined: Mon Feb 12, 2018 9:52 pm

Re: Debouncing with IRQ

Post by dubaleeiro » Mon Feb 12, 2018 9:56 pm

Hello @SpotlightKid
could you please show me how to implement your code to debounce an interrupt button set to Pin23 for example? I am pretty new at Python, as you could probably notice :)
Tks!

Post Reply