Interrupt button debounce with long / short press
-
Been trying this on and off over the last few days using different methods and cannot come up with one that is 100% reliable, I sometimes get a double trigger despite trying to trap it in the loop.
Here's what I have now, but this has intermittent results both in terms of detecting long / short presses as well as double triggering:
butms = 0 butup = 0 def pin_int(pin): global butms, butup if butms == 0: butup = 0 butms = time.ticks_ms() else: for i in range(100): if(pin()==0): butup = 0 return butup +=1 time.sleep_ms(1) if(butup > 99): diff = time.ticks_diff(butms, time.ticks_ms()) if(diff > 1000): print('press > 1 second',diff) else: print('short press:',diff) butms = 0 p_in = machine.Pin('P12', mode=machine.Pin.IN, pull=machine.Pin.PULL_UP) p_in.callback(machine.Pin.IRQ_FALLING|machine.Pin.IRQ_RISING, pin_int)
When using microchip devices, I could disable specific interrupts and so never faced this problem, but as far as I know I can only disable / enable all interrupts ?!
Any suggestions or proven, better snippets ?
-
-
@livius
here are the BUTTON class code (slightly modified from your version, mainly to add comments)from time import sleep_ms, ticks_ms, ticks_diff from machine import Pin from _thread import start_new_thread import gc class BUTTON: def __init__(self, pid='P10', longms=1000): self.pressms = 0 self.longms = longms self.pin = Pin(pid, mode=Pin.IN, pull=Pin.PULL_UP) self.pin.callback(Pin.IRQ_FALLING | Pin.IRQ_RISING, self.press) def long(self): pass def short(self): pass def press(self, pin): # If never pressed, store press time if self.pressms == 0: self.pressms = ticks_ms() else: # If pressed within 500 ms of first press, discard (button bounce) if ticks_diff(self.pressms, ticks_ms()) < 500: return # Wait for value to stabilize for 10 ms i = 0 while i < 10: sleep_ms(1) if self.pin() == 1: i = 0 else: i+=1 # Measure button press duration while self.pin() == 0: i+=1 if(i > self.longms): break sleep_ms(1) # Trigger short or long press if(i > self.longms): start_new_thread(self.long, ()) else: start_new_thread(self.short, ()) # Wait for button release while self.pin() == 0: pass self.pressms = 0 gc.collect()
and here is the code I use to instantiate the different buttons
import button def short_red(): print('Short red button press') def long_red(): print('Long red button press') def short_yellow(): print('Short yellow button press') def long_yellow(): print('Long yellow button press') def short_black(): print('Short black button press') def long_black(): print('Long black button press') red_button = button.BUTTON(pid='P22',longms=500) red_button.short = short_red red_button.long = long_red yellow_button = button.BUTTON(pid='P21',longms=500) yellow_button.short = short_yellow yellow_button.long = long_yellow black_button = button.BUTTON(pid='P10',longms=500) black_button.short = short_black black_button.long = long_black
-
@gael-wogenstahl
can you show how you declare all?
Code for three buttons?
-
@crankshaft
Hello !
I've tried to use your class with 3 different buttons but it seems once a button interrupt is triggered, all other buttons interrupts don't work anymore and only the future interrupts from the first pressed button are triggered.
I've been searching a lot for an explanation but couldn't find any.
Did you experience that kind of behavior ? Do you have a clue as to where that can come from ?
Thanks for your help !
-
@livius - I am using internal pullup, I could also add a decoupling capacitor across the switch but I wanted to do it all in software instead.
-
In case anyone is interested I created a class:
from time import sleep_ms, ticks_ms from machine import Pin import gc class BUTTON: def __init__(self,pid='P12',longms=1000): self.longms = longms self.butms = 0 self.pin = Pin(pid, mode=Pin.IN, pull=Pin.PULL_UP) self.pin.callback(Pin.IRQ_FALLING | Pin.IRQ_RISING, self.press) def long(self): pass def short(self): pass def press(self,pin): now = ticks_ms() if self.butms == 0: self.butms = now else: if self.butms == now: return i = 0 while i < 10: sleep_ms(1) if self.pin() == 1: i = 0 else: i+=1 while self.pin() == 0: i+=1 if(i > self.longms): break sleep_ms(1) if(i>1000): self.long() else: self.short() while self.pin() == 0: pass gc.collect()
And to call it:
def short(): print('short') def long(): print('long') import button but = button.BUTTON() but.short = short but.long = long
-
I came up with another version that also works:
def long(count): print('Long Press',count) def short(count): print('Short Press',count) count = 0 butms = 0 def btn_press_detected(pin): global butms, count now = time.ticks_ms() if butms == 0: butms = now else: if butms == now: return i = 0 while i < 10: time.sleep_ms(1) if pin() == 1: i = 0 else: i+=1 while pin() == 0: i+=1 if(i > 1000): break time.sleep_ms(1) count +=1 ## debug check for double-trigger only if(i>1000): long(count) else: short(count)
-
@crankshaft
good to see that you finally solve it
but i have question - because i saw this behavior self when i have too big/too small pullup/pulldown.
Do you use internal pin pullup or pull down. Or do you use external resistor? If external - what value?EDIT
+1 for debounce option
add it to:
https://forum.pycom.io/topic/433/features-wish-list
-
@crankshaft
Happy to help and glad it's over.100% agreed on the comment, if the button is debounced, that's two less interrupt handlers required and at least some bytes freed from memory.
Cheers,
M.
-
@mohpor - it's one of those common, small through-hole tactile buttons, but anyway I have your code working now with what appears to be 100% reliability and no double presses:
I added a check for last value and ignored it if received:
last = 0 def btn_press_detected(btn): global chrono, timer, last try: val = btn.value() if 0 == val: chrono.reset() chrono.start() timer.callback(long_press_handler) else: timer.callback(None) chrono.stop() t = chrono.read_ms() if(t==last): return last = t if (t > 30) & (t < 200): single_press_handler() finally: gc.collect()
Thanks so much for your help, although it does seem incredibly long-winded to achieve this when the same thing can be done on the RPI pins with a built in debounce option !
-
@crankshaft That's strange, I have been using this code for more than 2 weeks now, without a single double press or any crashes! What are using as button? A micro switch?
-
@mohpor - Thanks for helping, but I get multiple short presses (1 to 6) even if I increase the t > 100.
Am also getting a crash:
Short 0 Short 0 Guru Meditation Error of type LoadProhibited occurred on core 0. Exception was unhandled. Register dump: PC : 0x40081b87 PS : 0x00060330 A0 : 0x800de62b A1 : 0x3ffe15f0 A2 : 0x65642728 A3 : 0x3ffc39d8 A4 : 0x3ffe1a40 A5 : 0xd58eb6e0 A6 : 0x3ffe16f8 A7 : 0x0000005b A8 : 0x80083f91 A9 : 0x3ffe15b0 A10 : 0x00000003 A11 : 0x00060723 A12 : 0x00060723 A13 : 0xb33f0000 A14 : 0xb33fffff A15 : 0x3ffe16c0 SAR : 0x00000002 EXCCAUSE: 0x0000001c EXCVADDR: 0xd58eb6e0 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xffffffff Backtrace: 0x40081b87:0x3ffe15f0 0x400de62b:0x3ffe1610 0x400ecec0:0x3ffe1630 0x400e95a8:0x3ffe1650 0x400e9615:0x3ffe1670 0x400e2905:0x3ffe1690 0x400e047c:0x3ffe16d0 0x400f325a:0x3ffe16f0 0x400ecea1:0x3ffe1710 0x400e95a8:0x3ffe1730 0x400e9615:0x3ffe1750 0x400f4b68:0x3ffe1770 0x400ecfcd:0x3ffe1810 0x400e95a8:0x3ffe1850 0x400e95ec:0x3ffe1870 0x400dbf50:0x3ffe18a0 0x400e071f:0x3ffe18c0 0x400d7e80:0x3ffe1940 Rebooting... ets Jun 8 2016 00:22:57 rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00
I added a print(t) after t = chrono.read_ms() so I could monitor the time, and pressed the button quickly and got 3 hits all with the same timestamp:
>>> execfile('dev.py') Ram Free: 84.40% waiting... 0.0 182.9711 Short 0 182.9711 Short 0 182.9711 Short 0
-
Here it is what I am using and is reliable almost 100% of times (Haven't seen any double presses) Although, you can easily tweak time values to achieve higher precision:
from machine import Timer from machine import Pin import gc chrono = Timer.Chrono() timer = Timer.Alarm(None, 1.5, periodic = False) btn = Pin(Pin.exp_board.G16, mode= Pin.IN, pull=Pin.PULL_UP) def long_press_handler(alarm): print("****** LONG PRESS HANDLER ******") def single_press_handler(): print("****** BUTTON PRESSED ******") def btn_press_detected(arg): global chrono, timer try: val = btn() if 0 == val: chrono.reset() chrono.start() timer.callback(long_press_handler) else: timer.callback(None) chrono.stop() t = chrono.read_ms() if (t > 30) & (t < 200): single_press_handler() finally: gc.collect() btn.callback(Pin.IRQ_FALLING | Pin.IRQ_RISING, btn_press_detected)
As a side note, this implementation detects long press if the button has been kept down more than 1.5 seconds, regardless of user releasing the button. And it doesn't need any waiting loops.
-
Ignoring the long/short press for the time being, I have tried a slightly different approach, this also results in double presses / triggers:
busy = False def pin_int(pin): global busy if(busy): return busy = True i=0 while i < 100: time.sleep_ms(2) if(pin.value()==0): i=0 else:i+=1 print(pin.value(),i) time.sleep_ms(500) busy = False p_in = machine.Pin('P12', mode=machine.Pin.IN, pull=machine.Pin.PULL_UP) p_in.callback(machine.Pin.IRQ_FALLING, pin_int)
Update - even if I disable interrupts, I still get double triggering:
def pin_int(pin): machine.disable_irq() global busy