Individual ISR per GPIO

The convenient way

Imagine you would like to detect a button press in your project. The most effective way to do this is using interrupts. This way your main function can keep doing what it should do and you still can be sure to detect the button press.
All you need to do is to enable interrupts on the button pin and write a callback function to handle the interrupt.

Example:
The button is connected to the raspberry pico on pin 16. Please note that I use external pull-up and debounce circuitry to keep the code short. To enable the interrupt on pin 16 and define the callback, you can use function gpio_set_irq_enabled_with_callback. Please note that this function is a convenience function, which just calls all relevant functions for you.
It is implemented in file pico-sdk/src/rp2_common/hardware_gpio/gpio.c
All it does is:

gpio_set_irq_callback(callback);
gpio_set_irq_enabled(gpio, events, enabled);
if (enabled) irq_set_enabled(IO_IRQ_BANK0, true);

gpio_set_irq_enabled_with_callback takes four parameter:

uint gpio The button pin
uint32_t events The interrupt events you want to trigger the interrupt
bool enabled If true, the interrupr is enabled, if false it's disabled
gpio_irq_callback_t callback Function pointer to your ISR

Events can be:

GPIO_IRQ_LEVEL_LOW IRQ when the GPIO pin is a logical 0.
GPIO_IRQ_LEVEL_HIGH IRQ when the GPIO pin is a logical 1.
GPIO_IRQ_EDGE_FALL IRQ when the GPIO has transitioned from a logical 1 to a logical 0.
GPIO_IRQ_EDGE_RISE IRQ when the GPIO has transitioned from a logical 0 to a logical 1.

Multiple events can be activated by concatenating them with a logic OR.

gpio_set_irq_enabled_with_callback(PIN_BUTTON, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, irq_callback_button);

The function header of your callback function shall be

void irq_callback_button(uint gpio, uint32_t event_mask);

Please note that you can name the function according to your needs. There is no naming convention for callbacks.
If you have just this one interrupt pin, you can directly implement whatever you need to implement. But keep it short. You shall never printf or sleep in a interrupt service routine.
In case you have, let's say two buttons connected to pins 15 and 16 you shall check the gpio, which triggered the IRQ, in your callback.

void irq_callback_button(uint gpio, uint32_t event_mask)
{
 if(gpio == PIN_BUTTON1)
 {
  if(event_mask & GPIO_IRQ_EDGE_RISE)
  {
   // Put here what shall happen on a rising edge
  } else if(event_mask & GPIO_IRQ_EDGE_FALL)
  {
   // Put here what shall happen on a falling edge
  }
 }
 if(gpio == PIN_BUTTON2)
 {
  if(event_mask & GPIO_IRQ_EDGE_RISE)
  {
   // Put here what shall happen on a rising edge
  } else if(event_mask & GPIO_IRQ_EDGE_FALL)
  {
   // Put here what shall happen on a falling edge
  }
 }
}

Here's the complete file

#include <pico/stdlib.h>
#include <iostream>
#include <stdint.h>

const uint8_t PIN_BUTTON1 = 15;
const uint8_t PIN_BUTTON2 = 16;

void irq_callback_button(uint gpio, uint32_t event_mask)
{
 if(gpio == PIN_BUTTON1)
 {
  if((event_mask & GPIO_IRQ_EDGE_FALL) | (event_mask & GPIO_IRQ_EDGE_RISE))
  {
   gpio_put(PICO_DEFAULT_LED_PIN, true);
  }
 }

 if(gpio == PIN_BUTTON2)
 {
   if((event_mask & GPIO_IRQ_EDGE_FALL) | (event_mask & GPIO_IRQ_EDGE_RISE))
  {
   gpio_put(PICO_DEFAULT_LED_PIN, false);
  }
 }
}


int main()
{
 stdio_init_all();

 gpio_init(PICO_DEFAULT_LED_PIN);
 gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
 gpio_put(PICO_DEFAULT_LED_PIN, false);
 gpio_set_irq_enabled_with_callback(PIN_BUTTON1, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, irq_callback_button);
 gpio_set_irq_enabled_with_callback(PIN_BUTTON2, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, irq_callback_button);

 while(true)
 {
  std::cout << ("on ");
  sleep_ms(200);
  std::cout << ("off ");
  sleep_ms(200);
 }
 return 0;
}

If you'd like to add another GPIO to be handled by the ISR callback, use

void gpio_set_irq_enabled (uint gpio, uint32_t event_mask, bool enabled)

to just enable the IRQ of that new pin. I.e. both gpio IRQs will be handled by one IRQ handler.

Having multiple ISRs

Say you have a project with two buttons and two rotary encoder and you want separate ISRs for the buttons and the encoders, you can not go with gpio_set_irq_enabled_with_callback as it sets the the ISR for all GPIOs on one core. Instead you need to use gpio_add_raw_irq_handler.

void gpio_add_raw_irq_handler (uint gpio, irq_handler_t handler)

This function disables the default IRQ handler for the specified pin and activates the defined IRQ handler. Please note that the IRQ handler must clear the interrupt itself. Additionally please not that the function header of a raw IRQ handler differs from the one needed for gpio_set_irq_enabled_with_callback.
Prototype of a IRQ handler:

void my_irq_handler(void) {
 if (gpio_get_irq_event_mask(my_gpio_num) & my_gpio_event_mask) {
  gpio_acknowledge_irq(my_gpio_num, my_gpio_event_mask);
  // handle the IRQ
 }
}

Here is a complete example code for two buttons. Each of them is handled by its on IRQ handler:

#include <pico/stdlib.h>
#include <iostream>
#include <stdint.h>

const uint8_t PIN_BUTTON1 = 15;
const uint8_t PIN_BUTTON2 = 16;

bool state = true;

void irq_handler_1(void)
{
 if (gpio_get_irq_event_mask(PIN_BUTTON1) & (GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE)) {
  gpio_acknowledge_irq(PIN_BUTTON1, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE);
  gpio_put(PICO_DEFAULT_LED_PIN, true);
  state = true;
 }
}

void irq_handler_2(void)
{
 if (gpio_get_irq_event_mask(PIN_BUTTON2) & (GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE)) {
  gpio_acknowledge_irq(PIN_BUTTON2, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE);
  gpio_put(PICO_DEFAULT_LED_PIN, false);
  state = false;
 }
}

int main()
{
 stdio_init_all();

 gpio_init(PICO_DEFAULT_LED_PIN);
 gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
 gpio_put(PICO_DEFAULT_LED_PIN, false);

 gpio_add_raw_irq_handler_masked(1u << PIN_BUTTON2, irq_handler_2);
 gpio_set_irq_enabled(PIN_BUTTON2, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);

 gpio_add_raw_irq_handler_masked(1u << PIN_BUTTON1, irq_handler_1);
 gpio_set_irq_enabled(PIN_BUTTON1, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);

 irq_set_enabled(IO_IRQ_BANK0, true);

 while(true)
 {
  std::cout << state << " ";
  sleep_ms(400);
 }
 return 0;
}

Last edit: 2025-02-04