Multiple pins + Multiple callbacks not working! Urgent!



  • Hey guys,
    I've run into an issue with less than 24 hours till my Senior design project is due. Basically I have a device with multiple buttons which will then send different status codes based on which button is pushed. I implemented button debouncing as well as a manager which keeps track of when a button was last pushed so data won't constantly be sent over and over.

    There is actually a similar issue to this in the thread where code similar to mine is used and a reply from someone which seems to have the exact same issue found here- https://forum.pycom.io/topic/588/interrupt-button-debounce-with-long-short-press/14

    My main issue is that, when I create the 3 different buttons I am using, only the first button I ever press is able to be pressed afterwards (or result in the callback being called). All buttons get setup and I can access them inside the REPL, however when they get pressed, even after confirming that they were pressed via accessing the pin values, the callbacks never go off and I just can't seem to figure out why. I even made specific functions for each to ensure that wasn't an issue.

    So basically pressing any of the 3 buttons effectively doesn't allow the other buttons to be pushed. So pressing the button connected to P8 makes it so that neither P9 or P11s callback every goes back even though I can see that their callback is still bound to a function and access their values.

    At this point I'm completely lost. I'm unsure if it's not possible to do what I am attempting to do, but if anyone can help I would very much appreciate it.

    # Button.py
    from time import sleep_ms, ticks_ms, ticks_diff, time
    from machine import Pin, disable_irq, enable_irq, Timer
    from _thread import start_new_thread
    import gc
    import rgb
    
    
    class Manager:
    
        def __init__(self, sensor, node, status, delay=5):
            self.pressed = False
            self.last_pressed = 0
            self.current_status = None
            self.status_time = 0
            self.delay = delay
            self.sensor = sensor
            self.node = node
            self.status = status
            self.alarm = None
    
        def can_press(self):
            print("last pressed = {}\n delay = {}\ntime = {}\ndiff between = {}".format(self.last_pressed, self.delay,
                                                                                        time(), time() - self.last_pressed))
            print("--------------------------")
            print('\n')
            return time() - self.last_pressed > self.delay and not self.pressed
    
        def set_alarm(self):
            return Timer.Alarm(self.alarm_func, s=10, arg=[self.sensor, self.node, self.status], periodic=True)
    
        def alarm_func(self):
            pass
    
        def button_depressed(self):
            # print("reinitiating alarm")
            self.pressed = False
            self.last_pressed = time()
            # self.alarm = self.set_alarm()
            # print(self.alarm)
    
        def button_pressed(self):
            # print("cancelling alarm")
            # self.alarm.cancel()
            self.pressed = True
            # print(self.alarm)
    
    
    class BUTTON:
    
        def __init__(self, sensor, node, manager, status_alert=0, pid='P10', longms=1000):
            self.manager = manager
            self.pressms = 0
            self.longms = longms
            self.pin = Pin(pid, mode=Pin.IN, pull=Pin.PULL_DOWN)
            self.pin.callback(Pin.IRQ_RISING, self.press)
            self.sensor = sensor
            self.node = node
            self.status_alert = status_alert
            self.args = (self.sensor, self.node, self.status_alert)
            print("Button setup!")
    
        def check_delay(self):
            return self.manager.can_press()
    
        def long(self):
            pass
    
        def short(self):
            pass
    
        def press(self, pin):
            print("{} pressed!".format(pin))
            # state = disable_irq()
            # 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() == 0:
                    i = 0
                else:
                    i += 1
    
            if not self.check_delay():
                self.manager.button_pressed()
                print("CANT PRESS YET! HAHA")
                print("button done")
                print('-------------------')
                print('\n')
                # enable_irq(state)
                rgb.blink_red(1, 1, 1)
                self.manager.button_depressed()
                return
    
            self.manager.button_pressed()
    
            # Measure button press duration
            while self.pin() == 1:
                i += 1
                if i > self.longms:
                    break
                sleep_ms(1)
    
            # Trigger short or long press
            if i > self.longms:
                start_new_thread(self.long, self.args)
            else:
                start_new_thread(self.short, self.args)
    
            # Wait for button release.
            while self.pin() == 1:
                pass
            self.pressms = 0
            # enable_irq(state)
            rgb.blink_green(3)
            self.manager.button_depressed()
            gc.collect()
            print("button done!!")
    
    

    And the main file is here

    # main.py
    # main.py -- put your code here!
    from lora.abp_node_US915 import setup_node
    import gps_setup
    from pytrack import Pytrack
    import time
    from machine import Timer, Pin, PWM
    from _thread import start_new_thread
    import servo
    import struct
    import gc
    from LIS2HH12 import LIS2HH12
    from button import Manager, BUTTON
    
    gc.enable()
    
    
    def send_coords_button1(sensor, node, status):
        pitch = sensor.pitch()
        roll = sensor.roll()
        pitch_data = struct.pack('>f', pitch)
        roll_data = struct.pack('>f', roll)
        # coords = args[0].coordinates(debug=True)
        # lat = struct.pack('>f', coords[0])
        # long = struct.pack('>f', coords[1])
        status = bytes([status])
        # pkt = lat + long + status
        pkt = pitch_data + roll_data + status
        print("Raw packet data: ", pkt)
        node.send(pkt)
        print("Packet sent via BUTTON successfully")
        print("     Free memory: ", gc.mem_free())
        gc.collect()
        print("     Free memory after GC collect: ", gc.mem_free())
        print("----------------------------------------------")
    
    
    def send_coords_button2(sensor, node, status):
        pitch = sensor.pitch()
        roll = sensor.roll()
        pitch_data = struct.pack('>f', pitch)
        roll_data = struct.pack('>f', roll)
        # coords = args[0].coordinates(debug=True)
        # lat = struct.pack('>f', coords[0])
        # long = struct.pack('>f', coords[1])
        status = bytes([status])
        # pkt = lat + long + status
        pkt = pitch_data + roll_data + status
        print("Raw packet data: ", pkt)
        node.send(pkt)
        print("Packet sent via BUTTON successfully")
        print("     Free memory: ", gc.mem_free())
        gc.collect()
        print("     Free memory after GC collect: ", gc.mem_free())
        print("----------------------------------------------")
    
    
    def send_coords_button3(sensor, node, status):
        pitch = sensor.pitch()
        roll = sensor.roll()
        pitch_data = struct.pack('>f', pitch)
        roll_data = struct.pack('>f', roll)
        # coords = args[0].coordinates(debug=True)
        # lat = struct.pack('>f', coords[0])
        # long = struct.pack('>f', coords[1])
        status = bytes([status])
        # pkt = lat + long + status
        pkt = pitch_data + roll_data + status
        print("Raw packet data: ", pkt)
        node.send(pkt)
        print("Packet sent via BUTTON successfully")
        print("     Free memory: ", gc.mem_free())
        gc.collect()
        print("     Free memory after GC collect: ", gc.mem_free())
        print("----------------------------------------------")
    
    
    node = setup_node()
    py = Pytrack()
    acc = LIS2HH12()
    gps = gps_setup.setup_gps()
    
    manager = Manager(acc, node, 1)
    
    button_1 = BUTTON(acc, node, manager, pid='P8', status_alert=1)
    print("Button_1 value is " + str(button_1.pin()))
    button_1.short = send_coords_button1
    button_1.long = send_coords_button1
    
    button_2 = BUTTON(acc, node, manager, pid='P11', status_alert=2)
    print("Button_2 value is " + str(button_2.pin()))
    button_2.short = send_coords_button2
    button_2.long = send_coords_button2
    
    button_3 = BUTTON(acc, node, manager, pid='P9', status_alert=3)
    print("Button_3 value is " + str(button_3.pin()))
    button_3.short = send_coords_button3
    button_3.long = send_coords_button3
    
    print("Setup complete!")
    
    


  • @timh I kind of get what you're going for, but the issue is mostly that when attempting to use any sort of debouncing with multiple buttons (each button with their own callback), the first button pressed is the only button that the callback will run for until the device is reset (even though the device detects the other button is push as confirmed through looking at the pin value).

    So if I understand you correctly, I could simply provide arguments for each pin's callback function to identify which one was activated. That would work, but when the callback won't even go through for any button other than the first one pressed, that doesn't really work.

    I just worked around it and made a function to detect sequential long presses. Since I have 3 different status codes, I made it as such

    • 1 long press sends status code 1
    • 2 long presses sends status code 2
    • 3 long presses sends status code 3

    A user is notified of what press they are on based on the LED colors, and 3 green blinks represents that the status/data has been sent.



  • I had similar issues. The reason it did not work for me was that all callbacks (i.e. Timer, Pin, etc) are pushed to the same queue and executed sequentially.
    That means that the next callback will only be called, when the last one finished. Maybe one of your callbacks is not finishing properly.



  • @kbman99 You can use your simple function.

    Include the arg (pin identifier for instance) when you register each all back on the PIN.

    arg is an optional argument to pass to the callback. If left empty or set to None, the function will receive the Pin object that triggered it.

    or use the PIN object itself to identify which index. (You could even attach the last pressed ms to the pin)

    p2.callback(Pin.IRQ_RISING, press,'P2')

    Then use a global dictionary, or a list (and just have the arg be the index.) which will hold the last pressed time for each pin

    Then in your call back use the arg to look up the value of the last pressed.

    T



  • I tried an implementation that didn't use a whole bunch of classes and just inline and no luck. It works without all the functions running with a simple callback to a function which prints the IDs of the pins, but what use is that when I can't use debouncing....

    # main.py
    gc.enable()
    
    pressed = False
    last_pressed = 0
    pressms = 0
    longms = 1000
    
    
    def help(type, pin):
        print("Pin {} pressed, running {} func".format(pin.id(), type))
    
    
    def button_pressed():
        global pressed
        pressed = True
    
    
    def button_depressed():
        global pressed
        global last_pressed
        pressed = False
        last_pressed = time.time()
    
    
    def can_press(delay):
        global last_pressed
        global pressed
    
        return time.time() - last_pressed > delay and not pressed
    
    
    def press(pin):
        global pressms
        global longms
    
        # If never pressed, store press time
        if pressms == 0:
            pressms = ticks_ms()
        else:
            # If pressed within 500 ms of first press, discard (button bounce)
            if ticks_diff(pressms, ticks_ms()) < 500:
                return
    
        # Wait for value to stabilize for 10 ms
        i = 0
        while i < 10:
            sleep_ms(1)
            if pin() == 0:
                i = 0
            else:
                i += 1
    
        if not can_press(5):
            button_pressed()
            rgb.blink_red(1, 1, 1)
            button_depressed()
            return
    
        button_pressed()
    
        # Measure button press duration
        while pin() == 1:
            i += 1
            if i > longms:
                break
            sleep_ms(1)
    
        # Trigger short or long press
        if i > longms:
            start_new_thread(help, ('short', pin))
        else:
            start_new_thread(help, ('long', pin))
    
        # Wait for button release.
        while pin() == 1:
            pass
        pressms = 0
    
        rgb.blink_green(3)
        button_depressed()
        gc.collect()
        return 0
    
    p1 = Pin('P8', mode=Pin.IN, pull=Pin.PULL_DOWN)
    p1.callback(Pin.IRQ_RISING, press)
    
    p2 = Pin('P11', mode=Pin.IN, pull=Pin.PULL_DOWN)
    p2.callback(Pin.IRQ_RISING, press)
    
    p3 = Pin('P9', mode=Pin.IN, pull=Pin.PULL_DOWN)
    p3.callback(Pin.IRQ_RISING, press)
    


Pycom on Twitter