|
| 1 | +# GPIO Interrupt |
| 2 | + |
| 3 | +Let's see how we can use interrupts on ESP32. |
| 4 | + |
| 5 | +## Declaring global mutable objects |
| 6 | + |
| 7 | +At least in case of `esp_hal`, interrupt status is not cleared automatically, so we'll need to do that ourselves. |
| 8 | +For that, we will need to access the button object inside the interrupt handler, so we have to make it global. |
| 9 | + |
| 10 | +Additionally, since we will be modifying this global object, we have to ensure it's done in a *safe* way, without |
| 11 | +causing race conditions. |
| 12 | + |
| 13 | +We'll need to wrap our `Input` with three different types, so this will be very confusing at first. The purpose of each |
| 14 | +type will be explained at the end of this section. |
| 15 | + |
| 16 | +Start by importing the necessary types: |
| 17 | + |
| 18 | +```rust |
| 19 | +use core::cell::RefCell; |
| 20 | +use critical_section::Mutex; |
| 21 | +``` |
| 22 | + |
| 23 | +Now, we can declare our global button: |
| 24 | + |
| 25 | +```rust |
| 26 | +static BUTTON: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None)); |
| 27 | +``` |
| 28 | + |
| 29 | +As you can see, our `Input` is wrapped in an `Option`, which is wrapped in a `RefCell` which in turn is wrapped |
| 30 | +in `Mutex`. |
| 31 | + |
| 32 | +Let's look into each one starting with the outermost. |
| 33 | + |
| 34 | +### Mutex |
| 35 | + |
| 36 | +[`Mutex`][3], when combined with the critical section, allows us to get an exclusive access to the stored type. |
| 37 | +Unlike the `Mutex` in Rust's `std` library, `critical_section::Mutex` does not provide ability to mutate the |
| 38 | +contained value. For this, we have to rely on the next type. |
| 39 | + |
| 40 | +### RefCell |
| 41 | + |
| 42 | +In short, [`RefCell`][4] is a run-time borrow checker. When we call `borrow` or `borrow_mut` methods, the type will check |
| 43 | +that the value is not already borrowed. If it is, we will get a panic. If we successfully get the mutable reference, |
| 44 | +we can safely mutate the stored value, because we know that no-one else has any reference to our object. |
| 45 | + |
| 46 | +### Option |
| 47 | + |
| 48 | +[`Option`][5] is required because we cannot create `Input` at the initialization time. We need to initialize the chip, |
| 49 | +configure peripherals etc. `Option` allows us to initialize an "empty" object first and fill it later when we're ready. |
| 50 | + |
| 51 | +## Handler function |
| 52 | + |
| 53 | +Next, let's look how we can define an interrupt handler function. |
| 54 | + |
| 55 | +```rust |
| 56 | +#[handler] |
| 57 | +fn button_handler() { |
| 58 | + info!("GPIO interrupt!"); |
| 59 | + critical_section::with(|cs| { |
| 60 | + BUTTON |
| 61 | + .borrow(cs) // Borrow the value in Mutex |
| 62 | + .borrow_mut() // Mutably borrow value in RefCell |
| 63 | + .as_mut() // Get mutable reference to the value in Option |
| 64 | + .unwrap() // Unwrap the Option<&mut T> |
| 65 | + .clear_interrupt(); // Clear the interrupt flag |
| 66 | + }); |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +> 💡 To make things a bit more concise, `Mutex` provides a `borrow_ref_mut` method that combines the `Mutex` borrow |
| 71 | +> and `RefCell` borrow into one function. We will use that function from here on. |
| 72 | +
|
| 73 | +> 💡 Note how our handler function is marked with the [`#[handler]`][1] attribute macro. |
| 74 | +> We can use [`cargo-expand`][2] tool to expand the macros and see what code is being produced. |
| 75 | +> Our handler function gets expanded to the code below: |
| 76 | +> ```rust |
| 77 | +> extern "C" fn __esp_hal_internal_button_handler() { |
| 78 | +> // Function body goes here |
| 79 | +> } |
| 80 | +> |
| 81 | +> #[allow(non_upper_case_globals)] |
| 82 | +> const button_handler: esp_hal::interrupt::InterruptHandler = |
| 83 | +> esp_hal::interrupt::InterruptHandler::new( |
| 84 | +> __esp_hal_internal_button_handler, |
| 85 | +> esp_hal::interrupt::Priority::min(), |
| 86 | +> ); |
| 87 | +> ``` |
| 88 | +
|
| 89 | +## Setting up interrupts |
| 90 | +
|
| 91 | +Next, we can register our handler to handle the GPIO interrupts. Add this to the setup part of your `main` function: |
| 92 | +
|
| 93 | +```rust |
| 94 | +let mut io = esp_hal::gpio::Io::new(peripherals.IO_MUX); |
| 95 | +io.set_interrupt_handler(button_handler); |
| 96 | +``` |
| 97 | +
|
| 98 | +Finally, we start to listen to the button events and move the button object into our global state. |
| 99 | +We will use a "rising edge" event, which means that the interrupt will be triggered on button release. |
| 100 | + |
| 101 | +Add this code right after the existing declaration of `button`. |
| 102 | + |
| 103 | +```rust |
| 104 | +critical_section::with(|cs| { |
| 105 | + button.listen(Event::RisingEdge); |
| 106 | + BUTTON.borrow_ref_mut(cs).replace(button); |
| 107 | +}); |
| 108 | +``` |
| 109 | + |
| 110 | +## Experiment |
| 111 | + |
| 112 | +Try changing the event type to rising edge. Do you observe any difference? |
| 113 | + |
| 114 | + |
| 115 | +[1]: https://docs.espressif.com/projects/rust/esp-hal/1.0.0-rc.0/esp32c6/esp_hal/attr.handler.html |
| 116 | +[2]: https://github.com/dtolnay/cargo-expand |
| 117 | +[3]: https://docs.rs/critical-section/latest/critical_section/struct.Mutex.html |
| 118 | +[4]: https://doc.rust-lang.org/nightly/std/cell/struct.RefCell.html |
| 119 | +[5]: https://doc.rust-lang.org/nightly/std/option/index.html |
0 commit comments