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.
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.
The two most popular modes are
FALLING . If you select
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_LEVELinterrupt is triggered.
Pin.IRQ_HIGH_LEVELactivates the interrupt when the voltage of the signal is 3.3V.
When an input signal goes from
Pin.IRQ_RISINGinterrupt is triggered.
Suppose a pin is set to
Pin.IRQ_FALLINGwill generate an interrupt when the signal goes from
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.
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
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.
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.
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.
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.
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.