Skip to content

Commit

Permalink
Add rust05-gpio exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
chrysn committed Aug 16, 2024
1 parent 99393b6 commit 22389ca
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 0 deletions.
19 changes: 19 additions & 0 deletions rust05-gpio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "gpio-example"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib"]

[profile.release]
# Setting the panic mode has little effect on the built code (as Rust on RIOT
# supports no unwinding), but setting it allows builds on native without using
# the nightly-only lang_items feature.
panic = "abort"

[dependencies]
embedded-hal = "1.0.0"
riot-wrappers = { version = "0.8", features = [ "set_panic_handler", "panic_handler_format" ] }

rust_riotmodules = { path = "../RIOT/sys/rust_riotmodules/" }
34 changes: 34 additions & 0 deletions rust05-gpio/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
APPLICATION = gpio_example

# If no BOARD is found in the environment, use this default:
BOARD ?= feather-nrf52840-sense

# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../RIOT

USEMODULE += periph_gpio
USEMODULE += periph_gpio_irq

# Enable the milliseconds timer.
USEMODULE += ztimer
USEMODULE += ztimer_msec
USEMODULE += ztimer_sec

# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1

# Some workarounds are needed in order to get the tutorial running on
# some computers.
-include ../lab_workarounds.mk

# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1

# Tell the build system to use the Rust crate here
FEATURES_REQUIRED += rust_target
APPLICATION_RUST_MODULE = gpio_example
BASELIBS += $(APPLICATION_RUST_MODULE).module

include $(RIOTBASE)/Makefile.include
177 changes: 177 additions & 0 deletions rust05-gpio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# GPIOs

General Purpose Input/Outputs (GPIOs) are a peripheral that allows
microcontrollers to interact with the physical world. They are
commonly known as pins. As the name suggests, they can be either configured as
digital inputs or digital outputs. That is, they can read a digital value from
the outside, or present a digital value to the outside (which translates in
presenting a voltage of either 0V for digital `0` or commonly 3.3V for `1`).

They are useful for a huge range of tasks. As outputs, they could drive an
indication LED or a circuit like a valve or a lock (special electrical
adaption is needed in this case, such as a transistor, as the GPIOs don't
handle high currents). As inputs, they can be used to read buttons, switches
or digital sensors (e.g. a presence or a window sensor).

RIOT provides the `periph_gpio` module to interact with the peripheral. It
exposes a simple API that allows to use GPIOs independently of the underlying
platform. In this example we will make use of the most common functionalities
of the GPIO module.

To change to this directory from a different exercise, use the following command in the terminal.

```sh
$ cd ../rust05-gpios
```

## Task 1

Drive an LED using the GPIO API. So far we have operated on LEDs using a series
of LED abstractions provided by the board. What these macros do under the hood is to
write particular registers that control the GPIO peripheral.

**1. Outside the `main` function, `use` the gpio module to type less later:**

```rust
use riot_wrappers::gpio::{GPIO, OutputMode};
```

**2. Inside the `main` function define the led0 GPIO pin, and configure it as an output.**
**In the `feather-nrf52840-sense` we are currently using, the LED0 is connected to the**
**Port 1, Pin 9:**

```rust
let mut led0 = GPIO::from_port_and_pin(1, 9)
.expect("Pin should be available")
.configure_as_output(OutputMode::Out)
.expect("Pin should be usable as output");
```

**3. The LEDs on the board are on when the GPIO outputs `1`.**
**Inside the `main` function, periodically set the GPIO to high (turning the LED on) and low (turning the LED off):**

```rust
loop {
led0.set_high();
Clock::msec().sleep(Duration::from_millis(200));
led0.set_low();
Clock::msec().sleep(Duration::from_millis(800));
}
```

**5. Build and flash the application:**

```sh
$ make all flash
```

## GPIO interrupts

We could constantly read a GPIO input to check if the value has changed, but this
consumes CPU cycles and energy. Instead, we can configure a GPIO to generate an
event: an **interrupt** (we will see more about threads and interrupts in the
next task). Interrupts are generated when certain pre-established conditions
are met. We can configure a GPIO to generate an interrupt when the external
value changes to `0`, to `1`, or whenever there is a change.

