Building the Interrupt Handler

The goal of this exercise is to handle the interrupt that fires if the BOOT button is pushed. This exercise involves working with C bindings to the ESP-IDF and other unsafe operations, as well as non-typical rust documentation. In a first step we will go line by line to build this interrupt handler.

You can find a skeleton code for this exercise in advanced/button-interrupt/src/ You can find the solution for this exercise in advanced/button-interrupt/examples/


  1. Configure the button (GPIO 9) with a c struct gpio_config_tthe following settings:
    • input mode
    • pull up
    • interrupt on positive edge

The struct has the following fields:

  • pin_bit_mask: represents the Pin number, the value 1 shifted by the number of the pin.

  • mode: sets the mode of the pin, it can have the following settings:

    • gpio_mode_t_GPIO_MODE_INPUT
    • gpio_mode_t_GPIO_MODE_OUTPUT
    • gpio_mode_t_GPIO_MODE_DISABLE // disable gpio
    • gpio_mode_t_GPIO_MODE_OUTPUT_OD // open drain output
    • gpio_mode_t_GPIO_MODE_INPUT_OUTPUT // input and output
    • gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD // open drain input and output
  • pull_up_en: true.into(), if the GPIO is pulled up,

  • pull_down_en: true.into(), if the GPIO is pulled down,

  • intr_type: sets the interrupt type, it can have the following settings:

    • gpio_int_type_t_GPIO_INTR_ANYEDGE // interrupt at any edge
    • gpio_int_type_t_GPIO_INTR_DISABLE // interrupt disabled
    • gpio_int_type_t_GPIO_INTR_NEGEDGE // interrupt at negative edge
    • gpio_int_type_t_GPIO_INTR_POSEDGE // interrupt at positive edge

They are constants with numbers representing the bit that must be set in the corresponding register.

  1. Write the configuration into the register with unsafe extern "C" fn gpio_config. This needs to happen in the unsafe block. To make these FFI calls we can use the macro esp!($Cfunktion).

  2. Install a generic GPIO interrupt handler with unsafe extern "C" fn gpio_install_isr_service. This function takes ESP_INTR_FLAG_IRAM as argument.

  3. Create a static mut that holds the queue handle we are going to get from xQueueGenericCreate. This is a number that uniquely identifies one particular queue, as opposed to any of the other queues in our program. The queue storage itself if managed by the Operating System.

fn main() {
static mut EVENT_QUEUE: Option<QueueHandle_t> = None;
  1. Create the event queue using pub unsafe extern "C" fn xQueueGenericCreate. This lets us safely pass events from an interrupt routine to our main thread.

fn main() {
  1. Add a function which that will be called whenever there is a GPIO interrupt on our button pin. We put this function in a special block of RAM (iram0), so it will still be available even if the external flash is busy doing something else (like filesystem work). The function needs to get the queue handle from EVENT_QUEUE and call the xQueueGiveFromISR function with a std::ptr::null_mut() - the objects in our queue are of size zero, so we don't actually need a 'thing' to put on the queue. Instead, the act of pushing a 'nothing' is enough to wake up the other end!

fn main() {
#[link_section = ".iram0.text"]
unsafe extern "C" fn button_interrupt(_: *mut c_void) {
    xQueueGiveFromISR(EVENT_QUEUE.unwrap(), std::ptr::null_mut());

If the interrupt fires, an event is added to the queue.

  1. Pass the function we just wrote to the generic GPIO interrupt handler we registered earlier, along with the number of the GPIO pin that should cause this function to be executed.

fn main() {
  1. Inside a loop, wait until the queue has an item in it. That is, until the button_interrupt function puts something in the queue.

fn main() {
let res = xQueueReceive(EVENT_QUEUE.unwrap(), ptr::null_mut(), QUEUE_WAIT_TICKS);
  1. Handle the value of res, so that "Button pushed!" is logged, if the button is pushed.

  2. Run the program and push the BOOT button, so see how it works!