Micropython button interrupt handler function creation

So if you try to create multiple common handlers via lambda functions, you get screwed as the lambdas are optimized out. In the end all the interrupts calls the last lambda callback function.

How to overcome this?

Interrupt handling function

The function is called after some pre-setuped periphery state changes. If you want to print a number when you press a button you can setup it like this:

pin_id = 'A0'
new_callback = lambda x: print('Hello, button 1 pressed!')
new_btn = pyb.ExtInt(pin_id, pyb.ExtInt.IRQ_FALLING, pyb.Pin.PULL_UP,\

The official pyb.Pin doc is here.

Where the lambda inline function takes care of handler function creation. When the interrupt handler function is called "from the hardware" it always has one input parameter (the x in the lambda). If we print it by changing the callback function like this:

new_callback = lambda x: print('Hello, button 1 pressed, param = ', x)

We get:

# Pressed button A0:
Hello, button 1 pressed, param = 0

We find out that the parameter is a number specifying the External interrupt line. To put it simply, you cannot have multiple buttons setuped on the same line (on stm32f4). This means you cannot create interrupts on the pins 'A0' and 'B0' at the same time as they both has a external interrupt line number 0. You always can use different numbers so 'A0', 'B1', 'A2' etc. is perfectly fine.

But what if you want to print different numbers on individual buttons presses.

Lambda generation loop

import pyb
def init_buttons(self):
    bs = []
    pins = ['A4','A5','A6']

    # cannot use for loop to generate, because all but the last lambda 
    # function are optimalised out
    for i in range(len(pins)):
        bs.append(lambda x: print(i, ': line', x))  
    for i, pin_id in enumerate(pins):
        new_callback = bs[i]
        new_btn = pyb.ExtInt(pin_id, pyb.ExtInt.IRQ_FALLING, 
                pyb.Pin.PULL_UP, new_callback)


# Pressed button A4 :
3: line 4
# Pressed button A5 :
3: line 5
# Pressed button A6 :
3: line 6

Even this is not going to work - with the same outcome.

    i = 0
    bs.append(lambda x: on_arrow_button(i, x)); i+=1
    bs.append(lambda x: on_arrow_button(i, x)); i+=1
    bs.append(lambda x: on_arrow_button(i, x)); i+=1


This solution produces the right outcome, but is kinda unmaintianable.
Although this code is better as the button to output index coding is done here in the setup, and not in the handler every time you press it.

    bs.append(lambda x: print(4, ': line', x))
    bs.append(lambda x: print(5, ': line', x))
    bs.append(lambda x: print(6, ': line', x))


# Pressed button A4 :
1: line 4
# Pressed button A5 :
2: line 5
# Pressed button A6 :
3: line 6

Verbatim with mapper

With a little change it can be a little better for changing and still functional.

    mapper = [1,2,3]
    bs.append(lambda x: print(mapper[0], ': line', x))
    bs.append(lambda x: print(mapper[1], ': line', x))
    bs.append(lambda x: print(mapper[2], ': line', x))

Common handler function with mapper

You can always settle up for one more mapping statement to proceed in every interrupt handler and have only one common interrupt handler. Just map the interrupt line numbers to your wanted outputs inside the handler function.

mapper = {4:1, 5:2, 6:3}
def on_arrow_button(line):
    global mapper
    print(mapper[line], ': line', line

Lambda generator loop

You can try also other ways, to try to mess the optimization but the outcome is still bad.

x = [1,2,3]

def a(i):
    global x
    return x[i]
for i in range(len(pins)):
    bs.append(lambda x: print(a(i), ': line', x))


# Pressed button A4 :
3: line 4
# Pressed button A5 :
3: line 5
# Pressed button A6 :
3: line 6

Hardware setup

Also you may ask why did we setuped the pin with

pyb.ExtInt.IRQ_FALLING, pyb.Pin.PULL_UP

This depends on how we have wired the hardware button. In this example it should be connected like this:

  • in not-pressed state the wire from the button mcu pin is not connected to anything
    • The pull-up setup connects the pin in the mcu to the powering voltage VCC via a pull-up resistor. That means when we don't have it connected to nothing, the inner resistor makes the pin voltage to be almost VCC = logical 1.
  • if pressed the button should connect the button mcu pin to ground
    • In the pressed state, the ground is connected to the pin directly, and the VCC pull-up resistor cannot pull the pin voltage to the VCC -> it's grounded = logical 0.
    • That means when we want one button we need 2 wires = one for the button pin signal, and second for the mcu ground. For 2 buttons it's 3 wires and so forth.
      • num_of_wires = num_of_buttons + 1


Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *