From eb7a985aa2b63932c7704208c755c6482042bceb Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 16 Aug 2024 17:45:42 +0200 Subject: [PATCH] Add rust06-threads example --- rust06-threads/Cargo.toml | 21 +++++++ rust06-threads/Makefile | 31 ++++++++++ rust06-threads/README.md | 127 ++++++++++++++++++++++++++++++++++++++ rust06-threads/src/lib.rs | 40 ++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 rust06-threads/Cargo.toml create mode 100644 rust06-threads/Makefile create mode 100644 rust06-threads/README.md create mode 100644 rust06-threads/src/lib.rs diff --git a/rust06-threads/Cargo.toml b/rust06-threads/Cargo.toml new file mode 100644 index 0000000..6408fda --- /dev/null +++ b/rust06-threads/Cargo.toml @@ -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" diff --git a/rust06-threads/Makefile b/rust06-threads/Makefile new file mode 100644 index 0000000..99a08a9 --- /dev/null +++ b/rust06-threads/Makefile @@ -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 diff --git a/rust06-threads/README.md b/rust06-threads/README.md new file mode 100644 index 0000000..b58eb88 --- /dev/null +++ b/rust06-threads/README.md @@ -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 +// , we must provide the pointer +// inside a &'static mut +static STATIC_POINTER: StaticCell = 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. diff --git a/rust06-threads/src/lib.rs b/rust06-threads/src/lib.rs new file mode 100644 index 0000000..a225285 --- /dev/null +++ b/rust06-threads/src/lib.rs @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Christian Amsüss +// 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)); + } +}