cleanup docs and add channel synchronization example
This commit is contained in:
parent
b867245894
commit
ff5f5021fb
8 changed files with 187 additions and 56 deletions
4
docs/README.md
Normal file
4
docs/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# embassy docs
|
||||||
|
|
||||||
|
The documentation hosted at [https://embassy.dev/book](https://embassy.dev/book). Building the documentation requires
|
||||||
|
cloning the [embassy-book](https://github.com/embassy-rs/embassy-book) repository and following the instructions.
|
|
@ -3,11 +3,11 @@
|
||||||
** xref:project_structure.adoc[Project Structure]
|
** xref:project_structure.adoc[Project Structure]
|
||||||
** xref:new_project.adoc[Starting a new Embassy project]
|
** xref:new_project.adoc[Starting a new Embassy project]
|
||||||
** xref:best_practices.adoc[Best Practices]
|
** xref:best_practices.adoc[Best Practices]
|
||||||
* xref:layer_by_layer.adoc[Bare metal to async]
|
|
||||||
* xref:runtime.adoc[Executor]
|
* xref:runtime.adoc[Executor]
|
||||||
* xref:delaying_a_task.adoc[Delaying a Task]
|
* xref::time_keeping.adoc[Time-keeping]
|
||||||
* xref:sharing_peripherals.adoc[Sharing peripherals between tasks]
|
* xref:sharing_peripherals.adoc[Sharing peripherals]
|
||||||
* xref:hal.adoc[HAL]
|
* xref:hal.adoc[HAL]
|
||||||
|
** xref:layer_by_layer.adoc[Anatomy of an async HAL]
|
||||||
** xref:nrf.adoc[nRF]
|
** xref:nrf.adoc[nRF]
|
||||||
** xref:stm32.adoc[STM32]
|
** xref:stm32.adoc[STM32]
|
||||||
* xref:bootloader.adoc[Bootloader]
|
* xref:bootloader.adoc[Bootloader]
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
= Delaying a Task
|
|
||||||
|
|
||||||
In an embedded program, delaying a task is one of the most common actions taken. In an event loop, delays will need to be inserted to ensure
|
|
||||||
that other tasks have a chance to run before the next iteration of the loop is called, if no other I/O is performed. Embassy provides an abstraction
|
|
||||||
to delay the current task for a specified interval of time.
|
|
||||||
|
|
||||||
Timing is serviced by the `embassy::time::Timer` struct, which provides two timing methods.
|
|
||||||
|
|
||||||
`Timer::at` creates a future that completes at the specified `Instant`, relative to the system boot time.
|
|
||||||
`Timer::after` creates a future that completes after the specified `Duration`, relative to when the future was created.
|
|
||||||
|
|
||||||
An example of a delay is provided as follows:
|
|
||||||
|
|
||||||
[,rust]
|
|
||||||
----
|
|
||||||
use embassy::executor::{task, Executor};
|
|
||||||
use embassy::time::{Duration, Timer};
|
|
||||||
|
|
||||||
#[task]
|
|
||||||
/// Task that ticks periodically
|
|
||||||
async fn tick_periodic() -> ! {
|
|
||||||
loop {
|
|
||||||
rprintln!("tick!");
|
|
||||||
// async sleep primitive, suspends the task for 500ms.
|
|
||||||
Timer::after(Duration::from_millis(500)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
|
@ -18,7 +18,7 @@ my-project
|
||||||
|- rust-toolchain.toml
|
|- rust-toolchain.toml
|
||||||
----
|
----
|
||||||
|
|
||||||
=== .cargo/config.toml
|
== .cargo/config.toml
|
||||||
|
|
||||||
This directory/file describes what platform you're on, and configures link:https://github.com/probe-rs/probe-rs[probe-rs] to deploy to your device.
|
This directory/file describes what platform you're on, and configures link:https://github.com/probe-rs/probe-rs[probe-rs] to deploy to your device.
|
||||||
|
|
||||||
|
@ -36,17 +36,17 @@ target = "thumbv6m-none-eabi" # <-change for your platform
|
||||||
DEFMT_LOG = "trace" # <- can change to info, warn, or error
|
DEFMT_LOG = "trace" # <- can change to info, warn, or error
|
||||||
----
|
----
|
||||||
|
|
||||||
=== build.rs
|
== build.rs
|
||||||
|
|
||||||
This is the build script for your project. It links defmt (what is defmt?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example].
|
This is the build script for your project. It links defmt (what is defmt?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example].
|
||||||
|
|
||||||
=== Cargo.toml
|
== Cargo.toml
|
||||||
|
|
||||||
This is your manifest file, where you can configure all of the embassy components to use the features you need.
|
This is your manifest file, where you can configure all of the embassy components to use the features you need.
|
||||||
|
|
||||||
TODO: someone should exhaustively describe every feature for every component!
|
TODO: someone should exhaustively describe every feature for every component!
|
||||||
|
|
||||||
=== memory.x
|
== memory.x
|
||||||
|
|
||||||
This file outlines the flash/ram usage of your program. It is especially useful when using link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] on an nRF5x.
|
This file outlines the flash/ram usage of your program. It is especially useful when using link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] on an nRF5x.
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ MEMORY
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
=== rust-toolchain.toml
|
== rust-toolchain.toml
|
||||||
|
|
||||||
This file configures the rust version and configuration to use.
|
This file configures the rust version and configuration to use.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
= Sharing peripherals between tasks
|
= Sharing peripherals between tasks
|
||||||
|
|
||||||
Often times, more than one task needs access to the same resource (pin, communication interface, etc.). The following example shows how to use the on-board LED on a Raspberry Pi Pico board by two tasks simultaneously.
|
Often times, more than one task needs access to the same resource (pin, communication interface, etc.). Embassy provides many different synchronization primitives in the link:https://crates.io/crates/embassy-sync[embassy-sync] crate.
|
||||||
|
|
||||||
|
The following examples shows different ways to use the on-board LED on a Raspberry Pi Pico board by two tasks simultaneously.
|
||||||
|
|
||||||
|
== Sharing using a Mutex
|
||||||
|
|
||||||
|
Using mutual exclusion is the simplest way to share a peripheral.
|
||||||
|
|
||||||
[,rust]
|
[,rust]
|
||||||
----
|
----
|
||||||
|
@ -29,13 +35,12 @@ async fn main(spawner: Spawner) {
|
||||||
let dt = 100 * 1_000_000;
|
let dt = 100 * 1_000_000;
|
||||||
let k = 1.003;
|
let k = 1.003;
|
||||||
|
|
||||||
unwrap!(spawner.spawn(toggle(&LED, Duration::from_nanos(dt))));
|
unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt))));
|
||||||
unwrap!(spawner.spawn(toggle_slightly_slower(
|
unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64))));
|
||||||
&LED,
|
|
||||||
Duration::from_nanos((dt as f64 * k) as u64)
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A pool size of 2 means you can spawn two instances of this task.
|
||||||
|
#[embassy_executor::task(pool_size = 2)]
|
||||||
async fn toggle_led(led: &'static LedType, delay: Duration) {
|
async fn toggle_led(led: &'static LedType, delay: Duration) {
|
||||||
let mut ticker = Ticker::every(delay);
|
let mut ticker = Ticker::every(delay);
|
||||||
loop {
|
loop {
|
||||||
|
@ -48,31 +53,74 @@ async fn toggle_led(led: &'static LedType, delay: Duration) {
|
||||||
ticker.next().await;
|
ticker.next().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn toggle(led: &'static LedType, delay: Duration) {
|
|
||||||
toggle_led(led, delay).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn toggle_slightly_slower(led: &'static LedType, delay: Duration) {
|
|
||||||
toggle_led(led, delay).await
|
|
||||||
}
|
|
||||||
----
|
----
|
||||||
|
|
||||||
The structure facilitating access to the resource is the defined `LedType`.
|
The structure facilitating access to the resource is the defined `LedType`.
|
||||||
|
|
||||||
== Why so complicated
|
=== Why so complicated
|
||||||
|
|
||||||
Unwrapping the layers gives insight into why each one is needed.
|
Unwrapping the layers gives insight into why each one is needed.
|
||||||
|
|
||||||
=== `Mutex<RawMutexType, T>`
|
==== `Mutex<RawMutexType, T>`
|
||||||
|
|
||||||
The mutex is there so if one task gets the resource first and begins modifying it, all other tasks wanting to write will have to wait (the `led.lock().await` will return immediately if no task has locked the mutex, and will block if it is accessed somewhere else).
|
The mutex is there so if one task gets the resource first and begins modifying it, all other tasks wanting to write will have to wait (the `led.lock().await` will return immediately if no task has locked the mutex, and will block if it is accessed somewhere else).
|
||||||
|
|
||||||
=== `Option<T>`
|
==== `Option<T>`
|
||||||
|
|
||||||
The `LED` variable needs to be defined outside the main task as references accepted by tasks need to be `'static`. However, if it is outside the main task, it cannot be initialised to point to any pin, as the pins themselves are not initialised. Thus, it is set to `None`.
|
The `LED` variable needs to be defined outside the main task as references accepted by tasks need to be `'static`. However, if it is outside the main task, it cannot be initialised to point to any pin, as the pins themselves are not initialised. Thus, it is set to `None`.
|
||||||
|
|
||||||
=== `Output<AnyPin>`
|
==== `Output<AnyPin>`
|
||||||
|
|
||||||
To indicate that the pin will be set to an Output. The `AnyPin` could have been `embassy_rp::peripherals::PIN_25`, however this option lets the `toggle_led` function be more generic.
|
To indicate that the pin will be set to an Output. The `AnyPin` could have been `embassy_rp::peripherals::PIN_25`, however this option lets the `toggle_led` function be more generic.
|
||||||
|
|
||||||
|
== Sharing using a Channel
|
||||||
|
|
||||||
|
A channel is another way to ensure exclusive access to a resource. Using a channel is great in the cases where the access can happen at a later point in time, allowing you to enqueue operations and do other things.
|
||||||
|
|
||||||
|
[,rust]
|
||||||
|
----
|
||||||
|
use defmt::*;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::gpio;
|
||||||
|
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
|
||||||
|
use embassy_sync::channel::{Channel, Sender};
|
||||||
|
use embassy_time::{Duration, Ticker};
|
||||||
|
use gpio::{AnyPin, Level, Output};
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
enum LedState {
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
static CHANNEL: Channel<ThreadModeRawMutex, LedState, 64> = Channel::new();
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High);
|
||||||
|
|
||||||
|
let dt = 100 * 1_000_000;
|
||||||
|
let k = 1.003;
|
||||||
|
|
||||||
|
unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt))));
|
||||||
|
unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos((dt as f64 * k) as u64))));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match CHANNEL.receive().await {
|
||||||
|
LedState::Toggle => led.toggle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pool size of 2 means you can spawn two instances of this task.
|
||||||
|
#[embassy_executor::task(pool_size = 2)]
|
||||||
|
async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) {
|
||||||
|
let mut ticker = Ticker::every(delay);
|
||||||
|
loop {
|
||||||
|
control.send(LedState::Toggle).await;
|
||||||
|
ticker.next().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
This example replaces the Mutex with a Channel, and uses another task (the main loop) to drive the LED. The advantage of this approach is that only a single task references the peripheral, separating concerns. However, using a Mutex has a lower overhead and might be necessary if you need to ensure
|
||||||
|
that the operation is ecompleted before continuing to do other work in your task.
|
||||||
|
|
60
docs/modules/ROOT/pages/time_keeping.adoc
Normal file
60
docs/modules/ROOT/pages/time_keeping.adoc
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
= Time-keeping
|
||||||
|
|
||||||
|
In an embedded program, delaying a task is one of the most common actions taken. In an event loop, delays will need to be inserted to ensure
|
||||||
|
that other tasks have a chance to run before the next iteration of the loop is called, if no other I/O is performed. Embassy provides abstractions
|
||||||
|
to delay the current task for a specified interval of time.
|
||||||
|
|
||||||
|
The interface for time-keeping in Embassy is handled by the link:https://crates.io/crates/embassy-time[embassy-time] crate. The types can be used with the internal
|
||||||
|
timer queue in link:https://crates.io/crates/embassy-executor[embassy-executor] or a custom timer queue implementation.
|
||||||
|
|
||||||
|
== Timer
|
||||||
|
|
||||||
|
The `embassy::time::Timer` type provides two timing methods.
|
||||||
|
|
||||||
|
`Timer::at` creates a future that completes at the specified `Instant`, relative to the system boot time.
|
||||||
|
`Timer::after` creates a future that completes after the specified `Duration`, relative to when the future was created.
|
||||||
|
|
||||||
|
An example of a delay is provided as follows:
|
||||||
|
|
||||||
|
[,rust]
|
||||||
|
----
|
||||||
|
use embassy::executor::{task, Executor};
|
||||||
|
use embassy::time::{Duration, Timer};
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
/// Task that ticks periodically
|
||||||
|
async fn tick_periodic() -> ! {
|
||||||
|
loop {
|
||||||
|
rprintln!("tick!");
|
||||||
|
// async sleep primitive, suspends the task for 500ms.
|
||||||
|
Timer::after(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
== Delay
|
||||||
|
|
||||||
|
The `embassy::time::Delay` type provides an implementation of the link:https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/index.html[embedded-hal] and
|
||||||
|
link:https://docs.rs/embedded-hal-async/latest/embedded_hal_async/delay/index.html[embedded-hal-async] traits. This can be used for drivers
|
||||||
|
that expect a generic delay implementation to be provided.
|
||||||
|
|
||||||
|
An example of how this can be used:
|
||||||
|
|
||||||
|
[,rust]
|
||||||
|
----
|
||||||
|
use embassy::executor::{task, Executor};
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
/// Task that ticks periodically
|
||||||
|
async fn tick_periodic() -> ! {
|
||||||
|
loop {
|
||||||
|
rprintln!("tick!");
|
||||||
|
// async sleep primitive, suspends the task for 500ms.
|
||||||
|
generic_delay(embassy::time::Delay).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generic_delay<D: embedded_hal_async::delay::DelayNs>(delay: D) {
|
||||||
|
delay.delay_ms(500).await;
|
||||||
|
}
|
||||||
|
----
|
|
@ -1,5 +1,5 @@
|
||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
runner = "probe-rs run --chip RP2040"
|
runner = "probe-rs run --chip RP2040 --probe 1209:4853:0e0039001450563641333620"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||||
|
|
47
examples/rp/src/bin/blinky_two_channels.rs
Normal file
47
examples/rp/src/bin/blinky_two_channels.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
/// This example demonstrates how to access a given pin from more than one embassy task
|
||||||
|
/// The on-board LED is toggled by two tasks with slightly different periods, leading to the
|
||||||
|
/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar
|
||||||
|
/// to interference and the 'beats' you can hear if you play two frequencies close to one another
|
||||||
|
/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats)
|
||||||
|
use defmt::*;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::gpio;
|
||||||
|
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
|
||||||
|
use embassy_sync::channel::{Channel, Sender};
|
||||||
|
use embassy_time::{Duration, Ticker};
|
||||||
|
use gpio::{AnyPin, Level, Output};
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
enum LedState {
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
static CHANNEL: Channel<ThreadModeRawMutex, LedState, 64> = Channel::new();
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High);
|
||||||
|
|
||||||
|
let dt = 100 * 1_000_000;
|
||||||
|
let k = 1.003;
|
||||||
|
|
||||||
|
unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt))));
|
||||||
|
unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos((dt as f64 * k) as u64))));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match CHANNEL.receive().await {
|
||||||
|
LedState::Toggle => led.toggle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task(pool_size = 2)]
|
||||||
|
async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) {
|
||||||
|
let mut ticker = Ticker::every(delay);
|
||||||
|
loop {
|
||||||
|
control.send(LedState::Toggle).await;
|
||||||
|
ticker.next().await;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue