Raspberry Pi Pico Interrupts Tutorial- Examples in MicroPython

In this article, learn how to use interrupts in Raspberry Pi Pico. A pushbutton will be used to explain how interrupts work.

We can interface a push button using either Polling or Interrupt. Here, you can learn the difference between the two methods and which one to use. The code in this article is written in MicroPython.

RASPBERRY PI PICO EXTERNAL INTERRUPTS & BUTTON INTERFACING USING MICROPYTHON

The Raspberry Pi Pico & Pico W boards have 26 General Purpose Input/Output (GPIO) pins that can be accessed using the header pins All GPIO pins of Raspberry Pi Pico can be configured as either input or output. All GPIO pins can also be configured as external interrupts.

Polling vs. Interrupts

Polling and interrupts are the two methods used to interface external switches such as push-buttons, keypads, numpad, etc. to a microcontroller or microprocessor.

Polling is used when the application is not time-sensitive. Interrupts are used when a device needs the attention of a microcontroller instantly.

PollingInterrupt
Polling is when the CPU executes code sequentially to check if any change in state has occurred.In case of Interrupts, a device or a register notifies the CPU that it needs immediate attention.
CPU takes care of Polling.An interrupt handler handles interrupts.
Polling occurs at regular intervals due to the sequential execution of code.Interrupts can occur at any time.
Polling Vs Interrupt

Interrupts are handled by parts of software called Interrupt Service Routine(ISR). When an interrupt occurs, the CPU starts executing code within this routine. When the task in the routine is completed, the processor continues executing code from where it left off.

Interrupt Handling Diagram

Prerequisites For This Tutorial

  • A Raspberry Pi Pico development board
  • Push button(such as a momentary tactile switch)
  • Connecting wires and breadboard

Your Raspberry Pi Pico needs to be preloaded with a MicroPython UF2 file to program it in MicroPython. You can read our getting started guide for Raspberry Pi Pico where we show all the steps required to start programming Raspberry Pi Pico in MicroPython.

Read Pins using Polling in Raspberry Pi Pico

Let us first learn the polling method of reading an input pin state. If you are familiar with polling – jump to the interrupts section.

Connect a push-button to your Pico as shown in the diagram below.

Schematic of Raspberry Pi Pico with push-button. Designed in Fritzing.
Raspberry Pi Pico with push-button on a breadboard. Designed using Fritzing

Push buttons or tactile switches generally act as input devices. A push-button connected to the positive supply voltage of a microcontroller will be read as logical high (1) and a button connected to the ground will be read as logical low (0).

Raspberry Pi Pico has 26 easily accessible GPIOs explained in our in-depth article Raspberry Pi Pico Pinout Guide. You can use any of these GPIO pins to connect push-buttons. Here is the pin diagram of Raspberry Pi Pico W for reference.

raspberry pi pico w pinout
Raspberry Pi Pico W Pinout. Source: Datasheet

I will explain a simple MicroPython script to detect if an input pin is logical high or low.

The steps to upload code are explained here using Thonny IDE. You can also use any other IDE such as the uPyCraft IDE.

With the connections done as shown in the schematic above, connect Pico to your computer. Open Thonny IDE and set the interpreter to use MicroPython on Raspberry Pi Pico.

Create a new file (go toFile>New).

Paste the following script:

from machine import Pin
counter=0
pin = Pin(5, Pin.IN, Pin.PULL_UP)
while True:
    if pin.value()==0:
        print("Button Pressed")
        counter+=1
        print("Count={}".format(counter))Code language: Python (python)

Click on File>Save as and select the save location as Raspberry Pi Pico.

Thonny Save to

Name the code file as main.py.

main.py

Run the code by clicking the Run icon or by pressing the F5 key.

run-button-Thonny

Now, when you press the push-button, you should see output in the shell of Thonny IDE as shown below:

Alternatively, if you wish to detect a button press when the button is logically high instead of low, use PULL_DOWN instead of PULL_UP and modify your code as shown below:

from machine import Pin
counter=0
pin = Pin(5, Pin.IN, Pin.PULL_DOWN)
while True:
    if pin.value() is 1:
        counter+=1
        print("Button Pressed")
        print("Count={}".format(counter))Code language: Python (python)

