Exercise A: Interrupt-Driven Button

~20 min

Starting Point

Open the examples/interrupt-button/ project in the workshop repo. This is a working GPIO interrupt example that toggles an LED when a button is pressed.

Read through src/main.rs carefully. This is the most complex code pattern in the workshop. Map it to what you learned in the slides:

  1. Shared statestatic BUTTON: Mutex<RefCell<Option<Input>>>
  2. Moving the peripheralBUTTON.borrow_ref_mut(cs).replace(button)
  3. The handler#[handler] fn gpio_handler()
  4. Clearing the interruptbutton.clear_interrupt()

Your Task

Step 1: Adapt and Run

Update the pin assignments for your uFerris board and flash:

cd examples/interrupt-button
cargo build --release
espflash flash target/riscv32imc-unknown-none-elf/release/interrupt-button --monitor

Press the button. The LED should toggle.

Step 2: Explore Trigger Configurations

The example uses one specific edge trigger. Time to explore what else exists.

Look up the `Event` enum in esp-hal's GPIO module. What variants are available?

Try changing the trigger:

  1. Switch from FallingEdge to RisingEdge — what changes? Does the LED toggle on press or release now?

  2. Try AnyEdge — the LED should toggle on both press and release. What happens?

  3. Think about it: which trigger would you use for:

    • A light switch (toggle once per press)?
    • A camera shutter (action on press, not release)?
    • Measuring how long a button is held?

Step 3: Flag-Based Approach

The example toggles the LED inside the interrupt handler. But best practice says: keep handlers short, do work in the main loop.

Refactor to use a flag:

#![allow(unused)]
fn main() {
use core::sync::atomic::{AtomicBool, Ordering};

static BUTTON_PRESSED: AtomicBool = AtomicBool::new(false);
}
  1. In the handler: set the flag to true (and clear the interrupt)
  2. In the main loop: check the flag, toggle the LED, reset the flag
Look up `AtomicBool` in the Rust standard library docs. What methods does it provide? Try `store()`, `load()`, and `swap()`.

Step 4: Compare

Think about the difference between your Part 2 polling code and this interrupt code:

  • What can the main loop do now that it couldn't before?
  • When would you choose polling over interrupts?
  • When would you choose interrupts over polling?
```rust
#[handler]
fn gpio_handler() {
    critical_section::with(|cs| {
        let mut button = BUTTON.borrow_ref_mut(cs);
        if let Some(button) = button.as_mut() {
            button.clear_interrupt();
        }
    });
    BUTTON_PRESSED.store(true, Ordering::Relaxed);
}

// In main loop:
loop {
    if BUTTON_PRESSED.swap(false, Ordering::Relaxed) {
        led.toggle();
    }
    // CPU could do other work here, or sleep
}