Raspberry Pi Pico Interrupts Tutorial- Examples in MicroPython

In this article, learn how to use interrupts in Raspberry Pi Pico & Pico W. 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 interrupt

Polling and Interrupts are the two methods used to interface external switches such as push-buttons, keypads, Numpad, etc.

ⓘ Push buttons or tactile switches act as input devices for a microcontroller and we can read their status. A button connected to the positive supply voltage of a microcontroller will be read as logical-high(1) and a button attached to the ground will be read as logical-low(0).

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 or Pico W.
  • 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 Buttons using Polling in Raspberry Pi Pico

Let us first look at the polling method of reading a button. Connect a push button to your Pico as shown in the diagram below.

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

The Raspberry Pi Pico has 26 easily accessible GPIOs which is 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

We shall write a script to detect if the button is HIGH or LOW. Pico can pull a pin to the positive voltage with an internal resistor. Why do we need a pullup resistor? Because without the pull-up configured, a pin will give noisy input as it will be in a floating state.

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 your 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 an output as shown below:

Alternatively, if you wish to detect a button press when the button is 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.
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?

Advertisement

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 button presses are detected with a small amount of delay in between. For this, we shall use the time module in MicroPython. It 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 random value of time (in milliseconds) that increments up to a certain value, 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 code.

External Interrupts in Raspberry Pi Pico & Pico W

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 Raspberry Pi Pico 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

interrupt_flag=0
pin = Pin(5,Pin.IN,Pin.PULL_UP)
def callback(pin):
    global interrupt_flag
    interrupt_flag=1

pin.irq(trigger=Pin.IRQ_FALLING, handler=callback)
while True:
    if interrupt_flag is 1:
        print("Interrupt has occured")
        interrupt_flag=0Code 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.

External Interrupt Code Explained

We first import the Pin class and initialize a global variable called interrupt_flag to 0. We will use this variable to keep track of the occurrence of interrupts. Global variables in MicroPython can be accessed by all functions. Then we create a pin object of the Pin class. GPIO 5 is set as an input with PULL_UP enabled.

from machine import Pin
interrupt_flag=0
pin = Pin(5,Pin.IN,Pin.PULL_UP)Code language: Python (python)

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. We set the interrupt_flag variable as 1. Note that we need to use the global keyword when we change a global variable inside a function.

 def callback(pin):
    global interrupt_flag
    interrupt_flag=1Code language: Python (python)

To attach the interrupt to the pin, we use the irq() function. This function takes two arguments :

trigger : trigger can be of the following types-

(1) Pin.IRQ_FALLING– interrupt on falling edge.

(2) Pin.IRQ_RISING– interrupt on rising edge.

(3) Pin.IRQ_LOW_LEVEL – interrupt on LOW level.(Not supported in MicroPython for Pico at the time of publishing this article.)

(4) Pin.IRQ_HIGH_LEVEL - interrupt on HIGH level.(Not supported in MicroPython for Pico at the time of publishing this article.)

(5) Pin.IRQ_FALLING | Pin.IRQ_RISING – interrupt on both the rising edge and the falling edge.

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

pin.irq(trigger=Pin.IRQ_FALLING, handler=callback)Code language: Python (python)

In the while loop, we continuously check the value of the interrupt_flag variable. If this value is detected as ‘1’, that means an interrupt has occurred. We reset the variable to ‘0’ so that the variable can be set again when an interrupt occurs.

You might have noticed that the line “Interrupt has occurred” is printed in multiple lines with just a single button press. This is similar to the button-bouncing case that we discussed earlier in this article. We shall solve this problem in the next step.

Using External Interrupt to Toggle an LED

In the following example let us try to toggle the onboard LED of the Raspberry Pi Pico, and debounce the external interrupt button input. The code should be self-explanatory as if we have already discussed similar code earlier in this article.

# Source: Electrocredible.com, Language: MicroPython
from machine import Pin
import time
interrupt_flag=0
debounce_time=0
pin = Pin(5, Pin.IN, Pin.PULL_UP)
led = Pin("LED", Pin.OUT)
count=0

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_flag=0
        print("Interrupt Detected")
        led.toggle()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 I 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:


Posted

in

by

Comments

6 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.

Leave a Reply

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