Code Explained:

  • We import the Pin class from the machine module which is required to interact with the GPIO pins. Then we create a variable called counter to keep count of how many times the switch has been pressed.
from machine import Pin
counter=0Code language: JavaScript (javascript)
  • An object pin is created which takes 3 arguments. We designate GPIO 5 as an input with PULL_UP enabled. Pico can pull a pin to the positive voltage rail with an internal resistor. We need a pullup resistor because without it, a pin’s state might be noisy as it will be in a floating state.
pin = Pin(5, Pin.IN, Pin.PULL_UP)Code language: Python (python)
  • Inside the while loop, we see if pin.value() is equal to 0, which denotes that the pin has been connected to the ground(i.e. the pushbutton is pressed). If a button press is detected, we increment the counter variable and print the value of the counter.
if pin.value() is 1:
        counter+=1
        print("Button Pressed")
        print("Count={}".format(counter))Code language: Python (python)

Also read: Arduino vs MicroPython vs CircuitPython: Which One Will You Choose?

Debouncing a Pin in Raspberry Pi Pico

Did you notice any shortcomings in the script we wrote above? A single button press may increment the counter by many counts. This is due to the nature of mechanical contacts in a button and is called Bouncing.

Debouncing is a process of eliminating bouncing by using software or hardware. For example, a resistor-capacitor combination can be used for debouncing a switch. In software, the time elapsed between two consecutive button presses is measured, and if multiple inputs occur within a certain time interval, only one input is registered.

To implement debouncing, we need to modify our script so that push-button presses are detected only when there is a small amount of delay in between consecutive presses. Clicking a push-button rapidlywill not be registered as a valid input. For this, we shall use the time module in MicroPython to detect the time between button presses.

The time module contains the function time.ticks_ms() which is described as “Returns an increasing millisecond counter with an arbitrary reference point, that wraps around after some value.”

So the function returns a value of time (in milliseconds) that increments periodically, and we can use it to measure the time interval between two events.

Upload this modified script and save the file on Raspberry Pi Pico with a ‘.py’ filename extension:

from machine import Pin
import time
counter=0
debounce_time=0
pin = Pin(5, Pin.IN, Pin.PULL_UP)
while True:
    if ((pin.value() is 0) and (time.ticks_ms()-debounce_time) > 300):
        counter+=1
        debounce_time=time.ticks_ms()
        print("Button Pressed")
        print("Count={}".format(counter))Code language: Python (python)

We import the time module and create a variable called debounce_time. Then, we check if the time elapsed since the last button press is more than 300 milliseconds, using (time.ticks_ms()-debounce_time) > 300). If this condition is satisfied, then we increment the button press counter and set the debounce_time to the current value returned by time.ticks_ms(). This is very similar to using the millis() function in Arduino IDE.

External Interrupts in Raspberry Pi Pico

All GPIO pins in Raspberry Pi Pico support interrupts. The interrupts can be classified into three types:

  • Level High: An interrupt occurs when a pin is HIGH or at logic 1.
  • Level Low: An interrupt occurs when a pin is LOW or at logic 0.
  • Rising Edge: Interrupt occurs when a pin transitions from a LOW to HIGH.
  • Falling Edge: Interrupt occurs when a pin transitions from HIGH to LOW.

The level interrupts in RPi Pico do not latch, i.e. the interrupt becomes inactive as soon the GPIO changes its state from HIGH to LOW or vice versa.

Also read: Raspberry Pi Pico Serial Communication Example(MicroPython)

Using Push-Buttons to Trigger Interrupts

Let us now try to interface a pushbutton using an interrupt. For this example, we shall use the Falling Edge(or Edge Low) of a pin voltage to trigger an interrupt. The connections will be the same as we used in the previous example for Polling a button input i.e. connect a pushbutton between GPIO5 and GND pin.

  • In Thonny IDE, create a new project and upload the following interrupt example script to your Pi Pico.
from machine import Pin

pin = Pin(5,Pin.IN,Pin.PULL_UP)

def callback(pin):
    print("Interrupt has occured")

pin.irq(trigger=Pin.IRQ_RISING, handler=callback)Code language: Python (python)
  • Save and run the script.

When the script runs, you must see the line “Interrupt has occurred” displayed in your Thonny shell window when the pushbutton is pressed.

