Skip to content

Commit 22389ca

Browse files
committed
Add rust05-gpio exercise
1 parent 99393b6 commit 22389ca

File tree

4 files changed

+263
-0
lines changed

4 files changed

+263
-0
lines changed

rust05-gpio/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "gpio-example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["staticlib"]
8+
9+
[profile.release]
10+
# Setting the panic mode has little effect on the built code (as Rust on RIOT
11+
# supports no unwinding), but setting it allows builds on native without using
12+
# the nightly-only lang_items feature.
13+
panic = "abort"
14+
15+
[dependencies]
16+
embedded-hal = "1.0.0"
17+
riot-wrappers = { version = "0.8", features = [ "set_panic_handler", "panic_handler_format" ] }
18+
19+
rust_riotmodules = { path = "../RIOT/sys/rust_riotmodules/" }

rust05-gpio/Makefile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
APPLICATION = gpio_example
2+
3+
# If no BOARD is found in the environment, use this default:
4+
BOARD ?= feather-nrf52840-sense
5+
6+
# This has to be the absolute path to the RIOT base directory:
7+
RIOTBASE ?= $(CURDIR)/../RIOT
8+
9+
USEMODULE += periph_gpio
10+
USEMODULE += periph_gpio_irq
11+
12+
# Enable the milliseconds timer.
13+
USEMODULE += ztimer
14+
USEMODULE += ztimer_msec
15+
USEMODULE += ztimer_sec
16+
17+
# Comment this out to disable code in RIOT that does safety checking
18+
# which is not needed in a production environment but helps in the
19+
# development process:
20+
DEVELHELP ?= 1
21+
22+
# Some workarounds are needed in order to get the tutorial running on
23+
# some computers.
24+
-include ../lab_workarounds.mk
25+
26+
# Change this to 0 show compiler invocation lines by default:
27+
QUIET ?= 1
28+
29+
# Tell the build system to use the Rust crate here
30+
FEATURES_REQUIRED += rust_target
31+
APPLICATION_RUST_MODULE = gpio_example
32+
BASELIBS += $(APPLICATION_RUST_MODULE).module
33+
34+
include $(RIOTBASE)/Makefile.include

