Pin IRQ Debounce Messing with REPL

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
Duramaximizer
Posts: 12
Joined: Wed Mar 25, 2020 8:53 pm

Pin IRQ Debounce Messing with REPL

Post by Duramaximizer » Wed Mar 25, 2020 9:19 pm

Hello,

I have a push button on my board and have written a debounce routine to debounce the button press. The debounce routine is working great. I have a print statement in the debounce routine to show the button debounce has happened and it is working as expected.

When I enable the interrupt and press the button, the callback function works as expected (print statement shows), however the REPL is no longer working. I will type some characters and see nothing on the terminal, then when I press the push button again, the characters I typed show up but again the REPL is not responsive. I am able to reset the board using Ctrl + D, enter and then press the push button. Hitting Ctrl + C does not bring the REPL out of this stuck state.

I have tried disabling and re-enabling the interrupts in the debounce function but the same problem exists.

Any ideas?

User avatar
jimmo
Posts: 1685
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Pin IRQ Debounce Messing with REPL

Post by jimmo » Thu Mar 26, 2020 1:09 am

Hi,

Could you please post your code (or at least a minimal example that shows what you're describing)? Otherwise we'll be just guessing.

But...my guess is that maybe you have some delays or loops in in your interrupt handler?

Duramaximizer
Posts: 12
Joined: Wed Mar 25, 2020 8:53 pm

Re: Pin IRQ Debounce Messing with REPL

Post by Duramaximizer » Thu Mar 26, 2020 1:36 am

Thanks for replying!

I do have some loops. The debounce routine appends switch.value() to a buffer until it is equal to another buffer of all 1's. Delaying 1 ms between switch.value() reads. I then do the same thing but looking for 0's to know the button has been pressed and released. I do limit the buffer size to a maximum.

I had another routine I was using but I was experiencing multiple callbacks and still lots of bounce even with long delays. This one used a simple time delay and checked to see if the button value remained the same after the delay.

User avatar
jimmo
Posts: 1685
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Pin IRQ Debounce Messing with REPL

Post by jimmo » Thu Mar 26, 2020 3:28 am

Unfortunately you just can't do delays or "waiting" in an interrupt handler.

Instead I would recommend making a timer that goes off every few milliseconds. Inside the timer callback, add one to a global variable if the button is down, otherwise set it to zero.

At the point it reaches some threshold (i.e. "if counter == 5:" then that's a button press.

Duramaximizer
Posts: 12
Joined: Wed Mar 25, 2020 8:53 pm

Re: Pin IRQ Debounce Messing with REPL

Post by Duramaximizer » Thu Mar 26, 2020 1:38 pm

Thank you! I should be able to make that work like my debounce routine. Just need to adapt it to check for a series of 1's and then a release for a series of 0's before exiting.

Make a 1 ms timer and the callback will check the button value and add 1 to a global counter. Where do I run this timer? In the Pin interrupt routine?

I'm having a hard time figuring out the structuring to this.

User avatar
jimmo
Posts: 1685
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Pin IRQ Debounce Messing with REPL

Post by jimmo » Thu Mar 26, 2020 11:41 pm

Duramaximizer wrote:
Thu Mar 26, 2020 1:38 pm
Make a 1 ms timer and the callback will check the button value and add 1 to a global counter. Where do I run this timer? In the Pin interrupt routine?
No, in a new interrupt routine driven by a timer. On Pyboard you can use machine.Timer(-1) to easily create a "soft" timer. You don't need a pin interrupt handler.

Something like this: (untested)

Code: Select all

_DEBOUNCE_INTERVAL = const(5)
sw_counter = 0
def _debounce_timer(t):
  global sw_counter
  if sw.value():
    if sw_counter <= _DEBOUNCE_INTERVAL:
      sw_counter += 1
  else:
    sw_counter = 0
  if sw_counter == _DEBOUNCE_INTERVAL:
    button_down()
    
def button_down():
  # your code here
    
t = machine.Timer(-1, mode=machine.Timer.PERIODIC, period=10, callback=_debounce_timer)
You don't need to record the sequence, the counter idea achieves the same thing -- you're counting the number of ones (or zeros) in a row.

If you want to detect debounced presses and releases, then you can use two counters (one that counts "pressed" and resets when "released", and the other that counts "released" and resets when "pressed").

Edit: fixed a "dodgy typo" :p

Duramaximizer
Posts: 12
Joined: Wed Mar 25, 2020 8:53 pm

Re: Pin IRQ Debounce Messing with REPL

Post by Duramaximizer » Fri Mar 27, 2020 12:19 am

jimmo wrote:
Thu Mar 26, 2020 11:41 pm
Duramaximizer wrote:
Thu Mar 26, 2020 1:38 pm
Make a 1 ms timer and the callback will check the button value and add 1 to a global counter. Where do I run this timer? In the Pin interrupt routine?
No, in a new interrupt routine driven by a timer. On Pyboard you can use machine.Timer(-1) to easily create a "soft" timer. You don't need a pin interrupt handler.

Something like this: (untested)

Code: Select all

_DEBOUNCE_INTERVAL = const(5)
sw_counter = 0
def _debounce_timer(t):
  global sw_counter
  if sw.value():
    if sw_counter <= _DEBOUNCE_INTERVAL:
      sw_counter += 1
  else:
    sw_cunter = 0
  if sw_counter == _DEBOUNCE_INTERVAL:
    button_down()
    
def button_down():
  # your code here
    
t = machine.Timer(-1, mode=machine.Timer.PERIODIC, period=10, callback=_debounce_timer)
You don't need to record the sequence, the counter idea achieves the same thing -- you're counting the number of ones (or zeros) in a row.

If you want to detect debounced presses and releases, then you can use two counters (one that counts "pressed" and resets when "released", and the other that counts "released" and resets when "pressed").
Interesting! This is what I've came up with using a rising edge pin.irq initialized before this which calls the debounce_handler, which in turn calls the callback function, simply machine.deepsleep().

I am still getting some weird behavior. When the board is running it does not like to go to sleep, sometimes I'll need to press the button several times even though it is being debounced.

Code: Select all

    def debounce_handler(self, pin):
        global count
        global decount
        if pin.value():  #switch needs to be high
            count += 1
        if count >= 5:
            count = 5  # limit count to 5
        if count == 5 and not pin.value(): # button has been constant for 5 calls, now needs to be low
            decount = decount - 1
        if count == 5 and decount == 0:
            count = 0
            decount = 5
            self.call_callback(pin)
I'm trying to wait for the user to press the button, release the button and then do the interrupt.

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

Re: Pin IRQ Debounce Messing with REPL

Post by pythoncoder » Fri Mar 27, 2020 7:38 am

@jimmo Your algorithm should work (give or take a dodgy typo ;)) but it imposes a delay between the button being pushed and the callback occurring. This is unnecessary. You can run the callback immediately, with the timer preventing it from being called again until the bounce period has elapsed. This latency matters, for example in games.

@Duramaximizer I suggest you look at solutions based on uasyncio. Interrupts are overkill for a simple task like debouncing which doesn't need μs precision. See the Switch and Pushbutton classes here.
Peter Hinch

User avatar
jimmo
Posts: 1685
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Pin IRQ Debounce Messing with REPL

Post by jimmo » Fri Mar 27, 2020 11:11 am

@pythoncoder, thanks :) Fixed the typo! Yes you're right about the latency too. So for the OP, yes the other approach is to instead think of it as rate limiting the switch transitions... there are lots of ways to implement this (and you can adapt the above solution), depending on what sort of behavior you want.

Yes, seconded that asyncio is definitely a good idea here, and hopefully it'll flow through into making the rest of your program easier to write also!

Duramaximizer
Posts: 12
Joined: Wed Mar 25, 2020 8:53 pm

Re: Pin IRQ Debounce Messing with REPL

Post by Duramaximizer » Fri Mar 27, 2020 7:13 pm

Thanks for the replies.

Unfortunately I don't have access to uasyncio on this board at the moment and would need my linux box to get it on.

I tried jimmo's code. The debounce works flawlessly however when I use it for my callback I'm running into issues. I'm starting to think there's a problem with putting the board to sleep.

I have the button on pin A0. This is the only way to wake up the micro in deepsleep (rising edge).

With the board running, I hit the button and the callback is simply just machine.deepsleep(). The board instantly wakes back up! I feel like I'm missing something here. I thought it was an issue with button bounce but now I'm thinking it's something else. I have a pin interrupt setup on rising edge to wake the board up out of deepsleep. This works well, maybe this wakeup interrupt is getting triggered with my go to sleep debounce button press?

Post Reply