In electronics, an interrupt is a signal sent to a processor that tells it that an important task must be executed immediately, thus interrupting the program in progress.
Interrupts can be triggered in various ways, for example by an external event, a
timer they allow to perform tasks asynchronously, outside the main program.
In practice, interrupts are generally used to:
Execute portions of critical code when an external event occurs, for example when a button is pressed and this automatically triggers a Python function.
Execute functions periodically, e.g. to flash an LED every 5 seconds.
Without using interrupts, it is possible to create a script that turns on an LED when a button is pressed. However, this method has two major drawbacks.
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 drawback is that the script spends its time monitoring the value of the
pin_button pin to know if the button has been pressed. The script can’t do much more, because otherwise the second problem will occur: missing events. If the script performs other tasks in the loop, it may miss the temporary button press.
The advantage of using a hardware interrupt is that the detection is completely separate from the processor (and therefore from the Python script). With an interrupt, the
while loop of the script will be empty. The hardware responsible for the detection is also much more responsive than the MicroPython script.
With interrupts, it is no longer necessary to constantly monitor the value of a pin: a function is automatically executed when a change is detected.
Whether it is with a
timer or an external event, the interrupt is triggered when there is a signal change. Let’s discover the different possibilities 😊.
The detection of an event is based on the shape of the signal that arrives at the pin.
FALLING modes are the most commonly used. Note that if you use the
HIGH modes, the interrupt will loop as long as the signal does not change state.
Here are the different types of interruption detection possible:
Pin.IRQ_LOW_LEVELtriggers the interrupt when the signal is at 0V
Pin.IRQ_HIGH_LEVELtriggers the interrupt when the signal is at 3.3V
Pin.IRQ_RISING: Triggers the interrupt when the signal goes from
HIGH(From 0V to 3.3V)
Pin.IRQ_FALLINGtriggers the interrupt when the signal changes from
LOW(From 3.3V to 0V)
Here is a skeleton script in MicroPython that allows you to trigger an external interrupt via a signal received by your Pi Pico :
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 function
Pin.irq() function to create an interrupt request from a down signal applied to the
irq stands for Interrupt Request or in French, demander une requête d’interruption. The word
isr to designate the interrupt routine, i.e. the function that will be executed after the interrupt (here it is called
When an interrupt is triggered, the
interruption_handler() function will automatically execute with as input argument the pin on which the event was detected.
It is recommended to write an interrupt function (
isr ) as fast as possible to avoid disturbing the main program. For example, it is not recommended to send data via I2C, SPI directly from an interrupt. It is better to use flags in the form of boolean to store the detection of an event and then process it in the main loop.
La gestion des interruptions en MicroPython sera toujours plus lente qu’en code Arduino ou en C pur ! Cependant, il est possible de minimiser cette latence en utilisant des paramètres avancés.
We try to limit the number of actions performed within an interrupt. It is common to increment a variable inside the ISR and perform the long tasks in the main code according to the value of this variable. Here is an example that counts the number of times you press a push button.
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 be able to write to it inside the interrupt routine.
Even if the variable is defined at the very top of the Python script, you must add the keyword
global when the variable is used in a function. This tells the Python interpreter to use the global variable rather than creating a local variable (with the same name) that would only be used in the context of the function’s execution.
When we run the MicroPython script, we notice that the value of the increment is much larger than the number of presses made. Strange, isn’t it? This is due to the transitions of the logic levels at the push-button level, which are not perfect..
You have probably noticed that push buttons can give false positives: the interrupt routine then runs more times than it should. This is because the signal received by the Pico is not perfect: you get spurious pressure from the button.
This is called the**bounce effect** (bounce in English). It is possible to reduce the bounce of a push-button via the Python script directly, which is called debouncing software . It consists in not taking into account the transient period between the two logical states by waiting a certain delay.