Skip to content

Commit

Permalink
Add rust06-threads example
Browse files Browse the repository at this point in the history
  • Loading branch information
chrysn committed Aug 16, 2024
1 parent 6eca700 commit eb7a985
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 0 deletions.
21 changes: 21 additions & 0 deletions rust06-threads/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "thread-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]
riot-sys = "0.7.12"
riot-wrappers = { version = "0.8", features = ["set_panic_handler", "panic_handler_format"] }

rust_riotmodules = { path = "../RIOT/sys/rust_riotmodules/" }
static_cell = "2.1.0"
switch-hal = "0.4.0"
31 changes: 31 additions & 0 deletions rust06-threads/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# name of your application
APPLICATION = thread_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 += 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 = thread_example
BASELIBS += $(APPLICATION_RUST_MODULE).module

include $(RIOTBASE)/Makefile.include
127 changes: 127 additions & 0 deletions rust06-threads/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Threads

Threads are functions designed to be scheduled and executed by the CPU
independently. An example of a thread is the `main()` function.
In single-core MCUs only one thread can have control of the CPU at a given time.
The scheduler is in charge of deciding which thread takes control, depending on
its state and priority. Usually threads are waiting for some event to occur,
and are not 'runnable' (e.g., waiting for a certain time to pass, a packet to
arrive, an external interrupt or an event to be posted on a queue).

To create threads, we first define a function with the following signature:

```rust
fn thread_handler()
```

The function content is what will be executed whenever our thread is running.
The function gets called from the beginning only once: at the moment of creation.
That is why, commonly, thread handlers contain a loop which would run forever.

In addition to a handler function, threads require memory: a stack. There local
variables and other information will be stored when performing function calls.

To create a new thread, we provide stack memory, a function, and spawn a thread:

```C
fn thread_handler() {
// Some task for our thread
}

let my_thread = riot_wrappers::thread::spawn(
// Memory stack
thread_stack,
// The thread handler function
&mut thread_handler,
// Human readable name of the thread
c"my_thread",
// Priority of the thread. The lower value, the higher priority
(riot_sys::THREAD_PRIORITY_MAIN - 1) as _,
// Thread flags. By default, enable stack memory usage measurements.
(riot_sys::THREAD_CREATE_STACKTEST) as _,
);
```

RIOT scheduler executes a thread until one of the following conditions occurs:
- The thread finishes
- The thread waits for an incoming event (e.g event queue)
- An interrupt source is triggered (button, GPIO pin, timer, etc). In this case,
the scheduler resumes execution in the highest priority thread that does not have
pending events.

For more information visit the
[thread documentation page](https://doc.riot-os.org/group__core__thread.html).

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

```sh
$ cd ../riot06-threads
```

## A note on interrupts

We usually don't want to perform lengthy tasks from interrupt contexts, as they
will block the execution of all threads. What is commonly done instead, is to
use some signalization mechanism to offload a task to a thread. A good choice is
to use events, as they are allocated by the sender and we are certain that they
will not get lost (as opposed to messages in RIOT). We will see how to utilize
events on the next exercise.

## Task 1

Create a "blinky" thread that toggles the LED1 every 250 milliseconds.

**1. Create a handler function for your thread.**
**As we want the task to be repeated indefinitely, we include an infinite loop:**


```rust
fn blinky_handler() {
let mut led1 = riot_wrappers::led::LED::<1>::new();

loop {
println!(
"Thread {}\n",
riot_wrappers::thread::get_pid()
.get_name()
.unwrap_or("unnamed")
);

led1.on().unwrap();
Clock::msec().sleep(Duration::from_millis(20));
led1.off().unwrap();
Clock::msec().sleep(Duration::from_millis(180));
}
}
```

**2. Define a stack for your thread:**
```rust
use riot_sys::THREAD_STACKSIZE_DEFAULT;
use static_cell::StaticCell;
static THREAD_STACK: StaticCell<[u8; THREAD_STACKSIZE_DEFAULT as usize]> = StaticCell::new();
```

**3. Take possession of the stack, and spawn your thread.**
```rust
let thread_stack = THREAD_STACK.init_with(|| [0; THREAD_STACKSIZE_DEFAULT as usize]);
// Due to a bug in the spawn function
// <https://github.com/RIOT-OS/rust-riot-wrappers/issues/99>, we must provide the pointer
// inside a &'static mut
static STATIC_POINTER: StaticCell<fn()> = StaticCell::new();
let static_pointer = STATIC_POINTER.init(blinky_handler);
let my_thread = riot_wrappers::thread::spawn(
thread_stack,
static_pointer,
c"blinky",
(riot_sys::THREAD_PRIORITY_MAIN - 1) as _,
(riot_sys::THREAD_CREATE_STACKTEST) as _,
);
```

**4. Build and flash your application. Open a serial communication:**
```sh
$ make all flash term
```

You should see messages from both threads and the LEDs blinking at different rates.
40 changes: 40 additions & 0 deletions rust06-threads/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Christian Amsüss <[email protected]>
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![no_std]

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

// We are using the LED through a generic "on"/"off" interface.
use switch_hal::OutputSwitch;

extern crate rust_riotmodules;

riot_main!(main);

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

println!("Thread example.");

/* [TASK 1: create the thread here] */

let mut led0 = riot_wrappers::led::LED::<0>::new();

loop {
println!(
"Thread {}\n",
riot_wrappers::thread::get_pid()
.get_name()
.unwrap_or("unnamed")
);

led0.on().unwrap();
Clock::msec().sleep(Duration::from_millis(100));
led0.off().unwrap();
Clock::msec().sleep(Duration::from_millis(900));
}
}

0 comments on commit eb7a985

Please sign in to comment.