diff options
| author | Ulf Lilleengen <[email protected]> | 2024-01-10 09:48:09 +0100 |
|---|---|---|
| committer | Ulf Lilleengen <[email protected]> | 2024-01-10 09:52:46 +0100 |
| commit | ff5f5021fb4f469f72b3645760238ebe1c4d99af (patch) | |
| tree | 9ecdc1b4876a3d566a8135aa88ae3de95a78f650 /docs | |
| parent | b8672458947b4d48b5d5c950a60edd677d87e29e (diff) | |
cleanup docs and add channel synchronization example
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/README.md | 4 | ||||
| -rw-r--r-- | docs/modules/ROOT/nav.adoc | 6 | ||||
| -rw-r--r-- | docs/modules/ROOT/pages/delaying_a_task.adoc | 28 | ||||
| -rw-r--r-- | docs/modules/ROOT/pages/project_structure.adoc | 10 | ||||
| -rw-r--r-- | docs/modules/ROOT/pages/sharing_peripherals.adoc | 86 | ||||
| -rw-r--r-- | docs/modules/ROOT/pages/time_keeping.adoc | 60 |
6 files changed, 139 insertions, 55 deletions
diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..0bf3a6c89 --- /dev/null +++ b/docs/README.md | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | # embassy docs | ||
| 2 | |||
| 3 | The documentation hosted at [https://embassy.dev/book](https://embassy.dev/book). Building the documentation requires | ||
| 4 | cloning the [embassy-book](https://github.com/embassy-rs/embassy-book) repository and following the instructions. | ||
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index b692c44e1..44b0eddb9 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc | |||
| @@ -3,11 +3,11 @@ | |||
| 3 | ** xref:project_structure.adoc[Project Structure] | 3 | ** xref:project_structure.adoc[Project Structure] |
| 4 | ** xref:new_project.adoc[Starting a new Embassy project] | 4 | ** xref:new_project.adoc[Starting a new Embassy project] |
| 5 | ** xref:best_practices.adoc[Best Practices] | 5 | ** xref:best_practices.adoc[Best Practices] |
| 6 | * xref:layer_by_layer.adoc[Bare metal to async] | ||
| 7 | * xref:runtime.adoc[Executor] | 6 | * xref:runtime.adoc[Executor] |
| 8 | * xref:delaying_a_task.adoc[Delaying a Task] | 7 | * xref::time_keeping.adoc[Time-keeping] |
| 9 | * xref:sharing_peripherals.adoc[Sharing peripherals between tasks] | 8 | * xref:sharing_peripherals.adoc[Sharing peripherals] |
| 10 | * xref:hal.adoc[HAL] | 9 | * xref:hal.adoc[HAL] |
| 10 | ** xref:layer_by_layer.adoc[Anatomy of an async HAL] | ||
| 11 | ** xref:nrf.adoc[nRF] | 11 | ** xref:nrf.adoc[nRF] |
| 12 | ** xref:stm32.adoc[STM32] | 12 | ** xref:stm32.adoc[STM32] |
| 13 | * xref:bootloader.adoc[Bootloader] | 13 | * xref:bootloader.adoc[Bootloader] |
diff --git a/docs/modules/ROOT/pages/delaying_a_task.adoc b/docs/modules/ROOT/pages/delaying_a_task.adoc deleted file mode 100644 index 3171e3515..000000000 --- a/docs/modules/ROOT/pages/delaying_a_task.adoc +++ /dev/null | |||
| @@ -1,28 +0,0 @@ | |||
| 1 | = Delaying a Task | ||
| 2 | |||
| 3 | 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 | ||
| 4 | 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 | ||
| 5 | to delay the current task for a specified interval of time. | ||
| 6 | |||
| 7 | Timing is serviced by the `embassy::time::Timer` struct, which provides two timing methods. | ||
| 8 | |||
| 9 | `Timer::at` creates a future that completes at the specified `Instant`, relative to the system boot time. | ||
| 10 | `Timer::after` creates a future that completes after the specified `Duration`, relative to when the future was created. | ||
| 11 | |||
| 12 | An example of a delay is provided as follows: | ||
| 13 | |||
| 14 | [,rust] | ||
| 15 | ---- | ||
| 16 | use embassy::executor::{task, Executor}; | ||
| 17 | use embassy::time::{Duration, Timer}; | ||
| 18 | |||
| 19 | #[task] | ||
| 20 | /// Task that ticks periodically | ||
| 21 | async fn tick_periodic() -> ! { | ||
| 22 | loop { | ||
| 23 | rprintln!("tick!"); | ||
| 24 | // async sleep primitive, suspends the task for 500ms. | ||
| 25 | Timer::after(Duration::from_millis(500)).await; | ||
| 26 | } | ||
| 27 | } | ||
| 28 | ---- \ No newline at end of file | ||
diff --git a/docs/modules/ROOT/pages/project_structure.adoc b/docs/modules/ROOT/pages/project_structure.adoc index 3e6008ec4..61ffd05a6 100644 --- a/docs/modules/ROOT/pages/project_structure.adoc +++ b/docs/modules/ROOT/pages/project_structure.adoc | |||
| @@ -18,7 +18,7 @@ my-project | |||
| 18 | |- rust-toolchain.toml | 18 | |- rust-toolchain.toml |
| 19 | ---- | 19 | ---- |
| 20 | 20 | ||
| 21 | === .cargo/config.toml | 21 | == .cargo/config.toml |
| 22 | 22 | ||
| 23 | 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. | 23 | 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. |
| 24 | 24 | ||
| @@ -36,17 +36,17 @@ target = "thumbv6m-none-eabi" # <-change for your platform | |||
| 36 | DEFMT_LOG = "trace" # <- can change to info, warn, or error | 36 | DEFMT_LOG = "trace" # <- can change to info, warn, or error |
| 37 | ---- | 37 | ---- |
| 38 | 38 | ||
| 39 | === build.rs | 39 | == build.rs |
| 40 | 40 | ||
| 41 | 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]. | 41 | 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]. |
| 42 | 42 | ||
| 43 | === Cargo.toml | 43 | == Cargo.toml |
| 44 | 44 | ||
| 45 | This is your manifest file, where you can configure all of the embassy components to use the features you need. | 45 | This is your manifest file, where you can configure all of the embassy components to use the features you need. |
| 46 | 46 | ||
| 47 | TODO: someone should exhaustively describe every feature for every component! | 47 | TODO: someone should exhaustively describe every feature for every component! |
| 48 | 48 | ||
| 49 | === memory.x | 49 | == memory.x |
| 50 | 50 | ||
| 51 | 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. | 51 | 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. |
| 52 | 52 | ||
| @@ -63,7 +63,7 @@ MEMORY | |||
| 63 | } | 63 | } |
| 64 | ---- | 64 | ---- |
| 65 | 65 | ||
| 66 | === rust-toolchain.toml | 66 | == rust-toolchain.toml |
| 67 | 67 | ||
| 68 | This file configures the rust version and configuration to use. | 68 | This file configures the rust version and configuration to use. |
| 69 | 69 | ||
diff --git a/docs/modules/ROOT/pages/sharing_peripherals.adoc b/docs/modules/ROOT/pages/sharing_peripherals.adoc index 41f467942..fcba0e27b 100644 --- a/docs/modules/ROOT/pages/sharing_peripherals.adoc +++ b/docs/modules/ROOT/pages/sharing_peripherals.adoc | |||
| @@ -1,6 +1,12 @@ | |||
| 1 | = Sharing peripherals between tasks | 1 | = Sharing peripherals between tasks |
| 2 | 2 | ||
| 3 | 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. | 3 | 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. |
| 4 | |||
| 5 | The following examples shows different ways to use the on-board LED on a Raspberry Pi Pico board by two tasks simultaneously. | ||
| 6 | |||
| 7 | == Sharing using a Mutex | ||
| 8 | |||
| 9 | Using mutual exclusion is the simplest way to share a peripheral. | ||
| 4 | 10 | ||
| 5 | [,rust] | 11 | [,rust] |
| 6 | ---- | 12 | ---- |
| @@ -29,13 +35,12 @@ async fn main(spawner: Spawner) { | |||
| 29 | let dt = 100 * 1_000_000; | 35 | let dt = 100 * 1_000_000; |
| 30 | let k = 1.003; | 36 | let k = 1.003; |
| 31 | 37 | ||
| 32 | unwrap!(spawner.spawn(toggle(&LED, Duration::from_nanos(dt)))); | 38 | unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt)))); |
| 33 | unwrap!(spawner.spawn(toggle_slightly_slower( | 39 | unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64)))); |
| 34 | &LED, | ||
| 35 | Duration::from_nanos((dt as f64 * k) as u64) | ||
| 36 | ))); | ||
| 37 | } | 40 | } |
| 38 | 41 | ||
| 42 | // A pool size of 2 means you can spawn two instances of this task. | ||
| 43 | #[embassy_executor::task(pool_size = 2)] | ||
| 39 | async fn toggle_led(led: &'static LedType, delay: Duration) { | 44 | async fn toggle_led(led: &'static LedType, delay: Duration) { |
| 40 | let mut ticker = Ticker::every(delay); | 45 | let mut ticker = Ticker::every(delay); |
| 41 | loop { | 46 | loop { |
| @@ -48,31 +53,74 @@ async fn toggle_led(led: &'static LedType, delay: Duration) { | |||
| 48 | ticker.next().await; | 53 | ticker.next().await; |
| 49 | } | 54 | } |
| 50 | } | 55 | } |
| 51 | #[embassy_executor::task] | ||
| 52 | async fn toggle(led: &'static LedType, delay: Duration) { | ||
| 53 | toggle_led(led, delay).await | ||
| 54 | } | ||
| 55 | |||
| 56 | #[embassy_executor::task] | ||
| 57 | async fn toggle_slightly_slower(led: &'static LedType, delay: Duration) { | ||
| 58 | toggle_led(led, delay).await | ||
| 59 | } | ||
| 60 | ---- | 56 | ---- |
| 61 | 57 | ||
| 62 | The structure facilitating access to the resource is the defined `LedType`. | 58 | The structure facilitating access to the resource is the defined `LedType`. |
| 63 | 59 | ||
| 64 | == Why so complicated | 60 | === Why so complicated |
| 65 | 61 | ||
| 66 | Unwrapping the layers gives insight into why each one is needed. | 62 | Unwrapping the layers gives insight into why each one is needed. |
| 67 | 63 | ||
| 68 | === `Mutex<RawMutexType, T>` | 64 | ==== `Mutex<RawMutexType, T>` |
| 69 | 65 | ||
| 70 | 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). | 66 | 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). |
| 71 | 67 | ||
| 72 | === `Option<T>` | 68 | ==== `Option<T>` |
| 73 | 69 | ||
| 74 | 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`. | 70 | 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`. |
| 75 | 71 | ||
| 76 | === `Output<AnyPin>` | 72 | ==== `Output<AnyPin>` |
| 77 | 73 | ||
| 78 | 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. | 74 | 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. |
| 75 | |||
| 76 | == Sharing using a Channel | ||
| 77 | |||
| 78 | 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. | ||
| 79 | |||
| 80 | [,rust] | ||
| 81 | ---- | ||
| 82 | use defmt::*; | ||
| 83 | use embassy_executor::Spawner; | ||
| 84 | use embassy_rp::gpio; | ||
| 85 | use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; | ||
| 86 | use embassy_sync::channel::{Channel, Sender}; | ||
| 87 | use embassy_time::{Duration, Ticker}; | ||
| 88 | use gpio::{AnyPin, Level, Output}; | ||
| 89 | use {defmt_rtt as _, panic_probe as _}; | ||
| 90 | |||
| 91 | enum LedState { | ||
| 92 | Toggle, | ||
| 93 | } | ||
| 94 | static CHANNEL: Channel<ThreadModeRawMutex, LedState, 64> = Channel::new(); | ||
| 95 | |||
| 96 | #[embassy_executor::main] | ||
| 97 | async fn main(spawner: Spawner) { | ||
| 98 | let p = embassy_rp::init(Default::default()); | ||
| 99 | let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); | ||
| 100 | |||
| 101 | let dt = 100 * 1_000_000; | ||
| 102 | let k = 1.003; | ||
| 103 | |||
| 104 | unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt)))); | ||
| 105 | unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos((dt as f64 * k) as u64)))); | ||
| 106 | |||
| 107 | loop { | ||
| 108 | match CHANNEL.receive().await { | ||
| 109 | LedState::Toggle => led.toggle(), | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | // A pool size of 2 means you can spawn two instances of this task. | ||
| 115 | #[embassy_executor::task(pool_size = 2)] | ||
| 116 | async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) { | ||
| 117 | let mut ticker = Ticker::every(delay); | ||
| 118 | loop { | ||
| 119 | control.send(LedState::Toggle).await; | ||
| 120 | ticker.next().await; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | ---- | ||
| 124 | |||
| 125 | 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 | ||
| 126 | that the operation is ecompleted before continuing to do other work in your task. | ||
diff --git a/docs/modules/ROOT/pages/time_keeping.adoc b/docs/modules/ROOT/pages/time_keeping.adoc new file mode 100644 index 000000000..5068216ed --- /dev/null +++ b/docs/modules/ROOT/pages/time_keeping.adoc | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | = Time-keeping | ||
| 2 | |||
| 3 | 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 | ||
| 4 | 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 | ||
| 5 | to delay the current task for a specified interval of time. | ||
| 6 | |||
| 7 | 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 | ||
| 8 | timer queue in link:https://crates.io/crates/embassy-executor[embassy-executor] or a custom timer queue implementation. | ||
| 9 | |||
| 10 | == Timer | ||
| 11 | |||
| 12 | The `embassy::time::Timer` type provides two timing methods. | ||
| 13 | |||
| 14 | `Timer::at` creates a future that completes at the specified `Instant`, relative to the system boot time. | ||
| 15 | `Timer::after` creates a future that completes after the specified `Duration`, relative to when the future was created. | ||
| 16 | |||
| 17 | An example of a delay is provided as follows: | ||
| 18 | |||
| 19 | [,rust] | ||
| 20 | ---- | ||
| 21 | use embassy::executor::{task, Executor}; | ||
| 22 | use embassy::time::{Duration, Timer}; | ||
| 23 | |||
| 24 | #[task] | ||
| 25 | /// Task that ticks periodically | ||
| 26 | async fn tick_periodic() -> ! { | ||
| 27 | loop { | ||
| 28 | rprintln!("tick!"); | ||
| 29 | // async sleep primitive, suspends the task for 500ms. | ||
| 30 | Timer::after(Duration::from_millis(500)).await; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | ---- | ||
| 34 | |||
| 35 | == Delay | ||
| 36 | |||
| 37 | 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 | ||
| 38 | link:https://docs.rs/embedded-hal-async/latest/embedded_hal_async/delay/index.html[embedded-hal-async] traits. This can be used for drivers | ||
| 39 | that expect a generic delay implementation to be provided. | ||
| 40 | |||
| 41 | An example of how this can be used: | ||
| 42 | |||
| 43 | [,rust] | ||
| 44 | ---- | ||
| 45 | use embassy::executor::{task, Executor}; | ||
| 46 | |||
| 47 | #[task] | ||
| 48 | /// Task that ticks periodically | ||
| 49 | async fn tick_periodic() -> ! { | ||
| 50 | loop { | ||
| 51 | rprintln!("tick!"); | ||
| 52 | // async sleep primitive, suspends the task for 500ms. | ||
| 53 | generic_delay(embassy::time::Delay).await | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | async fn generic_delay<D: embedded_hal_async::delay::DelayNs>(delay: D) { | ||
| 58 | delay.delay_ms(500).await; | ||
| 59 | } | ||
| 60 | ---- | ||