*Unfortunately, there are no safe Rust wrappers for this functionality yet.*
*Thus, we jump right into how C functions are accessed in unsafe Rust --*
*an exercise that under ideal conditions is needed rarely, but hey:*
*learning how to do it by hand is a way towards having the high-level wrappers around!*

## Task 2

Turn the LED1 on whenever a button is pressed, and turn it off when the button has been released.
Use an interrupt to detect the value change.

**1. The same way as done in the previous task, initialize the GPIO pin for the LED1.**
**LED1 is connected to the Port 1, Pin 10.**
**Define `led1` in the main function.**

**2. Add crates we will need:**

```
$ cargo add riot-sys
$ cargo add static-cell
```

**and add**

```rust
use static_cell::StaticCell;
use riot_wrappers::gpio::OutputGPIO;
```

**below the use of task 1.**

We will use riot-sys to gain access to the low-level C functions,
and static-cell to safely obtain a mutable reference to static memory.

**2. Define a struct to transfer ownership of the used pins to the callback function.**
**While this could be done with statics just as well, mutable statics are discouraged in Rust.**

```rust
struct PinsForInterrupt {
button: GPIO,
led1: OutputGPIO,
}
```

**3. Write a callback function, which will be called when an interrupt occurs.**
**Whenever the button is pressed, the button's pin value is read to `0`.**
**As all we can pass across the C API for interrupts is a pointer,**
**we define that this pointer has the semantics of a `&'static mut PinsForInterrupt` pointer,**
**and cast it back accordingly.**

```rust
extern "C" fn button_callback(arg: *mut riot_sys::libc::c_void) {
let pins = unsafe { &mut *(arg as *mut PinsForInterrupt) };

// On a regular input pin we could run `.is_low()`, but the `riot_wrappers::gpio::OutputGPIO`
// type would reconfigure the pin in its constructor, so we even read it manually.
if unsafe { riot_sys::gpio_read(pins.button.to_c()) } == 0 {
pins.led1.set_high();
}
else {
pins.led1.set_low();
}
}
```

**3. Define the GPIO pin connected to the user button on your board, and store its address for initialization.**
**The user button is connected to the Port 1, Pin 2.**

```rust
let button = GPIO::from_port_and_pin(1, 2)
.expect("Pin should be available");
let button_address = button.to_c();
```

**4. Define a local static, move the pins into it, and get a static mutable reference to it.**

```rust
static PINS: StaticCell<PinsForInterrupt> = StaticCell::new();
let pins: &mut PinsForInterrupt = PINS.init(PinsForInterrupt {
led1,
button,
});
```

The documentation of [static_cell](https://docs.rs/static_cell/latest/static_cell/) explains well when it is convenient,
and which alternatives there are.
Particularly tempting alternatives here are
just unsafely using mutable statics (we know what we are doing … but we may miss details when refactoring later),
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).

**5. Initialize the button from its address, configuring the callback to be called on any change, and passing in control of the pins.**

```rust
unsafe {
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);
};
```

Note that the safety of this relies on GPIO interrupts not preempting themselves:
While RIOT can be used with priorized interrupts, the same interrupt will not trigger a jump while it is being executed.

**6. Build and flash the application.**
33 changes: 33 additions & 0 deletions rust05-gpio/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Christian Amsüss <[email protected]>
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![no_std]

use riot_wrappers::riot_main;
use riot_wrappers::println;
use riot_wrappers::ztimer::Clock;
use core::time::Duration;

// [TASK 1: Add convenience `use` line]

// [TASK 2: Add convenience `use` line]

extern crate rust_riotmodules;

riot_main!(main);

// [TASK 2: Define the interrupt's struct]

fn main() {
// Startup delay to ensure the terminal is connected
Clock::sec().sleep(Duration::from_secs(5));

println!("GPIOs example.");

// [TASK 1: Initialize led0 here]

// [TASK 2: Initialize led1 and button here]

// [TASK 1: Loop here]
}

// [TASK 2: Define button_callback here]

0 comments on commit 22389ca

Please sign in to comment.