You might notice that the line “Interrupt has occurred” is printed multiple times with just a single button press. This is similar to the button-bouncing issue we discussed earlier in this article. We shall deal with this issue shortly below.

External Interrupt Code Explained

We first import the Pin class.

from machine import PinCode language: Python (python)

GPIO 5 is set as an input with internal PULL_UP enabled.

pin = Pin(5,Pin.IN,Pin.PULL_UP)

Next, we define a function called callback() to handle interrupts. The code inside this function should not perform any complex task, as it needs to hand over CPU usage to the main program quickly.

 def callback(pin):
    print("Interrupt has occured")Code language: Python (python)

To attach the interrupt to the pin, we use the irq() function.

pin.irq(trigger=Pin.IRQ_FALLING, handler=callback)

This function takes two arguments :

1. trigger : trigger can be of the following types-

  • Pin.IRQ_FALLING– interrupt on falling edge.
  • Pin.IRQ_RISING– interrupt on rising edge.
  • Pin.IRQ_LOW_LEVEL – interrupt on LOW level.(Not supported in MicroPython for Pico at the time of publishing this article.)
  • Pin.IRQ_HIGH_LEVEL - interrupt on HIGH level.(Not supported in MicroPython for Pico at the time of publishing this article.)
  • Pin.IRQ_FALLING | Pin.IRQ_RISING – interrupt on both the rising edge and the falling edge.

2. handler : handler specifies the function that will be called when an interrupt occurs.

Debouncing Interrupts & Controlling Output Pins

Debouncing is required even when using interrupts. Consider the example of edge-triggered interrupts (like Pin.IRQ_FALLING) that fire when a signal changes state. But a mechanical button doesn’t change state cleanly. It often bounces between HIGH and LOW several times within a few milliseconds before settling. Multiple falling edges can happen due to bouncing. Each of those can trigger the interrupt, causing your callback to run multiple times even if you only tapped the button once.

In the following example let us toggle the onboard LED of the Raspberry Pi Pico on the press of a push-button and use debouncing on the interrupt pin.

from machine import Pin
import time

# Variables for debounce timing and interrupt handling
interrupt_flag = 0
debounce_time = 0

# Set up button pin with pull-up resistor
button = Pin(5, Pin.IN, Pin.PULL_UP)

# Set up onboard LED on Raspberry Pi Pico
led = Pin("LED", Pin.OUT)

# Interrupt callback function
def callback(pin):
    global interrupt_flag, debounce_time
    current_time = time.ticks_ms()
    
    # Debounce check- only proceeds if 500ms has passed since the last trigger
    if time.ticks_diff(current_time, debounce_time) > 500:
        interrupt_flag = 1  # Set flag to handle in the main loop
        debounce_time = current_time  # Update last debounce time

# Attach interrupt to button (falling edge = button press)
button.irq(trigger=Pin.IRQ_FALLING, handler=callback)

# Main loop
while True:
    if interrupt_flag:
        print("Button press detected!")
        led.toggle()  # Toggle LED state
        interrupt_flag = 0  # Reset flag
    time.sleep(0.01)  # Small delay to avoid busy-waiting
Code language: Python (python)

Now, when the pushbutton is pressed, the onboard LED must change between ON and OFF states.

Polling or Interrupt? Which One Should You Use?

If you are interfacing external devices that are time-sensitive, you should use interrupts. If your program is simple, you can use polling to interface buttons. Some microcontrollers have few external interrupt-compatible pins. If you have to interface many buttons, such as a keypad, you sometimes have to opt for polling instead of using interrupts.

I hope you found this article on using external interrupts in Raspberry Pi Pico to be useful. Please share your thoughts in the comments below. Thank you for reading.

Also read:

Share this post


Posted

in

by

Comments

