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.
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) toHIGH
(3.3V), thePin.IRQ_RISING
interrupt is triggered.Suppose a pin is set to
Pin.IRQ_FALLING
will generate an interrupt when the signal goes fromHIGH
toLOW
(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
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 theisr
.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.
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.
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$/monthAlready subscribed? Sign in