Skip to content

uPesy now ships to the entire European Union

Livraison à partir de seulement 2.50€ !

Contents Menu Expand Light mode Dark mode Auto light/dark mode

How to use interrupts in MicroPython with a Pico RPi?

(Updated at 02/02/2023)

In electronics, an interrupt is an urgent signal sent to a processor asking it to prioritize and perform a particular task immediately. This stops the program that the processor was previously executing.

Interrupts can be caused by various events - such as external events and timers . They help to perform tasks that are not part of the main program and can be done simultaneously as the rest of the program is running. This is known as asynchronous execution.

Interrupts in MicroPython

In practice, interrupts are generally used to:

  • Execute a Python function with a specific code that can be automatically triggered when a button is pressed.

  • Run functions at a specified interval, such as flashing an LED every 5 seconds.

It is possible to write code to turn on an LED when a key is pressed without using interrupts. However, this technique has two significant disadvantages.

from machine import Pin

pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)
pin_led    = Pin(25, mode=Pin.OUT)

while True:
    if not pin_button.value():
        pin_led.on()
    else:
        pin_led.off()

The first problem is that the script must constantly check the value of the pin_button pin to determine if the button has been pressed. Doing anything else in the loop can cause the second problem, where a button press may go unnoticed. This is known as a “missing event.”

The clear advantage of using a hardware interrupt is that it obliterates the detection process from the processor, which means it won’t be present in the Python script. This means that the while loop in the script will be empty. In addition, the detection hardware is more responsive than the MicroPython script.

Note

Interrupts provide a quick and easy way of responding to events without constantly checking a pin’s value. Whenever pin changes are detected, a defined function is automatically performed.

Interrupts allow a program to respond to changes in signals. These can be generated by a timer or by an external event. To understand more about interrupts, let’s look at the different possibilities. 🤓

Triggering a hardware interrupt: detection modes

The detection of an event is based on the shape of the signal that arrives at the pin.

Event detection on a digital signal

Different detection modes

Note

The two most popular modes are RISING and FALLING . If you select LOW and HIGH modes, the interrupt will repeat until the signal changes direction.

Here are the different types of interruption detection possible:

  • When the signal is at 0 volts, the Pin.IRQ_LOW_LEVEL interrupt is triggered.

  • Pin.IRQ_HIGH_LEVEL activates the interrupt when the voltage of the signal is 3.3V.

  • When an input signal goes from LOW (0V) to HIGH (3.3V), the Pin.IRQ_RISING interrupt is triggered.

  • Suppose a pin is set to Pin.IRQ_FALLING will generate an interrupt when the signal goes from HIGH to LOW (3.3V to 0V).

Configuring and using interrupts in MicroPython on the Pico

A skeleton Python script to follow

This example script in MicroPython allows you to use a signal received by your Pi Pico to activate an external interrupt.

from machine import Pin

pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)

def interruption_handler(pin):
    ...

pin_button.irq(trigger=Pin.IRQ_FALLING,handler=interruption_handler)

while True:
    ...

The code uses the Pin.irq() function, which takes a signal from a pin_button and converts it into an interrupt request.

Note

irq is the French abbreviation for Interrupt Requestor, which literally means “demander une requête d’interruption”. The abbreviation isr stands for Interrupt Routine, which is the function executed after the interrupt is triggered (here it is called interruption_handler() ).

When an interrupt is enabled, interruption_handler() is called with the pin where the event occurred as the input argument.

It is essential to write an interrupt function (isr ) quickly to avoid interrupting the main program. For example, sending data via I2C or SPI directly from an interrupt is not recommended. A better approach is to use a boolean flag to store the detection of an event and then handle it in the main loop.

Note

Interrupt handling in MicroPython will never be as fast as in Arduino or pure C code! However, it is possible to reduce this delay by using more sophisticated configurations.

Example: Turn on an LED when a push button is pressed

Wiring of the push button and LED on the bread board

Electrical circuit to be made

Wiring of the push button and LED on the bread board

Electrical circuit to be made

This is the code you need to run to detect when a button is pressed and turn on an LED using an interrupt:

import time
from machine import Pin

pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)
pin_led    = Pin(16, mode=Pin.OUT)


def button_isr(pin):
  pin_led.value(not pin_led.value())

pin_button.irq(trigger=Pin.IRQ_FALLING,handler=button_isr)

while True:
  ...

Note

In this example, an interrupt is activated on a falling edge. It is possible to use the OR operator (denoted by | ) to combine different modes, resulting in the interrupt being triggered on either a rising or falling edge:

pin_button.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING,handler=button_isr)

Here are some functions that may be useful to you:

  • irq.init() : Re-initialize the interrupt. It will be automatically reactivated.

  • irq.enable() : Enable the interrupt.

  • irq.disable() : Disable the interrupt.

  • irq() : Manually launch the call of the interrupt routine.

  • irq.flags() to know the type of event that triggered the interrupt. It can only be used in the isr .

    def pin_handler(pin):
        print('Interrupt from pin {}'.format(pin.id()))
        flags = pin.irq().flags()
        if flags & Pin.IRQ_RISING:
            # handle rising edge
        else:
            # handle falling edge
        # disable the interrupt
        pin.irq().deinit()
    

To take advantage of these features, you must use the pin variable associated with an interrupt - such as pin_button.irq().enable() .

A basic understanding of interrupts in MicroPython is essential. Suppose you want to take full advantage of their features, such as setting priorities between multiple interrupts firing simultaneously. In that case, it is recommended that you consult the official documentation . If you are looking for tips and optimization techniques, you can find them in the advanced section of the documentation. 😎

Use global variables to manage events in the main program

Limiting the number of actions performed within an interrupt is recommended to avoid overloading the system. A common practice is incrementing a variable within the ISR (Interrupt Service Routine) and completing the long tasks in the main code according to the value of this variable. For example, here is code that counts the number of times a button is pressed.

import time
from machine import Pin

button_pressed_count = 0 # global variable
pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)

def button_isr(pin):
    global button_pressed_count
    button_pressed_count += 1


if __name__ == "__main__":
    button_pressed_count_old = 0
    pin_button.irq(trigger=Pin.IRQ_FALLING,handler=button_isr)

    While True:
        if button_pressed_count_old != button_pressed_count:
           print('Button value:', button_pressed_count)
           button_pressed_count_old = button_pressed_count

        if button_pressed_count > 10: # heavy task here
            ...

We use a global variable to write to it inside the interrupt routine.

Warning

It is important to note that when a variable is created at the top of a Python script, the keyword global must be used when referenced within a function. This tells the Python interpreter to use the global variable rather than creating a local version of the same variable that would only be used within the function.

Result interrupt button

When we run our MicroPython code, we see a bigger increase than when the button was pressed. This is confusing! The not-so-smooth changes of the logic levels at the button level are why.

Improve reliability of detection: debounce the push button

You may have noticed that buttons can give false positives: the interrupt routine runs more often than it should. This is because the signal received by the Pico is not perfect: you are getting incorrect pressure from the button.

bouncing effect of a push button

This is called the bounce effect . It is possible to**reduce the bounce of a button** directly via the Python script called debouncing software . It ignores the short period between the two logical states by waiting a long time.

This section is available to premium members only. You still have 91% to discover.

Subscribe for only 5$/month

Already subscribed? Sign in