Skip to content

Commit 1686d75

Browse files
committed
Add GPIO interrupt section.
1 parent 01419cc commit 1686d75

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed

src/gpio_interrupt.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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

Comments
 (0)