rust05-gpio/README.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# GPIOs
2+
3+
General Purpose Input/Outputs (GPIOs) are a peripheral that allows
4+
microcontrollers to interact with the physical world. They are
5+
commonly known as pins. As the name suggests, they can be either configured as
6+
digital inputs or digital outputs. That is, they can read a digital value from
7+
the outside, or present a digital value to the outside (which translates in
8+
presenting a voltage of either 0V for digital `0` or commonly 3.3V for `1`).
9+
10+
They are useful for a huge range of tasks. As outputs, they could drive an
11+
indication LED or a circuit like a valve or a lock (special electrical
12+
adaption is needed in this case, such as a transistor, as the GPIOs don't
13+
handle high currents). As inputs, they can be used to read buttons, switches
14+
or digital sensors (e.g. a presence or a window sensor).
15+
16+
RIOT provides the `periph_gpio` module to interact with the peripheral. It
17+
exposes a simple API that allows to use GPIOs independently of the underlying
18+
platform. In this example we will make use of the most common functionalities
19+
of the GPIO module.
20+
21+
To change to this directory from a different exercise, use the following command in the terminal.
22+
23+
```sh
24+
$ cd ../rust05-gpios
25+
```
26+
27+
## Task 1
28+
29+
Drive an LED using the GPIO API. So far we have operated on LEDs using a series
30+
of LED abstractions provided by the board. What these macros do under the hood is to
31+
write particular registers that control the GPIO peripheral.
32+
33+
**1. Outside the `main` function, `use` the gpio module to type less later:**
34+
35+
```rust
36+
use riot_wrappers::gpio::{GPIO, OutputMode};
37+
```
38+
39+
**2. Inside the `main` function define the led0 GPIO pin, and configure it as an output.**
40+
**In the `feather-nrf52840-sense` we are currently using, the LED0 is connected to the**
41+
**Port 1, Pin 9:**
42+
43+
```rust
44+
let mut led0 = GPIO::from_port_and_pin(1, 9)
45+
.expect("Pin should be available")
46+
.configure_as_output(OutputMode::Out)
47+
.expect("Pin should be usable as output");
48+
```
49+
50+
**3. The LEDs on the board are on when the GPIO outputs `1`.**
51+
**Inside the `main` function, periodically set the GPIO to high (turning the LED on) and low (turning the LED off):**
52+
53+
```rust
54+
loop {
55+
led0.set_high();
56+
Clock::msec().sleep(Duration::from_millis(200));
57+
led0.set_low();
58+
Clock::msec().sleep(Duration::from_millis(800));
59+
}
60+
```
61+
62+
**5. Build and flash the application:**
63+
64+
```sh
65+
$ make all flash
66+
```
67+
68+
## GPIO interrupts
69+
70+
We could constantly read a GPIO input to check if the value has changed, but this
71+
consumes CPU cycles and energy. Instead, we can configure a GPIO to generate an
72+
event: an **interrupt** (we will see more about threads and interrupts in the
73+
next task). Interrupts are generated when certain pre-established conditions
74+
are met. We can configure a GPIO to generate an interrupt when the external
75+
value changes to `0`, to `1`, or whenever there is a change.
76+
77+
*Unfortunately, there are no safe Rust wrappers for this functionality yet.*
78+
*Thus, we jump right into how C functions are accessed in unsafe Rust --*
79+
*an exercise that under ideal conditions is needed rarely, but hey:*
80+
*learning how to do it by hand is a way towards having the high-level wrappers around!*
81+
82+
## Task 2
83+
84+
Turn the LED1 on whenever a button is pressed, and turn it off when the button has been released.
85+
Use an interrupt to detect the value change.
86+
87+
**1. The same way as done in the previous task, initialize the GPIO pin for the LED1.**
88+
**LED1 is connected to the Port 1, Pin 10.**
89+
**Define `led1` in the main function.**
90+
91+
**2. Add crates we will need:**
92+
93+
```
94+
$ cargo add riot-sys
95+
$ cargo add static-cell
96+
```
97+
98+
**and add**
99+
100+
```rust
101+
use static_cell::StaticCell;
102+
use riot_wrappers::gpio::OutputGPIO;
103+
```
104+
105+
**below the use of task 1.**
106+
107+
We will use riot-sys to gain access to the low-level C functions,
108+
and static-cell to safely obtain a mutable reference to static memory.
109+
110+
**2. Define a struct to transfer ownership of the used pins to the callback function.**
111+
**While this could be done with statics just as well, mutable statics are discouraged in Rust.**
112+
113+
```rust
114+
struct PinsForInterrupt {
115+
button: GPIO,
116+
led1: OutputGPIO,
117+
}
118+
```
119+
120+
**3. Write a callback function, which will be called when an interrupt occurs.**
121+
**Whenever the button is pressed, the button's pin value is read to `0`.**
122+
**As all we can pass across the C API for interrupts is a pointer,**
123+
**we define that this pointer has the semantics of a `&'static mut PinsForInterrupt` pointer,**
124+
**and cast it back accordingly.**
125+
126+
```rust
127+
extern "C" fn button_callback(arg: *mut riot_sys::libc::c_void) {
128+
let pins = unsafe { &mut *(arg as *mut PinsForInterrupt) };
129+
130+
// On a regular input pin we could run `.is_low()`, but the `riot_wrappers::gpio::OutputGPIO`
131+
// type would reconfigure the pin in its constructor, so we even read it manually.
132+
if unsafe { riot_sys::gpio_read(pins.button.to_c()) } == 0 {
133+
pins.led1.set_high();
134+
}
135+
else {
136+
pins.led1.set_low();
137+
}
138+
}
139+
```
140+
141+
**3. Define the GPIO pin connected to the user button on your board, and store its address for initialization.**
142+
**The user button is connected to the Port 1, Pin 2.**
143+
144+
```rust
145+
let button = GPIO::from_port_and_pin(1, 2)
146+
.expect("Pin should be available");
147+
let button_address = button.to_c();
148+
```
149+
150+
**4. Define a local static, move the pins into it, and get a static mutable reference to it.**
151+
152+
```rust
153+
static PINS: StaticCell<PinsForInterrupt> = StaticCell::new();
154+
let pins: &mut PinsForInterrupt = PINS.init(PinsForInterrupt {
155+
led1,
156+
button,
157+
});
158+
```
159+
160+
The documentation of [static_cell](https://docs.rs/static_cell/latest/static_cell/) explains well when it is convenient,
161+
and which alternatives there are.
162+
Particularly tempting alternatives here are
163+
just unsafely using mutable statics (we know what we are doing … but we may miss details when refactoring later),
164+
or using a RIOT Mutex (but then we'd have to do more error handling, because if a mutex is locked, it can not be awaited in an interrupt).
165+
166+
**5. Initialize the button from its address, configuring the callback to be called on any change, and passing in control of the pins.**
167+
168+
```rust
169+
unsafe {
170+
riot_sys::gpio_init_int(button_address, riot_sys::gpio_mode_t_GPIO_IN_PU, riot_sys::gpio_flank_t_GPIO_BOTH, Some(button_callback), pins as *mut _ as *mut riot_sys::libc::c_void);
171+
};
172+
```
173+
174+
Note that the safety of this relies on GPIO interrupts not preempting themselves:
175+
While RIOT can be used with priorized interrupts, the same interrupt will not trigger a jump while it is being executed.
176+
177+
**6. Build and flash the application.**

rust05-gpio/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-FileCopyrightText: Christian Amsüss <[email protected]>
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
#![no_std]
4+
5+
use riot_wrappers::riot_main;
6+
use riot_wrappers::println;
7+
use riot_wrappers::ztimer::Clock;
8+
use core::time::Duration;
9+
10+
// [TASK 1: Add convenience `use` line]
11+
12+
// [TASK 2: Add convenience `use` line]
13+
14+
extern crate rust_riotmodules;
15+
16+
riot_main!(main);
17+
18+
// [TASK 2: Define the interrupt's struct]
19+
20+
fn main() {
21+
// Startup delay to ensure the terminal is connected
22+
Clock::sec().sleep(Duration::from_secs(5));
23+
24+
println!("GPIOs example.");
25+
26+
// [TASK 1: Initialize led0 here]
27+
28+
// [TASK 2: Initialize led1 and button here]
29+
30+
// [TASK 1: Loop here]
31+
}
32+
33+
// [TASK 2: Define button_callback here]

0 commit comments

Comments
 (0)