# Interrupt Essentials ## Polling vs. Interrupts ``` POLLING: INTERRUPTS: ┌──────────────┐ ┌──────────────┐ │ loop { │ │ loop { │ │ if pressed │ ← CPU busy │ // sleep │ ← CPU idle │ do_thing │ checking │ // or do │ until event │ } │ constantly │ // other │ │ } │ │ // work │ └──────────────┘ └──────┬───────┘ │ INTERRUPT! ┌──────▼───────┐ │ handler() { │ ← CPU wakes up │ do_thing │ handles event │ } │ goes back └──────────────┘ ``` --- ## GPIO Interrupt Triggers When should the interrupt fire? | Trigger | When It Fires | |---------|--------------| | `Event::FallingEdge` | Pin goes from HIGH → LOW (button press with pull-up) | | `Event::RisingEdge` | Pin goes from LOW → HIGH (button release with pull-up) | | `Event::AnyEdge` | Any transition — both press and release | --- ## The Interrupt Pattern in Rust Interrupts introduce a challenge: the interrupt handler and your main code need to **share state**. In Rust, this means dealing with `Send`, `Sync`, and interior mutability. ### The Standard Pattern ```rust use core::cell::RefCell; use critical_section::Mutex; // 1. Shared state: wrap in Mutex
>> static BUTTON: Mutex
>> = Mutex::new(RefCell::new(None)); // 2. In main: move the peripheral into the shared state critical_section::with(|cs| { BUTTON.borrow_ref_mut(cs).replace(button); }); // 3. The interrupt handler #[handler] fn gpio_handler() { critical_section::with(|cs| { let mut button = BUTTON.borrow_ref_mut(cs); if let Some(button) = button.as_mut() { // Handle the interrupt button.clear_interrupt(); } }); } ``` ### Key Pieces | Piece | Purpose | |-------|---------| | `Mutex
>>` | Safe shared access between main code and interrupt handler | | `critical_section::with` | Temporarily disables interrupts to safely access shared state | | `#[handler]` | Marks a function as an interrupt handler (esp-hal attribute) | | `clear_interrupt()` | **Must** be called — tells the hardware you've handled the event | ```
The Embedded Rustacean · Rust Week 2026