I'm working on a project that use four buttons to navigate in a menu. The root options of the menu have nested options etc. The buttons don't have hardware de-bouncing.

My ultimate objective is to leverage a function wait_button() that wait for any press of the four buttons and returns the channel ID.

For performance reasons, I want to use an interrupt approach instead of polling.

At a first glance it seems pretty straightforward using this inspiring answer which leverages queue and RPi.GPIO. I tested something like this:

def wait_button():
    from queue import Queue

    queue = Queue()
    GPIO.add_event_detect(config.A_BUTTON, GPIO.FALLING, queue.put, bouncetime=300)
    GPIO.add_event_detect(config.B_BUTTON, GPIO.FALLING, queue.put, bouncetime=300)
    GPIO.add_event_detect(config.C_BUTTON, GPIO.FALLING, queue.put, bouncetime=300)
    GPIO.add_event_detect(config.D_BUTTON, GPIO.FALLING, queue.put, bouncetime=300)

    chId  = queue.get()

    return chId

Then using it in a menu's loop with:

while True:
    button == wait_button()

    if button == config.A_BUTTON:
        do_something1(arg1, arg2)
    elif button == config.B_BUTTON:
        do_something2(arg3, ...)
    elif button == config.C_BUTTON:

It works very well until some bounces start to appear when subsequent button presses happen too fast. On my side I suspect GPIO.remove_event_detect() and GPIO.add_event_detect() to be called in sequence as fast as the bouncetime becomes obsoleted.

I'm looking for any hint to implement wait_button() as clean and elegant as possible. Maybe I should use threads and wait_for_edge() ?

UPDATE May/27 I tried the same code with gpiozero instead of RPi.GPIO and it seems to work perfectly:

from gpiozero import Button
from queue import Queue

leftButton = Button(config.LEFT_BOUTON, pull_up=False)
rightButton = Button(config.RIGHT_BOUTON, pull_up=False)
homeButton = Button(config.HOME_BOUTON, pull_up=False)
pauseButton = Button(config.PAUSE_BOUTON, pull_up=False)

i = 1
def wait_button():

    queue = Queue()

    leftButton.when_pressed = queue.put
    rightButton.when_pressed = queue.put
    homeButton.when_pressed = queue.put
    pauseButton.when_pressed = queue.put

    e = queue.get()

    return e.pin.number

# somewhere in the code
def menu_record():
    while True:
        b = wait_button()
        if b == config.LEFT_BOUTON:
            print("start recording")
            # here I have access to the variables context and able to do:
            # start_record(a,b,c,d...)
        if b == config.RIGHT_BOUTON:
            print("branch to...")
            # another_menu(e,f...)

Now I've been thinking by one of the answers (thanks), if the it's the best design for a menu / submenu interface (it's an audio interface btw), wait for button input and branch from a menu to another (that can be nested), doing actions etc. But it's a bit out of topic from the current question.

  • 1
    "use an interrupt approach instead of polling" -> If by "polling" you mean iterating them in some kind of fast loop, I agree. However, there are syscall based functions such as poll() and select() (those are C lib, but every high level language will have a wrapper implementation) that block on a set of file descriptors and fire when one or more of them change state. Which is the basis of much/most event based programming and exactly what you want to do. I'm not a python user and don't know how feasible that is w/ the gpio libs but keep it in mind vis., what "polling" may refer to. – goldilocks May 26 '20 at 14:53
  • Yes, by "polling" I meant a fast while True loop which is not the way to go because it consume barely all the CPU power. This is where queue.get() is useful because it uses epool() (if I'm not wrong) – martin-mystere May 26 '20 at 16:41
  • Yes, epoll() is akin to poll() and select(). "It works very well until some bounces start to appear when subsequent button presses happen too fast." -> This implies to me the bounce time may be excessive; ~50-100 ms should be more than sufficient, and it would need to be more than that for a person to click a mechanical button up and down multiple times inside of it. If it's not more than that, it probably isn't the bounce code unless there is a serious logical flaw in it. – goldilocks May 26 '20 at 16:44
  • lowering bouncetime to 50ms seems to almost clear the bouncing effect. But I still do not understand why 300ms make the bounce effect to appear eg. why one press something leads to two events. As my understanding for bouncetime is the period of time during which the callback will be called only once no matter what. There is something with the timing somehow but I don't get it yet. On the other hand, after 40-100 hits on a button the program seems to freeze at queue.get() not answering to any press, a bit like if no callback was attached at all... – martin-mystere May 26 '20 at 19:56
  • I've been editing my question. I think I have something working... The next question would be to see if it's a good boilerplate design for an menu interface w/ branching. – martin-mystere May 27 '20 at 15:36

You could do this, although waiting is a poor strategy. Interrupts may be a brute force way to overcome this, but there are better solutions.

I suggest gpiozero.Button specifically https://gpiozero.readthedocs.io/en/stable/api_input.html#gpiozero.Button.when_pressed

This will let you handle many more events with minimal overhead, avoiding all the complexity of interrupts. As a bonus you get de-bounce.

See https://gpiozero.readthedocs.io/en/stable/recipes_advanced.html#button-controlled-robot for an example

  • Hi ! I'm not sure to understand as I need to wait for an user input that is, any of the four button. – martin-mystere May 26 '20 at 13:34
  • Or, are you suggesting to make use of pause() after setting up the callbacks ? – martin-mystere May 26 '20 at 13:50
  • You define a callback for each button. No pauses which would just make the program sluggish. There are examples on the gpiozero site. – Milliways May 26 '20 at 22:53
  • I know how to use callback, I'm just wondering how it makes sense not to pause when you're working with UI and menu (and submenus) that rely/wait on user input eg. all the time. One would need to redefine the callbacks for each menu (and more importantly be able to pass parameters to the callbacks). I'll think about it. – martin-mystere May 27 '20 at 14:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.