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
}
}
}
#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.
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