Welcome to the Pycom forum Forum
    • \[\[global:header.categories\]\]
    • \[\[global:header.recent\]\]
    • \[\[global:header.tags\]\]
    • \[\[global:header.popular\]\]
    • Login

    Explore Pybytes | Official Documentation | Report a Firmware Bug/Issue | GitHub

    Interrupt button debounce with long / short press

    Scheduled Pinned Locked Moved WiPy 2&3
    15 Posts 5 Posters 5.5k Views 7 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • C Offline
      crankshaft
      last edited by crankshaft

      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
      
      1 Reply Last reply Reply Quote 0
      • M Offline
        mohpor
        last edited by

        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.

        C 1 Reply Last reply Reply Quote 0
        • C Offline
          crankshaft @mohpor
          last edited by crankshaft

          @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
          
          1 Reply Last reply Reply Quote 0
          • M Offline
            mohpor
            last edited by

            @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?

            C 1 Reply Last reply Reply Quote 0
            • C Offline
              crankshaft @mohpor
              last edited by

              @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 !

              1 Reply Last reply Reply Quote 0
              • M Offline
                mohpor
                last edited by

                @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.

                1 Reply Last reply Reply Quote 0
                • L Offline
                  livius @crankshaft
                  last edited by livius

                  @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

                  think about people first, then computers

                  C 1 Reply Last reply Reply Quote 0
                  • C Offline
                    crankshaft
                    last edited by

                    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)
                    
                    1 Reply Last reply Reply Quote 0
                    • C Offline
                      crankshaft
                      last edited by crankshaft

                      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
                      
                      G 1 Reply Last reply Reply Quote 1
                      • C Offline
                        crankshaft @livius
                        last edited by

                        @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.

                        1 Reply Last reply Reply Quote 0
                        • G Offline
                          gael-wogenstahl @crankshaft
                          last edited by

                          @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 !

                          L 1 Reply Last reply Reply Quote 0
                          • L Offline
                            livius @gael-wogenstahl
                            last edited by

                            @gael-wogenstahl
                            can you show how you declare all?
                            Code for three buttons?

                            think about people first, then computers

                            G 1 Reply Last reply Reply Quote 0
                            • G Offline
                              gael-wogenstahl @livius
                              last edited by

                              @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
                              
                              M 1 Reply Last reply Reply Quote 0
                              • M Offline
                                mahe @gael-wogenstahl
                                last edited by

                                @gael-wogenstahl

                                Hi,

                                I'm experiencing the same kind of problems - did you ever solve it?

                                Thanks

                                1 Reply Last reply Reply Quote 1

                                Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                                Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                                With your input, this post could be even better 💗

                                Register Login
                                • 1 / 1
                                • First post
                                  Last post

                                Pycom on Twitter

                                Pycom