10 responses to “Raspberry Pi Pico Interrupts Tutorial- Examples in MicroPython”

  1. Afzal Avatar
    Afzal

    Excellent tutorial on the Interrupts. I look forward to see in C++ for Pi Pico.

  2. Steve Avatar
    Steve

    Good explanation but I think to avoid confusion it might be better to use a different name for pin, such as Button, since that is what the external physical function you are monitoring is.
    pin = Pin(5, Pin.IN, Pin.PULL_UP) is very software speak but
    Button = Pin(5, Pin.IN, Pin.PULL_UP) is more beginner understandable and separates the physical pin from the Pin function.

  3. David Avatar
    David

    I came upon this article while chasing down noise on my PICO project. It would be nice indeed to have a command such as Pin.IRQ_LOW_LEVEL or Pin.IRQ_HIGH_LEVEL as this would help with stray electrical noise that could be viewed by the PICO as a rising or falling edge. However, these triggers are not available in the PICO command set. Perhaps they are available in other RPi products. Simply getting up from a chair with synthetic fiber in winter will trigger the GPIO on the PICO (i.e. you don’t even have to touch anything close to the PICO.)

  4. Ron Keno Avatar
    Ron Keno

    Please port your Pico Interrupt Routine for use with the Arduino IDE 2.2.1.

    Thank you.

  5. Jim Avatar
    Jim

    Your debounce code appears to have a problem. If there are no more “falling edges” after the 500 ms timer, there will be nothing to trigger the code to set the interrupt_flag. Your debounce time starts at 0, so you are likely to get an un-debounced “interrupt” reported on the first falling edge.

    The idea behind software debounce is to wait until there are no changes for a set period of time, 500ms in your case. Noisy inputs that don’t settle down are managed by “voting”.

    Your debounce scheme needs to use a ONE_SHOT, 500 ms timer that is restarted with every falling edge reported. When that timer expires, the interrupt is reported. The next falling edge would restart the timer.

    1. Abhilekh Das Avatar
      Abhilekh Das

      Hello Jim.

      While using a ONE_SHOT timer mode as you mentioned, the interrupt will be reported after a delay when the timer expires. But external interrupts are often time-critical. The interrupt tasks must execute as soon the callback function is called. The ONE_SHOT timer mode can be used to implement debouncing in other ways though.

      Note that the “debounce_time” variable is updated every time an interrupt occurs. The code checks if the time elapsed since the last interrupt is more than 500 milliseconds, using (time.ticks_ms()-debounce_time) > 500). If this condition is satisfied,we set the debounce_time to the value returned by time.ticks_ms(), i.e. it stores the time when the interrupt occurs. So the “if” statement will always ensure the time between two interrupt events is atleast 500ms. You can try building the project yourself to see how the code works.

      Regards.

  6. Jay McGrath Avatar
    Jay McGrath

    Great article! I learned a lot! thank you
    Can this be applied to capturing “double-clicks”? I want to write a program that captures four click event-types: 1) single click, 2) double click, 3) single click but hold for 2 seconds, and 4) single click but hold for 5 seconds. My application will keep score of a card game I am working on, based on the sequence of these events.

    Can I use the teachings of this article to do the above? Or, will this only apply to single clicks? Many thanks

    1. Abhilekh Das Avatar
      Abhilekh Das

      You can implement a script that can identify different types of pushbutton clicks using interrupts or by simply using ‘if’ statements. Measure the time between two clicks for detecting double click or measure the time elapsed in a particular state to determine long press.

  7. David Avatar
    David

    Hi There,
    Nice article, I’m following along to learn more about interrupts.
    I have added a simple count of the number of button presses in the while loop.
    I notice if I hold the button for a short amount of time I can get it to count on the release (up) of the button as well as the press (down).
    It only does it sometimes. I think if I hold the button down for longer than half a second.
    What would I change to avoid this problem?
    (My button is on GPIO 14)
    Here is the slightly modified example
    Thanks
    David

    from machine import Pin
    import time

    interrupt_flag=0
    interrupt_count=0
    debounce_time=0

    pin = Pin(14,Pin.IN,Pin.PULL_UP)
    def callback(pin):
    global interrupt_flag, debounce_time

    if (time.ticks_ms()-debounce_time) > 500:
    interrupt_flag= 1
    debounce_time=time.ticks_ms()

    pin.irq(trigger=Pin.IRQ_FALLING, handler=callback)

    while True:
    if interrupt_flag is 1:
    interrupt_count=interrupt_count+1
    print(“Interrupt has occured, Interrupt Count=”+str(interrupt_count))
    interrupt_flag=0

  8. Stan Avatar
    Stan

    Very clear and informative tutorial. It was just what I needed for my simple project.

Leave a Reply

Your email address will not be published. Required fields are marked *