From 8d8a1e5afcf23013fc8ed604d4aa55cedff66a16 Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 28 Jan 2023 21:57:43 +0100 Subject: [PATCH] Book: Major rework for RTIC v2 --- book/en/src/SUMMARY.md | 19 +- book/en/src/by-example.md | 12 +- book/en/src/by-example/app.md | 24 ++- book/en/src/by-example/app_idle.md | 29 ++- book/en/src/by-example/app_init.md | 25 +-- book/en/src/by-example/app_minimal.md | 16 +- book/en/src/by-example/app_priorities.md | 30 +--- book/en/src/by-example/app_task.md | 17 +- book/en/src/by-example/channel.md | 112 ++++++++++++ book/en/src/by-example/delay.md | 116 ++++++++++++ book/en/src/by-example/hardware_tasks.md | 30 +--- book/en/src/by-example/resources.md | 136 ++++++--------- book/en/src/by-example/software_tasks.md | 106 ++++++++--- book/en/src/by-example/starting_a_project.md | 2 + book/en/src/preface.md | 165 ++++++++++++++++-- book/en/src/rtic_vs.md | 31 ++++ rtic/Cargo.toml | 20 ++- rtic/ci/expected/async-channel-done.run | 9 + .../ci/expected/async-channel-no-receiver.run | 1 + rtic/ci/expected/async-channel-no-sender.run | 1 + rtic/ci/expected/async-channel-try.run | 2 + rtic/ci/expected/async-channel.run | 2 +- rtic/ci/expected/async-timeout.run | 19 +- rtic/ci/expected/common.run | 3 + ...essage_passing.run => spawn_arguments.run} | 0 rtic/ci/expected/spawn_err.run | 3 + rtic/ci/expected/spawn_loop.run | 7 + rtic/examples/async-channel-done.rs | 65 +++++++ rtic/examples/async-channel-no-receiver.rs | 40 +++++ rtic/examples/async-channel-no-sender.rs | 40 +++++ rtic/examples/async-channel-try.rs | 48 +++++ rtic/examples/async-channel.rs | 15 +- rtic/examples/async-delay.rs | 2 + rtic/examples/async-timeout.rs | 87 +++++++++ rtic/examples/common.rs | 86 +++++++++ rtic/examples/lock-free.rs | 50 ++++++ ...{message_passing.rs => spawn_arguments.rs} | 0 rtic/examples/spawn_err.rs | 40 +++++ rtic/examples/spawn_loop.rs | 42 +++++ rtic/examples/zero-prio-task_err.no_rs | 66 +++++++ 40 files changed, 1267 insertions(+), 251 deletions(-) create mode 100644 book/en/src/by-example/channel.md create mode 100644 book/en/src/by-example/delay.md create mode 100644 book/en/src/rtic_vs.md create mode 100644 rtic/ci/expected/async-channel-done.run create mode 100644 rtic/ci/expected/async-channel-no-receiver.run create mode 100644 rtic/ci/expected/async-channel-no-sender.run create mode 100644 rtic/ci/expected/async-channel-try.run rename rtic/ci/expected/{message_passing.run => spawn_arguments.run} (100%) create mode 100644 rtic/ci/expected/spawn_err.run create mode 100644 rtic/ci/expected/spawn_loop.run create mode 100644 rtic/examples/async-channel-done.rs create mode 100644 rtic/examples/async-channel-no-receiver.rs create mode 100644 rtic/examples/async-channel-no-sender.rs create mode 100644 rtic/examples/async-channel-try.rs create mode 100644 rtic/examples/async-timeout.rs create mode 100644 rtic/examples/common.rs create mode 100644 rtic/examples/lock-free.rs rename rtic/examples/{message_passing.rs => spawn_arguments.rs} (100%) create mode 100644 rtic/examples/spawn_err.rs create mode 100644 rtic/examples/spawn_loop.rs create mode 100644 rtic/examples/zero-prio-task_err.no_rs diff --git a/book/en/src/SUMMARY.md b/book/en/src/SUMMARY.md index 853f3a5319..407be6d5b8 100644 --- a/book/en/src/SUMMARY.md +++ b/book/en/src/SUMMARY.md @@ -4,15 +4,13 @@ - [RTIC by example](./by-example.md) - [The `app`](./by-example/app.md) + - [Hardware tasks & `pend`](./by-example/hardware_tasks.md) + - [Software tasks & `spawn`](./by-example/software_tasks.md) - [Resources](./by-example/resources.md) - [The init task](./by-example/app_init.md) - [The idle task](./by-example/app_idle.md) - - [Defining tasks](./by-example/app_task.md) - - [Hardware tasks](./by-example/hardware_tasks.md) - - [Software tasks & `spawn`](./by-example/software_tasks.md) - - [Message passing & `capacity`](./by-example/message_passing.md) - - [Task priorities](./by-example/app_priorities.md) - - [Monotonic & `spawn_{at/after}`](./by-example/monotonic.md) + - [Channel based communication](./by-example/channel.md) + - [Tasks with delay](./by-example/delay.md) - [Starting a new project](./by-example/starting_a_project.md) - [The minimal app](./by-example/app_minimal.md) - [Tips & Tricks](./by-example/tips.md) @@ -23,13 +21,13 @@ - [Inspecting generated code](./by-example/tips_view_code.md) - [Running tasks from RAM](./by-example/tips_from_ram.md) +- [RTIC vs. the world](./rtic_vs.md) - [Awesome RTIC examples](./awesome_rtic.md) - [Migration Guides](./migration.md) - [v0.5.x to v1.0.x](./migration/migration_v5.md) - [v0.4.x to v0.5.x](./migration/migration_v4.md) - [RTFM to RTIC](./migration/migration_rtic.md) - [Under the hood](./internals.md) - - [Cortex-M architectures](./internals/targets.md) @@ -38,3 +36,10 @@ + + + \ No newline at end of file diff --git a/book/en/src/by-example.md b/book/en/src/by-example.md index 419a4bae64..a2e5b27887 100644 --- a/book/en/src/by-example.md +++ b/book/en/src/by-example.md @@ -1,14 +1,15 @@ # RTIC by example -This part of the book introduces the Real-Time Interrupt-driven Concurrency (RTIC) framework -to new users by walking them through examples of increasing complexity. +This part of the book introduces the RTIC framework to new users by walking them through examples of increasing complexity. All examples in this part of the book are accessible at the [GitHub repository][repoexamples]. The examples are runnable on QEMU (emulating a Cortex M3 target), thus no special hardware required to follow along. -[repoexamples]: https://github.com/rtic-rs/cortex-m-rtic/tree/master/examples +[repoexamples]: https://github.com/rtic-rs/rtic/tree/master/examples + +## Running an example To run the examples with QEMU you will need the `qemu-system-arm` program. Check [the embedded Rust book] for instructions on how to set up an @@ -28,11 +29,12 @@ $ cargo run --target thumbv7m-none-eabi --example locals Yields this output: ``` console -{{#include ../../../ci/expected/locals.run}} +{{#include ../../../rtic/ci/expected/locals.run}} ``` > **NOTE**: You can choose target device by passing a target > triple to cargo (e.g. `cargo run --example init --target thumbv7m-none-eabi`) or > configure a default target in `.cargo/config.toml`. > -> For running the examples, we use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`. \ No newline at end of file +> For running the examples, we (typically) use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`. +> Since the M3 architecture is backwards compatible to the M0/M0+ architecture, you may also use the `thumbv6m-none-eabi`, in case you want to inspect generated assembly code for the M0/M0+ architecture. diff --git a/book/en/src/by-example/app.md b/book/en/src/by-example/app.md index 2c6aca7a2b..cef8288523 100644 --- a/book/en/src/by-example/app.md +++ b/book/en/src/by-example/app.md @@ -2,25 +2,31 @@ ## Requirements on the `app` attribute -All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute -only applies to a `mod`-item containing the RTIC application. The `app` -attribute has a mandatory `device` argument that takes a *path* as a value. -This must be a full path pointing to a -*peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or -newer. +All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute only applies to a `mod`-item containing the RTIC application. The `app` attribute has a mandatory `device` argument that takes a *path* as a value. This must be a full path pointing to a *peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or newer. -The `app` attribute will expand into a suitable entry point and thus replaces -the use of the [`cortex_m_rt::entry`] attribute. +The `app` attribute will expand into a suitable entry point and thus replaces the use of the [`cortex_m_rt::entry`] attribute. [`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html [`svd2rust`]: https://crates.io/crates/svd2rust [`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html +## Structure and zero-cost concurrency + +An RTIC `app` is an executable system model for since-core applications, declaring a set of `local` and `shared` resources operated on by a set of `init`, `idle`, *hardware* and *software* tasks. In short the `init` task runs before any other task returning the set of `local` and `shared` resources. Tasks run preemptively based on their associated static priority, `idle` has the lowest priority (and can be used for background work, and/or to put the system to sleep until woken by some event). Hardware tasks are bound to underlying hardware interrupts, while software tasks are scheduled by asynchronous executors (one for each software task priority). + +At compile time the task/resource model is analyzed under the Stack Resource Policy (SRP) and executable code generated with the following outstanding properties: + +- guaranteed race-free resource access and deadlock-free execution on a single-shared stack + - hardware task scheduling is performed directly by the hardware, and + - software task scheduling is performed by auto generated async executors tailored to the application. + +Overall, the generated code infers no additional overhead in comparison to a hand-written implementation, thus in Rust terms RTIC offers a zero-cost abstraction to concurrency. + ## An RTIC application example To give a flavour of RTIC, the following example contains commonly used features. In the following sections we will go through each feature in detail. ``` rust -{{#include ../../../../examples/common.rs}} +{{#include ../../../../rtic/examples/common.rs}} ``` diff --git a/book/en/src/by-example/app_idle.md b/book/en/src/by-example/app_idle.md index 537902a442..4856ee19a6 100644 --- a/book/en/src/by-example/app_idle.md +++ b/book/en/src/by-example/app_idle.md @@ -1,52 +1,47 @@ # The background task `#[idle]` -A function marked with the `idle` attribute can optionally appear in the -module. This becomes the special *idle task* and must have signature -`fn(idle::Context) -> !`. +A function marked with the `idle` attribute can optionally appear in the module. This becomes the special *idle task* and must have signature `fn(idle::Context) -> !`. -When present, the runtime will execute the `idle` task after `init`. Unlike -`init`, `idle` will run *with interrupts enabled* and must never return, -as the `-> !` function signature indicates. +When present, the runtime will execute the `idle` task after `init`. Unlike `init`, `idle` will run *with interrupts enabled* and must never return, as the `-> !` function signature indicates. [The Rust type `!` means “never”][nevertype]. [nevertype]: https://doc.rust-lang.org/core/primitive.never.html -Like in `init`, locally declared resources will have `'static` lifetimes that -are safe to access. +Like in `init`, locally declared resources will have `'static` lifetimes that are safe to access. The example below shows that `idle` runs after `init`. ``` rust -{{#include ../../../../examples/idle.rs}} +{{#include ../../../../rtic/examples/idle.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example idle -{{#include ../../../../ci/expected/idle.run}} +{{#include ../../../../rtic/ci/expected/idle.run}} ``` By default, the RTIC `idle` task does not try to optimize for any specific targets. -A common useful optimization is to enable the [SLEEPONEXIT] and allow the MCU -to enter sleep when reaching `idle`. +A common useful optimization is to enable the [SLEEPONEXIT] and allow the MCU to enter sleep when reaching `idle`. ->**Caution** some hardware unless configured disables the debug unit during sleep mode. +>**Caution**: some hardware unless configured disables the debug unit during sleep mode. > >Consult your hardware specific documentation as this is outside the scope of RTIC. The following example shows how to enable sleep by setting the -[`SLEEPONEXIT`][SLEEPONEXIT] and providing a custom `idle` task replacing the -default [`nop()`][NOP] with [`wfi()`][WFI]. +[`SLEEPONEXIT`][SLEEPONEXIT] and providing a custom `idle` task replacing the default [`nop()`][NOP] with [`wfi()`][WFI]. [SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit [WFI]: https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/WFI [NOP]: https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/NOP ``` rust -{{#include ../../../../examples/idle-wfi.rs}} +{{#include ../../../../rtic/examples/idle-wfi.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example idle-wfi -{{#include ../../../../ci/expected/idle-wfi.run}} +{{#include ../../../../rtic/ci/expected/idle-wfi.run}} ``` + +> **Notice**: The `idle` task cannot be used together with *software* tasks running at priority zero. The reason is that `idle` is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to *software* tasks at the same priority. diff --git a/book/en/src/by-example/app_init.md b/book/en/src/by-example/app_init.md index 5bf6200e1c..62fb55b837 100644 --- a/book/en/src/by-example/app_init.md +++ b/book/en/src/by-example/app_init.md @@ -1,35 +1,28 @@ # App initialization and the `#[init]` task An RTIC application requires an `init` task setting up the system. The corresponding `init` function must have the -signature `fn(init::Context) -> (Shared, Local, init::Monotonics)`, where `Shared` and `Local` are resource +signature `fn(init::Context) -> (Shared, Local)`, where `Shared` and `Local` are the resource structures defined by the user. -The `init` task executes after system reset, [after an optionally defined `pre-init` code section][pre-init] and an always occurring internal RTIC -initialization. - +The `init` task executes after system reset (after the optionally defined [pre-init] and internal RTIC +initialization). The `init` task runs *with interrupts disabled* and has exclusive access to Cortex-M (the +`bare_metal::CriticalSection` token is available as `cs`) while device specific peripherals are available through +the `core` and `device` fields of `init::Context`. [pre-init]: https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.pre_init.html - -The `init` and optional `pre-init` tasks runs *with interrupts disabled* and have exclusive access to Cortex-M (the -`bare_metal::CriticalSection` token is available as `cs`). - -Device specific peripherals are available through the `core` and `device` fields of `init::Context`. - ## Example -The example below shows the types of the `core`, `device` and `cs` fields, and showcases the use of a `local` -variable with `'static` lifetime. -Such variables can be delegated from the `init` task to other tasks of the RTIC application. +The example below shows the types of the `core`, `device` and `cs` fields, and showcases the use of a `local` variable with `'static` lifetime. Such variables can be delegated from the `init` task to other tasks of the RTIC application. -The `device` field is only available when the `peripherals` argument is set to the default value `true`. +The `device` field is available when the `peripherals` argument is set to the default value `true`. In the rare case you want to implement an ultra-slim application you can explicitly set `peripherals` to `false`. ``` rust -{{#include ../../../../examples/init.rs}} +{{#include ../../../../rtic/examples/init.rs}} ``` Running the example will print `init` to the console and then exit the QEMU process. ``` console $ cargo run --target thumbv7m-none-eabi --example init -{{#include ../../../../ci/expected/init.run}} +{{#include ../../../../rtic/ci/expected/init.run}} ``` diff --git a/book/en/src/by-example/app_minimal.md b/book/en/src/by-example/app_minimal.md index d0ff40a303..f241089061 100644 --- a/book/en/src/by-example/app_minimal.md +++ b/book/en/src/by-example/app_minimal.md @@ -3,5 +3,19 @@ This is the smallest possible RTIC application: ``` rust -{{#include ../../../../examples/smallest.rs}} +{{#include ../../../../rtic/examples/smallest.rs}} ``` + +RTIC is designed with resource efficiency in mind. RTIC itself does not rely on any dynamic memory allocation, thus RAM requirement is dependent only on the application. The flash memory footprint is below 1kB including the interrupt vector table. + +For a minimal example you can expect something like: +``` console +$ cargo size --example smallest --target thumbv7m-none-eabi --release +Finished release [optimized] target(s) in 0.07s + text data bss dec hex filename + 924 0 0 924 39c smallest +``` + + diff --git a/book/en/src/by-example/app_priorities.md b/book/en/src/by-example/app_priorities.md index 8cee7499e1..f03ebf7390 100644 --- a/book/en/src/by-example/app_priorities.md +++ b/book/en/src/by-example/app_priorities.md @@ -4,23 +4,18 @@ The `priority` argument declares the static priority of each `task`. -For Cortex-M, tasks can have priorities in the range `1..=(1 << NVIC_PRIO_BITS)` -where `NVIC_PRIO_BITS` is a constant defined in the `device` crate. +For Cortex-M, tasks can have priorities in the range `1..=(1 << NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device` crate. -Omitting the `priority` argument the task priority defaults to `1`. -The `idle` task has a non-configurable static priority of `0`, the lowest priority. +Omitting the `priority` argument the task priority defaults to `1`. The `idle` task has a non-configurable static priority of `0`, the lowest priority. > A higher number means a higher priority in RTIC, which is the opposite from what > Cortex-M does in the NVIC peripheral. > Explicitly, this means that number `10` has a **higher** priority than number `9`. -The highest static priority task takes precedence when more than one -task are ready to execute. +The highest static priority task takes precedence when more than one task are ready to execute. The following scenario demonstrates task prioritization: -Spawning a higher priority task A during execution of a lower priority task B suspends -task B. Task A has higher priority thus preempting task B which gets suspended -until task A completes execution. Thus, when task A completes task B resumes execution. +Spawning a higher priority task A during execution of a lower priority task B pends task A. Task A has higher priority thus preempting task B which gets suspended until task A completes execution. Thus, when task A completes task B resumes execution. ```text Task Priority @@ -39,23 +34,17 @@ Task Priority The following example showcases the priority based scheduling of tasks: ``` rust -{{#include ../../../../examples/preempt.rs}} +{{#include ../../../../rtic/examples/preempt.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example preempt -{{#include ../../../../ci/expected/preempt.run}} +{{#include ../../../../rtic/ci/expected/preempt.run}} ``` -Note that the task `bar` does *not* preempt task `baz` because its priority -is the *same* as `baz`'s. The higher priority task `bar` runs before `foo` -when `baz`returns. When `bar` returns `foo` can resume. +Note that the task `bar` does *not* preempt task `baz` because its priority is the *same* as `baz`'s. The higher priority task `bar` runs before `foo` when `baz`returns. When `bar` returns `foo` can resume. -One more note about priorities: choosing a priority higher than what the device -supports will result in a compilation error. - -The error is cryptic due to limitations in the Rust language -if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like: +One more note about priorities: choosing a priority higher than what the device supports will result in a compilation error. The error is cryptic due to limitations in the Rust language, if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like: ```text error[E0080]: evaluation of constant value failed @@ -68,5 +57,4 @@ if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks l ``` -The error message incorrectly points to the starting point of the macro, but at least the -value subtracted (in this case 9) will suggest which task causes the error. +The error message incorrectly points to the starting point of the macro, but at least the value subtracted (in this case 9) will suggest which task causes the error. diff --git a/book/en/src/by-example/app_task.md b/book/en/src/by-example/app_task.md index d83f1ff15a..e0c67ad2c2 100644 --- a/book/en/src/by-example/app_task.md +++ b/book/en/src/by-example/app_task.md @@ -1,21 +1,18 @@ + + # Defining tasks with `#[task]` Tasks, defined with `#[task]`, are the main mechanism of getting work done in RTIC. Tasks can -* Be spawned (now or in the future, also by themselves) -* Receive messages (passing messages between tasks) -* Be prioritized, allowing preemptive multitasking +* Be spawned (now or in the future) +* Receive messages (message passing) +* Prioritized allowing preemptive multitasking * Optionally bind to a hardware interrupt -RTIC makes a distinction between “software tasks” and “hardware tasks”. +RTIC makes a distinction between “software tasks” and “hardware tasks”. Hardware tasks are tasks that are bound to a specific interrupt vector in the MCU while software tasks are not. -*Hardware tasks* are tasks that are bound to a specific interrupt vector in the MCU while software tasks are not. - -This means that if a hardware task is bound to, lets say, a UART RX interrupt, the task will be run every -time that interrupt triggers, usually when a character is received. - -*Software tasks* are explicitly spawned in a task, either immediately or using the Monotonic timer mechanism. +This means that if a hardware task is bound to an UART RX interrupt the task will run every time this interrupt triggers, usually when a character is received. In the coming pages we will explore both tasks and the different options available. diff --git a/book/en/src/by-example/channel.md b/book/en/src/by-example/channel.md new file mode 100644 index 0000000000..99bfedda3c --- /dev/null +++ b/book/en/src/by-example/channel.md @@ -0,0 +1,112 @@ +# Communication over channels. + +Channels can be used to communicate data between running *software* tasks. The channel is essentially a wait queue, allowing tasks with multiple producers and a single receiver. A channel is constructed in the `init` task and backed by statically allocated memory. Send and receive endpoints are distributed to *software* tasks: + +```rust +... +const CAPACITY: usize = 5; +#[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, CAPACITY); + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + sender2::spawn(s.clone()).unwrap(); + ... +``` + +In this case the channel holds data of `u32` type with a capacity of 5 elements. + +## Sending data + +The `send` method post a message on the channel as shown below: + +```rust +#[task] +async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); +} +``` + +## Receiving data + +The receiver can `await` incoming messages: + +```rust +#[task] +async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + ... + } +} +``` + +For a complete example: + +``` rust +{{#include ../../../../rtic/examples/async-channel.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel.run}} +``` + +Also sender endpoint can be awaited. In case there the channel capacity has not been reached, `await` the sender can progress immediately, while in the case the capacity is reached, the sender is blocked until there is free space in the queue. In this way data is never lost. + +In the below example the `CAPACITY` has been reduced to 1, forcing sender tasks to wait until the data in the channel has been received. + +``` rust +{{#include ../../../../rtic/examples/async-channel-done.rs}} +``` + +Looking at the output, we find that `Sender 2` will wait until the data sent by `Sender 1` as been received. + +> **NOTICE** *Software* tasks at the same priority are executed asynchronously to each other, thus **NO** strict order can be assumed. (The presented order here applies only to the current implementation, and may change between RTIC framework releases.) + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-done --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-done.run}} +``` + +## Error handling + +In case all senders have been dropped `await` on an empty receiver channel results in an error. This allows to gracefully implement different types of shutdown operations. + +``` rust +{{#include ../../../../rtic/examples/async-channel-no-sender.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-no-sender --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-no-sender.run}} +``` + +Similarly, `await` on a send channel results in an error in case the receiver has been dropped. This allows to gracefully implement application level error handling. + +The resulting error returns the data back to the sender, allowing the sender to take appropriate action (e.g., storing the data to later retry sending it). + +``` rust +{{#include ../../../../rtic/examples/async-channel-no-receiver.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-no-receiver --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-no-receiver.run}} +``` + + + +## Try API + +In cases you wish the sender to proceed even in case the channel is full. To that end, a `try_send` API is provided. + +``` rust +{{#include ../../../../rtic/examples/async-channel-try.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-try --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-try.run}} +``` \ No newline at end of file diff --git a/book/en/src/by-example/delay.md b/book/en/src/by-example/delay.md new file mode 100644 index 0000000000..d35d9da453 --- /dev/null +++ b/book/en/src/by-example/delay.md @@ -0,0 +1,116 @@ +# Tasks with delay + +A convenient way to express *miniminal* timing requirements is by means of delaying progression. + +This can be achieved by instantiating a monotonic timer: + +```rust +... +rtic_monotonics::make_systick_timer_queue!(TIMER); + +#[init] +fn init(cx: init::Context) -> (Shared, Local) { + let systick = Systick::start(cx.core.SYST, 12_000_000); + TIMER.initialize(systick); + ... +``` + +A *software* task can `await` the delay to expire: + +```rust +#[task] +async fn foo(_cx: foo::Context) { + ... + TIMER.delay(100.millis()).await; + ... +``` + +Technically, the timer queue is implemented as a list based priority queue, where list-nodes are statically allocated as part of the underlying task `Future`. Thus, the timer queue is infallible at run-time (its size and allocation is determined at compile time). + +For a complete example: + +``` rust +{{#include ../../../../rtic/examples/async-delay.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-delay --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-delay.run}} +``` + +## Timeout + +Rust `Futures` (underlying Rust `async`/`await`) are composable. This makes it possible to `select` in between `Futures` that have completed. + +A common use case is transactions with associated timeout. In the examples shown below, we introduce a fake HAL device which performs some transaction. We have modelled the time it takes based on the input parameter (`n`) as `350ms + n * 100ms)`. + +Using the `select_biased` macro from the `futures` crate it may look like this: + +```rust +// Call hal with short relative timeout using `select_biased` +select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), + _ = TIMER.delay(200.millis()).fuse() => hprintln!("timeout", ), // this will finish first +} +``` + +Assuming the `hal_get` will take 450ms to finish, a short timeout of 200ms will expire. + +```rust +// Call hal with long relative timeout using `select_biased` +select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), // hal finish first + _ = TIMER.delay(1000.millis()).fuse() => hprintln!("timeout", ), +} +``` + +By extending the timeout to 1000ms, the `hal_get` will finish first. + +Using `select_biased` any number of futures can be combined, so its very powerful. However, as the timeout pattern is frequently used, it is directly supported by the RTIC [rtc-monotonics] and [rtic-time] crates. The second example from above using `timeout_after`: + +```rust +// Call hal with long relative timeout using monotonic `timeout_after` +match TIMER.timeout_after(1000.millis(), hal_get(&TIMER, 1)).await { + Ok(v) => hprintln!("hal returned {}", v), + _ => hprintln!("timeout"), +} +``` + +In cases you want exact control over time without drift. For this purpose we can use exact points in time using `Instance`, and spans of time using `Duration`. Operations on the `Instance` and `Duration` types are given by the [fugit] crate. + +[fugit]: https://crates.io/crates/fugit + +```rust +// get the current time instance +let mut instant = TIMER.now(); + +// do this 3 times +for n in 0..3 { + // exact point in time without drift + instant += 1000.millis(); + TIMER.delay_until(instant).await; + + // exact point it time for timeout + let timeout = instant + 500.millis(); + hprintln!("now is {:?}, timeout at {:?}", TIMER.now(), timeout); + + match TIMER.timeout_at(timeout, hal_get(&TIMER, n)).await { + Ok(v) => hprintln!("hal returned {} at time {:?}", v, TIMER.now()), + _ => hprintln!("timeout"), + } +} +``` + +`instant = TIMER.now()` gives the baseline (i.e., the exact current point in time). We want to call `hal_get` after 1000ms relative to this exact point in time. This can be accomplished by `TIMER.delay_until(instant).await;`. We define the absolute point in time for the `timeout`, and call `TIMER.timeout_at(timeout, hal_get(&TIMER, n)).await`. For the first loop iteration `n == 0`, and the `hal_get` will take 350ms (and finishes before the timeout). For the second iteration `n == 1`, and `hal_get` will take 450ms (and again succeeds to finish before the timeout). For the third iteration `n == 2` (`hal_get` will take 5500ms to finish). In this case we will run into a timeout. + + +The complete example: + +``` rust +{{#include ../../../../rtic/examples/async-timeout.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-timeout --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-timeout.run}} +``` diff --git a/book/en/src/by-example/hardware_tasks.md b/book/en/src/by-example/hardware_tasks.md index 2d405d324d..e3e51acc59 100644 --- a/book/en/src/by-example/hardware_tasks.md +++ b/book/en/src/by-example/hardware_tasks.md @@ -1,39 +1,27 @@ # Hardware tasks -At its core RTIC is using a hardware interrupt controller ([ARM NVIC on cortex-m][NVIC]) -to schedule and start execution of tasks. All tasks except `pre-init`, `#[init]` and `#[idle]` -run as interrupt handlers. +At its core RTIC is using the hardware interrupt controller ([ARM NVIC on cortex-m][NVIC]) to perform scheduling and executing tasks, and all (*hardware*) tasks except `#[init]` and `#[idle]` run as interrupt handlers. This also means that you can manually bind tasks to interrupt handlers. -Hardware tasks are explicitly bound to interrupt handlers. +To bind an interrupt use the `#[task]` attribute argument `binds = InterruptName`. This task becomes the interrupt handler for this hardware interrupt vector. -To bind a task to an interrupt, use the `#[task]` attribute argument `binds = InterruptName`. -This task then becomes the interrupt handler for this hardware interrupt vector. +All tasks bound to an explicit interrupt are *hardware tasks* since they start execution in reaction to a hardware event (interrupt). -All tasks bound to an explicit interrupt are called *hardware tasks* since they -start execution in reaction to a hardware event. +Specifying a non-existing interrupt name will cause a compilation error. The interrupt names are commonly defined by [PAC or HAL][pacorhal] crates. -Specifying a non-existing interrupt name will cause a compilation error. The interrupt names -are commonly defined by [PAC or HAL][pacorhal] crates. +Any available interrupt vector should work, but different hardware might have added special properties to select interrupt priority levels, such as the [nRF “softdevice”](https://github.com/rtic-rs/cortex-m-rtic/issues/434). -Any available interrupt vector should work. Specific devices may bind -specific interrupt priorities to specific interrupt vectors outside -user code control. See for example the -[nRF “softdevice”](https://github.com/rtic-rs/cortex-m-rtic/issues/434). - -Beware of using interrupt vectors that are used internally by hardware features; -RTIC is unaware of such hardware specific details. +Beware of re-purposing interrupt vectors used internally by hardware features, RTIC is unaware of such hardware specific details. [pacorhal]: https://docs.rust-embedded.org/book/start/registers.html [NVIC]: https://developer.arm.com/documentation/100166/0001/Nested-Vectored-Interrupt-Controller/NVIC-functional-description/NVIC-interrupts -The example below demonstrates the use of the `#[task(binds = InterruptName)]` attribute to declare a -hardware task bound to an interrupt handler. +The example below demonstrates the use of the `#[task(binds = InterruptName)]` attribute to declare a hardware task bound to an interrupt handler. In the example the interrupt triggering task execution is manually pended (`rtic::pend(Interrupt::UART0)`). However, in the typical case, interrupts are pended by the hardware peripheral. RTIC does not interfere with mechanisms for clearing peripheral interrupts, so any hardware specific implementation is completely up to the implementer. ``` rust -{{#include ../../../../examples/hardware.rs}} +{{#include ../../../../rtic/examples/hardware.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example hardware -{{#include ../../../../ci/expected/hardware.run}} +{{#include ../../../../rtic/ci/expected/hardware.run}} ``` diff --git a/book/en/src/by-example/resources.md b/book/en/src/by-example/resources.md index 30089d34a2..ea67b2661b 100644 --- a/book/en/src/by-example/resources.md +++ b/book/en/src/by-example/resources.md @@ -1,176 +1,138 @@ # Resource usage -The RTIC framework manages shared and task local resources allowing persistent data -storage and safe accesses without the use of `unsafe` code. +The RTIC framework manages shared and task local resources allowing persistent data storage and safe accesses without the use of `unsafe` code. -RTIC resources are visible only to functions declared within the `#[app]` module and the framework -gives the user complete control (on a per-task basis) over resource accessibility. +RTIC resources are visible only to functions declared within the `#[app]` module and the framework gives the user complete control (on a per-task basis) over resource accessibility. -Declaration of system-wide resources is done by annotating **two** `struct`s within the `#[app]` module -with the attribute `#[local]` and `#[shared]`. -Each field in these structures corresponds to a different resource (identified by field name). -The difference between these two sets of resources will be covered below. +Declaration of system-wide resources is done by annotating **two** `struct`s within the `#[app]` module with the attribute `#[local]` and `#[shared]`. Each field in these structures corresponds to a different resource (identified by field name). The difference between these two sets of resources will be covered below. -Each task must declare the resources it intends to access in its corresponding metadata attribute -using the `local` and `shared` arguments. Each argument takes a list of resource identifiers. -The listed resources are made available to the context under the `local` and `shared` fields of the -`Context` structure. +Each task must declare the resources it intends to access in its corresponding metadata attribute using the `local` and `shared` arguments. Each argument takes a list of resource identifiers. The listed resources are made available to the context under the `local` and `shared` fields of the `Context` structure. -The `init` task returns the initial values for the system-wide (`#[shared]` and `#[local]`) -resources, and the set of initialized timers used by the application. The monotonic timers will be -further discussed in [Monotonic & `spawn_{at/after}`](./monotonic.md). +The `init` task returns the initial values for the system-wide (`#[shared]` and `#[local]`) resources. + + ## `#[local]` resources -`#[local]` resources are locally accessible to a specific task, meaning that only that task can -access the resource and does so without locks or critical sections. This allows for the resources, -commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific -task. +`#[local]` resources accessible only to a single task. This task is given unique access to the resource without the use of locks or critical sections. -Thus, a task `#[local]` resource can only be accessed by one singular task. -Attempting to assign the same `#[local]` resource to more than one task is a compile-time error. +This allows for the resources, commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific task. (Thus, a task `#[local]` resource can only be accessed by one single task.) Attempting to assign the same `#[local]` resource to more than one task is a compile-time error. -Types of `#[local]` resources must implement a [`Send`] trait as they are being sent from `init` -to a target task, crossing a thread boundary. +Types of `#[local]` resources must implement [`Send`] trait as they are being sent from `init` to the target task and thus crossing the *thread* boundary. [`Send`]: https://doc.rust-lang.org/stable/core/marker/trait.Send.html -The example application shown below contains two tasks where each task has access to its own -`#[local]` resource; the `idle` task has its own `#[local]` as well. +The example application shown below contains three tasks `foo`, `bar` and `idle`, each having access to its own `#[local]` resource. ``` rust -{{#include ../../../../examples/locals.rs}} +{{#include ../../../../rtic/examples/locals.rs}} ``` Running the example: ``` console $ cargo run --target thumbv7m-none-eabi --example locals -{{#include ../../../../ci/expected/locals.run}} +{{#include ../../../../rtic/ci/expected/locals.run}} ``` -Local resources in `#[init]` and `#[idle]` have `'static` -lifetimes. This is safe since both tasks are not re-entrant. - ### Task local initialized resources -Local resources can also be specified directly in the resource claim like so: -`#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`; this allows for creating locals which do no need to be -initialized in `#[init]`. +A special use-case of local resources are the ones specified directly in the task declaration, `#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`. This allows for creating locals which do no need to be initialized in `#[init]`. Moreover, local resources in `#[init]` and `#[idle]` have `'static` lifetimes, this is safe since both are not re-entrant. -Types of `#[task(local = [..])]` resources have to be neither [`Send`] nor [`Sync`] as they -are not crossing any thread boundary. +Types of `#[task(local = [..])]` resources have to be neither [`Send`] nor [`Sync`] as they are not crossing any thread boundary. [`Sync`]: https://doc.rust-lang.org/stable/core/marker/trait.Sync.html In the example below the different uses and lifetimes are shown: ``` rust -{{#include ../../../../examples/declared_locals.rs}} +{{#include ../../../../rtic/examples/declared_locals.rs}} ``` - +You can run the application, but as the example is designed merely to showcase the lifetime properties there is no output (it suffices to build the application). + +``` console +$ cargo build --target thumbv7m-none-eabi --example declared_locals +``` + ## `#[shared]` resources and `lock` -Critical sections are required to access `#[shared]` resources in a data race-free manner and to -achieve this the `shared` field of the passed `Context` implements the [`Mutex`] trait for each -shared resource accessible to the task. This trait has only one method, [`lock`], which runs its -closure argument in a critical section. +Critical sections are required to access `#[shared]` resources in a data race-free manner and to achieve this the `shared` field of the passed `Context` implements the [`Mutex`] trait for each shared resource accessible to the task. This trait has only one method, [`lock`], which runs its closure argument in a critical section. [`Mutex`]: ../../../api/rtic/trait.Mutex.html [`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock -The critical section created by the `lock` API is based on dynamic priorities: it temporarily -raises the dynamic priority of the context to a *ceiling* priority that prevents other tasks from -preempting the critical section. This synchronization protocol is known as the -[Immediate Ceiling Priority Protocol (ICPP)][icpp], and complies with -[Stack Resource Policy (SRP)][srp] based scheduling of RTIC. +The critical section created by the `lock` API is based on dynamic priorities: it temporarily raises the dynamic priority of the context to a *ceiling* priority that prevents other tasks from preempting the critical section. This synchronization protocol is known as the [Immediate Ceiling Priority Protocol (ICPP)][icpp], and complies with [Stack Resource Policy (SRP)][srp] based scheduling of RTIC. [icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol [srp]: https://en.wikipedia.org/wiki/Stack_Resource_Policy -In the example below we have three interrupt handlers with priorities ranging from one to three. -The two handlers with the lower priorities contend for a `shared` resource and need to succeed in locking the -resource in order to access its data. The highest priority handler, which does not access the `shared` -resource, is free to preempt a critical section created by the lowest priority handler. +In the example below we have three interrupt handlers with priorities ranging from one to three. The two handlers with the lower priorities contend for the `shared` resource and need to lock the resource for accessing the data. The highest priority handler, which do not access the `shared` resource, is free to preempt the critical section created by the lowest priority handler. ``` rust -{{#include ../../../../examples/lock.rs}} +{{#include ../../../../rtic/examples/lock.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example lock -{{#include ../../../../ci/expected/lock.run}} +{{#include ../../../../rtic/ci/expected/lock.run}} ``` Types of `#[shared]` resources have to be [`Send`]. ## Multi-lock -As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The -following examples show this in use: +As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The following examples show this in use: ``` rust -{{#include ../../../../examples/multilock.rs}} +{{#include ../../../../rtic/examples/multilock.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example multilock -{{#include ../../../../ci/expected/multilock.run}} +{{#include ../../../../rtic/ci/expected/multilock.run}} ``` ## Only shared (`&-`) access -By default, the framework assumes that all tasks require exclusive access (`&mut-`) to resources, -but it is possible to specify that a task only requires shared access (`&-`) to a resource using the -`&resource_name` syntax in the `shared` list. +By default, the framework assumes that all tasks require exclusive mutable access (`&mut-`) to resources, but it is possible to specify that a task only requires shared access (`&-`) to a resource using the `&resource_name` syntax in the `shared` list. -The advantage of specifying shared access (`&-`) to a resource is that no locks are required to -access the resource even if the resource is contended by more than one task running at different -priorities. The downside is that the task only gets a shared reference (`&-`) to the resource, -limiting the operations it can perform on it, but where a shared reference is enough this approach -reduces the number of required locks. In addition to simple immutable data, this shared access can -be useful where the resource type safely implements interior mutability, with appropriate locking -or atomic operations of its own. +The advantage of specifying shared access (`&-`) to a resource is that no locks are required to access the resource even if the resource is contended by more than one task running at different priorities. The downside is that the task only gets a shared reference (`&-`) to the resource, limiting the operations it can perform on it, but where a shared reference is enough this approach reduces the number of required locks. In addition to simple immutable data, this shared access can be useful where the resource type safely implements interior mutability, with appropriate locking or atomic operations of its own. -Note that in this release of RTIC it is not possible to request both exclusive access (`&mut-`) -and shared access (`&-`) to the *same* resource from different tasks. Attempting to do so will -result in a compile error. +Note that in this release of RTIC it is not possible to request both exclusive access (`&mut-`) and shared access (`&-`) to the *same* resource from different tasks. Attempting to do so will result in a compile error. -In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime and then -used from two tasks that run at different priorities without any kind of lock. +In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime (returned by `init`) and then used from two tasks that run at different priorities without any kind of lock. ``` rust -{{#include ../../../../examples/only-shared-access.rs}} +{{#include ../../../../rtic/examples/only-shared-access.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example only-shared-access -{{#include ../../../../ci/expected/only-shared-access.run}} +{{#include ../../../../rtic/ci/expected/only-shared-access.run}} ``` -## Lock-free resource access of shared resources +## Lock-free access of shared resources -A critical section is *not* required to access a `#[shared]` resource that's only accessed by tasks -running at the *same* priority. In this case, you can opt out of the `lock` API by adding the -`#[lock_free]` field-level attribute to the resource declaration (see example below). Note that -this is merely a convenience to reduce needless resource locking code, because even if the +A critical section is *not* required to access a `#[shared]` resource that's only accessed by tasks running at the *same* priority. In this case, you can opt out of the `lock` API by adding the `#[lock_free]` field-level attribute to the resource declaration (see example below). + + -Also worth noting: using `#[lock_free]` on resources shared by -tasks running at different priorities will result in a *compile-time* error -- not using the `lock` -API would be a data race in that case. +To adhere to the Rust [aliasing] rule, a resource may be either accessed through multiple immutable references or a singe mutable reference (but not both at the same time). + +[aliasing]: https://doc.rust-lang.org/nomicon/aliasing.html + +Using `#[lock_free]` on resources shared by tasks running at different priorities will result in a *compile-time* error -- not using the `lock` API would violate the aforementioned alias rule. Similarly, for each priority there can be only a single *software* task accessing a shared resource (as an `async` task may yield execution to other *software* or *hardware* tasks running at the same priority). However, under this single-task restriction, we make the observation that the resource is in effect no longer `shared` but rather `local`. Thus, using a `#[lock_free]` shared resource will result in a *compile-time* error -- where applicable, use a `#[local]` resource instead. ``` rust -{{#include ../../../../examples/lock-free.rs}} +{{#include ../../../../rtic/examples/lock-free.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example lock-free -{{#include ../../../../ci/expected/lock-free.run}} +{{#include ../../../../rtic/ci/expected/lock-free.run}} ``` diff --git a/book/en/src/by-example/software_tasks.md b/book/en/src/by-example/software_tasks.md index 8ee185bd15..27527078d3 100644 --- a/book/en/src/by-example/software_tasks.md +++ b/book/en/src/by-example/software_tasks.md @@ -1,47 +1,99 @@ # Software tasks & spawn -The RTIC concept of a software task shares a lot with that of [hardware tasks](./hardware_tasks.md) -with the core difference that a software task is not explicitly bound to a specific -interrupt vector, but rather bound to a “dispatcher” interrupt vector running -at the intended priority of the software task (see below). +The RTIC concept of a *software* task shares a lot with that of [hardware tasks](./hardware_tasks.md) with the core difference that a software task is not explicitly bound to a specific +interrupt vector, but rather to a “dispatcher” interrupt vector running at the same priority as the software task. -Thus, software tasks are tasks which are not *directly* bound to an interrupt vector. +Similarly to *hardware* tasks, the `#[task]` attribute used on a function declare it as a task. The absence of a `binds = InterruptName` argument to the attribute declares the function as a *software task*. -The `#[task]` attributes used on a function determine if it is -software tasks, specifically the absence of a `binds = InterruptName` -argument to the attribute definition. +The static method `task_name::spawn()` spawns (starts) a software task and given that there are no higher priority tasks running the task will start executing directly. -The static method `task_name::spawn()` spawns (schedules) a software -task by registering it with a specific dispatcher. If there are no -higher priority tasks available to the scheduler (which serves a set -of dispatchers), the task will start executing directly. +The *software* task itself is given as an `async` Rust function, which allows the user to optionally `await` future events. This allows to blend reactive programming (by means of *hardware* tasks) with sequential programming (by means of *software* tasks). -All software tasks at the same priority level share an interrupt handler bound to their dispatcher. -What differentiates software and hardware tasks is the usage of either a dispatcher or a bound interrupt vector. +Whereas, *hardware* tasks are assumed to run-to-completion (and return), *software* tasks may be started (`spawned`) once and run forever, with the side condition that any loop (execution path) is broken by at least one `await` (yielding operation). -The interrupt vectors used as dispatchers cannot be used by hardware tasks. +All *software* tasks at the same priority level shares an interrupt handler acting as an async executor dispatching the software tasks. -Availability of a set of “free” (not in use by hardware tasks) and usable interrupt vectors allows the framework -to dispatch software tasks via dedicated interrupt handlers. +This list of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an argument to the `#[app]` attribute, where you define the set of free and usable interrupts. -This set of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an -argument to the `#[app]` attribute. +Each interrupt vector acting as dispatcher gets assigned to one priority level meaning that the list of dispatchers need to cover all priority levels used by software tasks. -Each interrupt vector acting as dispatcher gets assigned to a unique priority level meaning that -the list of dispatchers needs to cover all priority levels used by software tasks. +Example: The `dispatchers =` argument needs to have at least 3 entries for an application using three different priorities for software tasks. -Example: The `dispatchers =` argument needs to have at least 3 entries for an application using -three different priorities for software tasks. - -The framework will give a compilation error if there are not enough dispatchers provided. +The framework will give a compilation error if there are not enough dispatchers provided, or if a clash occurs between the list of dispatchers and interrupts bound to *hardware* tasks. See the following example: ``` rust -{{#include ../../../../examples/spawn.rs}} +{{#include ../../../../rtic/examples/spawn.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example spawn -{{#include ../../../../ci/expected/spawn.run}} +{{#include ../../../../rtic/ci/expected/spawn.run}} ``` +You may `spawn` a *software* task again, given that it has run-to-completion (returned). + +In the below example, we `spawn` the *software* task `foo` from the `idle` task. Since the default priority of the *software* task is 1 (higher than `idle`), the dispatcher will execute `foo` (preempting `idle`). Since `foo` runs-to-completion. It is ok to `spawn` the `foo` task again. + +Technically the async executor will `poll` the `foo` *future* which in this case leaves the *future* in a *completed* state. + +``` rust +{{#include ../../../../rtic/examples/spawn_loop.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example spawn_loop +{{#include ../../../../rtic/ci/expected/spawn_loop.run}} +``` + +An attempt to `spawn` an already spawned task (running) task will result in an error. Notice, the that the error is reported before the `foo` task is actually run. This is since, the actual execution of the *software* task is handled by the dispatcher interrupt (`SSIO`), which is not enabled until we exit the `init` task. (Remember, `init` runs in a critical section, i.e. all interrupts being disabled.) + +Technically, a `spawn` to a *future* that is not in *completed* state is considered an error. + +``` rust +{{#include ../../../../rtic/examples/spawn_err.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example spawn_err +{{#include ../../../../rtic/ci/expected/spawn_err.run}} +``` + +## Passing arguments +You can also pass arguments at spawn as follows. + +``` rust +{{#include ../../../../rtic/examples/spawn_arguments.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example spawn_arguments +{{#include ../../../../rtic/ci/expected/spawn_arguments.run}} +``` + +## Priority zero tasks + +In RTIC tasks run preemptively to each other, with priority zero (0) the lowest priority. You can use priority zero tasks for background work, without any strict real-time requirements. + +Conceptually, one can see such tasks as running in the `main` thread of the application, thus the resources associated are not required the [Send] bound. + +[Send]: https://doc.rust-lang.org/nomicon/send-and-sync.html + + +``` rust +{{#include ../../../../rtic/examples/zero-prio-task.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example zero-prio-task +{{#include ../../../../rtic/ci/expected/zero-prio-task.run}} +``` + +> **Notice**: *software* task at zero priority cannot co-exist with the [idle] task. The reason is that `idle` is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to *software* tasks at the same priority. + +--- + +Application side safety: Technically, the RTIC framework ensures that `poll` is never executed on any *software* task with *completed* future, thus adhering to the soundness rules of async Rust. + + + diff --git a/book/en/src/by-example/starting_a_project.md b/book/en/src/by-example/starting_a_project.md index fe7be57818..8638f9064d 100644 --- a/book/en/src/by-example/starting_a_project.md +++ b/book/en/src/by-example/starting_a_project.md @@ -10,6 +10,8 @@ If you are targeting ARMv6-M or ARMv8-M-base architecture, check out the section This will give you an RTIC application with support for RTT logging with [`defmt`] and stack overflow protection using [`flip-link`]. There is also a multitude of examples provided by the community: +For inspiration you may look at the below resources. For now they cover RTIC 1.0.x, but will be updated with RTIC 2.0.x examples over time. + - [`rtic-examples`] - Multiple projects - [https://github.com/kalkyl/f411-rtic](https://github.com/kalkyl/f411-rtic) - ... More to come diff --git a/book/en/src/preface.md b/book/en/src/preface.md index 6041dfedea..3f47cb3d1c 100644 --- a/book/en/src/preface.md +++ b/book/en/src/preface.md @@ -1,7 +1,7 @@
RTIC
-

Real-Time Interrupt-driven Concurrency

+

The Embedded Rust RTOS

A concurrency framework for building real-time systems

@@ -10,29 +10,160 @@ This book contains user level documentation for the Real-Time Interrupt-driven Concurrency (RTIC) framework. The API reference is available [here](../../api/). -Formerly known as Real-Time For the Masses. + -This is the documentation of v1.0.x of RTIC; for the documentation of version +This is the documentation of v2.0.x (pre-release) of RTIC 2. +## RTIC - The Past, current and Future + +This section gives a background to the RTIC model. Feel free to skip to section [RTIC the model](preface.md#rtic-the-model) for a TL;DR. + +The RTIC framework takes the outset from real-time systems research at Luleå University of Technology (LTU) Sweden. RTIC is inspired by the concurrency model of the [Timber] language, the [RTFM-SRP] based scheduler, the [RTFM-core] language and [Abstract Timer] implementation. For a full list of related research see [TODO]. + +[Timber]: https://timber-lang.org/ +[RTFM-SRP]: https://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf +[RTFM-core]: https://ltu.diva-portal.org/smash/get/diva2:1013248/FULLTEXT01.pdf +[AbstractTimer]: https://ltu.diva-portal.org/smash/get/diva2:1013030/FULLTEXT01.pdf + +## Stack Resource Policy based Scheduling + +Stack Resource Policy (SRP) based concurrency and resource management is at heart of the RTIC framework. The [SRP] model itself extends on [Priority Inheritance Protocols], and provides a set of outstanding properties for single core scheduling. To name a few: + +- preemptive deadlock and race-free scheduling +- resource efficiency + - tasks execute on a single shared stack + - tasks run-to-completion with wait free access to shared resources +- predictable scheduling, with bounded priority inversion by a single (named) critical section +- theoretical underpinning amenable to static analysis (e.g., for task response times and overall schedulability) + +SRP comes with a set of system wide requirements: +- each task is associated a static priority, +- tasks execute on a single-core, +- tasks must be run-to-completion, and +- resources must be claimed/locked in LIFO order. + +[SRP]: https://link.springer.com/article/10.1007/BF00365393 +[Priority Inheritance Protocols]: https://ieeexplore.ieee.org/document/57058 + +## SRP analysis + +SRP based scheduling requires the set of static priority tasks and their access to shared resources to be known in order to compute a static *ceiling* (𝝅) for each resource. The static resource *ceiling* 𝝅(r) reflects the maximum static priority of any task that accesses the resource `r`. + +### Example + +Assume two tasks `A` (with priority `p(A) = 2`) and `B` (with priority `p(B) = 4`) both accessing the shared resource `R`. The static ceiling of `R` is 4 (computed from `𝝅(R) = max(p(A) = 2, p(B) = 4) = 4`). + +A graph representation of the example: + +```mermaid +graph LR + A["p(A) = 2"] --> R + B["p(A) = 4"] --> R + R["𝝅(R) = 4"] +``` + +## RTIC the hardware accelerated real-time scheduler + +SRP itself is compatible both to dynamic and static priority scheduling. For the implementation of RTIC we leverage on the underlying hardware for accelerated static priority scheduling. + +In the case of the `ARM Cortex-M` architecture, each interrupt vector entry `v[i]` is associated a function pointer (`v[i].fn`), and a static priority (`v[i].priority`), an enabled- (`v[i].enabled`) and a pending-bit (`v[i].pending`). + +An interrupt `i` is scheduled (run) by the hardware under the conditions: +1. is `pended` and `enabled` and has a priority higher than the (optional `BASEPRI`) register, and +1. has the highest priority among interrupts meeting 1. + +The first condition (1) can be seen a filter allowing RTIC to take control over which tasks should be allowed to start (and which should be prevented from starting). + +The SPR model for single-core static scheduling on the other hand states that a task should be scheduled (run) under the conditions: +1. it is `requested` to run and has a static priority higher than the current system ceiling (𝜫) +1. it has the highest static priority among tasks meeting 1. + +The similarities are striking and it is not by chance/luck/coincidence. The hardware was cleverly designed with real-time scheduling in mind. + +In order to map the SRP scheduling onto the hardware we need to have a closer look on the system ceiling (𝜫). Under SRP 𝜫 is computed as the maximum priority ceiling of the currently held resources, and will thus change dynamically during the system operation. + +## Example + +Assume the task model above. Starting from an idle system, 𝜫 is 0, (no task is holding any resource). Assume that `A` is requested for execution, it will immediately be scheduled. Assume that `A` claims (locks) the resource `R`. During the claim (lock of `R`) any request `B` will be blocked from starting (by 𝜫 = `max(𝝅(R) = 4) = 4`, `p(B) = 4`, thus SRP scheduling condition 1 is not met). + +## Mapping + +The mapping of static priority SRP based scheduling to the Cortex M hardware is straightforward: + +- each task `t` are mapped to an interrupt vector index `i` with a corresponding function `v[i].fn = t` and given the static priority `v[i].priority = p(t)`. +- the current system ceiling is mapped to the `BASEPRI` register or implemented through masking the interrupt enable bits accordingly. + +## Example + +For the running example, a snapshot of the ARM Cortex M [NVIC] may have the following configuration (after task `A` has been pended for execution.) + +| Index | Fn | Priority | Enabled | Pended | +| ----- | --- | -------- | ------- | ------ | +| 0 | A | 2 | true | true | +| 1 | B | 4 | true | false | + +[NVIC]: https://developer.arm.com/documentation/ddi0337/h/nested-vectored-interrupt-controller/about-the-nvic + +(As discussed later, the assignment of interrupt and exception vectors is up to the user.) + + +A claim (lock(r)) will change the current system ceiling (𝜫) and can be implemented as a *named* critical section: + - old_ceiling = 𝜫, 𝜫 = 𝝅(r) + - execute code within critical section + - old_ceiling = 𝜫 + +This amounts to a resource protection mechanism requiring only two machine instructions on enter and one on exit the critical section for managing the `BASEPRI` register. For architectures lacking `BASEPRI`, we can implement the system ceiling through a set of machine instructions for disabling/enabling interrupts on entry/exit for the named critical section. The number of machine instructions vary depending on the number of mask registers that needs to be updated (a single machine operation can operate on up to 32 interrupts, so for the M0/M0+ architecture a single instruction suffice). RTIC will determine the ceiling values and masking constants at compile time, thus all operations is in Rust terms zero-cost. + +In this way RTIC fuses SRP based preemptive scheduling with a zero-cost hardware accelerated implementation, resulting in "best in class" guarantees and performance. + +Given that the approach is dead simple, how come SRP and hardware accelerated scheduling is not adopted by any other mainstream RTOS? + +The answer is simple, the commonly adopted threading model does not lend itself well to static analysis - there is no known way to extract the task/resource dependencies from the source code at compile time (thus ceilings cannot be efficiently computed and the LIFO resource locking requirement cannot be ensured). Thus SRP based scheduling is in the general case out of reach for any thread based RTOS. + +## RTIC into the Future + +Asynchronous programming in various forms are getting increased popularity and language support. Rust natively provides an `async`/`await` API for cooperative multitasking and the compiler generates the necessary boilerplate for storing and retrieving execution contexts (i.e., managing the set of local variables that spans each `await`). + +The Rust standard library provides collections for dynamically allocated data-structures (useful to manage execution contexts at run-time. However, in the setting of resource constrained real-time systems, dynamic allocations are problematic (both regarding performance and reliability - Rust runs into a *panic* on an out-of-memory condition). Thus, static allocation is king! + +RTIC provides a mechanism for `async`/`await` that relies solely on static allocations. However, the implementation relies on the `#![feature(type_alias_impl_trait)]` (TAIT) which is undergoing stabilization (thus RTIC 2.0.x currently requires a *nightly* toolchain). Technically, using TAIT, the compiler determines the size of each execution context allowing static allocation. + +From a modelling perspective `async/await` lifts the run-to-completion requirement of SRP, and each section of code between two yield points (`await`s) can be seen as an individual task. The compiler will reject any attempt to `await` while holding a resource (not doing so would break the strict LIFO requirement on resource usage under SRP). + +So with the technical stuff out of the way, what does `async/await` bring to the RTIC table? + +The answer is - improved ergonomics! In cases you want a task to perform a sequence of requests (and await their results in order to progress). Without `async`/`await` the programmer would be forced to split the task into individual sub-tasks and maintain some sort of state encoding (and manually progress by selecting sub-task). Using `async/await` each yield point (`await`) essentially represents a state, and the progression mechanism is built automatically for you at compile time by means of `Futures`. + +Rust `async`/`await` support is still incomplete and/or under development (e.g., there are no stable way to express `async` closures, precluding use in iterator patterns). Nevertheless, Rust `async`/`await` is production ready and covers most common use cases. + +An important property is that futures are composable, thus you can await either, all, or any combination of possible futures (allowing e.g., timeouts and/or asynchronous errors to be promptly handled). For more details and examples see Section [todo]. + +## RTIC the model + +An RTIC `app` is a declarative and executable system model for single-core applications, defining a set of (`local` and `shared`) resources operated on by a set of (`init`, `idle`, *hardware* and *software*) tasks. In short the `init` task runs before any other task returning a set of resources (`local` and `shared`). Tasks run preemptively based on their associated static priority, `idle` has the lowest priority (and can be used for background work, and/or to put the system to sleep until woken by some event). Hardware tasks are bound to underlying hardware interrupts, while software tasks are scheduled by asynchronous executors (one for each software task priority). + +At compile time the task/resource model is analyzed under SRP and executable code generated with the following outstanding properties: + +- guaranteed race-free resource access and deadlock-free execution on a single-shared stack (thanks to SRP) + - hardware task scheduling is performed directly by the hardware, and + - software task scheduling is performed by auto generated async executors tailored to the application. + +The RTIC API design ensures that both SRP requirements and Rust soundness rules are upheld at all times, thus the executable model is correct by construction. Overall, the generated code infers no additional overhead in comparison to a hand-written implementation, thus in Rust terms RTIC offers a zero-cost abstraction to concurrency. + + + diff --git a/book/en/src/rtic_vs.md b/book/en/src/rtic_vs.md new file mode 100644 index 0000000000..2f8c8d5856 --- /dev/null +++ b/book/en/src/rtic_vs.md @@ -0,0 +1,31 @@ +# RTIC vs. the world + +RTIC aims to provide the lowest level of abstraction needed for developing robust and reliable embedded software. + +It provides a minimal set of required mechanisms for safe sharing of mutable resources among interrupts and asynchronously executing tasks. The scheduling primitives leverages on the underlying hardware for unparalleled performance and predictability, in effect RTIC provides in Rust terms a zero-cost abstraction to concurrent real-time programming. + + + +## Comparison regarding safety and security + +Comparing RTIC to traditional a Real-Time Operating System (RTOS) is hard. Firstly, a traditional RTOS typically comes with no guarantees regarding system safety, even the most hardened kernels like the formally verified [seL4] kernel. Their claims to integrity, confidentiality, and availability regards only the kernel itself (under additional assumptions its configuration and environment). They even state: + +"An OS kernel, verified or not, does not automatically make a system secure. In fact, any system, no matter how secure, can be used in insecure ways." + +[seL4]: https://sel4.systems/ + +### Security by design + +In the world of information security we commonly find: + +- confidentiality, protecting the information from being exposed to an unauthorized party, +- integrity, referring to accuracy and completeness of data, and +- availability, referring to data being accessible to authorized users. + +Obviously, a traditional OS can guarantee neither confidentiality nor integrity, as both requires the security critical code to be trusted. Regarding availability, this typically boils down to the usage of system resources. Any OS that allows for dynamic allocation of resources, relies on that the application correctly handles allocations/de-allocations, and cases of allocation failures. + +Thus their claim is correct, security is completely out of hands for the OS, the best we can hope for is that it does not add further vulnerabilities. + +RTIC on the other hand holds your back. The declarative system wide model gives you a static set of tasks and resources, with precise control over what data is shared and between which parties. Moreover, Rust as a programming language comes with strong properties regarding integrity (compile time aliasing, mutability and lifetime guarantees, together with ensured data validity). + +Using RTIC these properties propagate to the system wide model, without interference of other applications running. The RTIC kernel is internally infallible without any need of dynamically allocated data. \ No newline at end of file diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml index 12a34da1b8..9b8a2916c3 100644 --- a/rtic/Cargo.toml +++ b/rtic/Cargo.toml @@ -10,7 +10,19 @@ categories = ["concurrency", "embedded", "no-std", "asynchronous"] description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" documentation = "https://rtic.rs/" edition = "2021" -keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] +keywords = [ + "arm", + "cortex-m", + "risc-v", + "embedded", + "async", + "runtime", + "futures", + "await", + "no-std", + "rtos", + "bare-metal", +] license = "MIT OR Apache-2.0" name = "rtic" readme = "README.md" @@ -31,6 +43,7 @@ bare-metal = "1.0.0" #portable-atomic = { version = "0.3.19" } atomic-polyfill = "1" + [build-dependencies] version_check = "0.9" @@ -42,6 +55,11 @@ rtic-time = { path = "../rtic-time" } rtic-channel = { path = "../rtic-channel" } rtic-monotonics = { path = "../rtic-monotonics" } +[dev-dependencies.futures] +version = "0.3.26" +default-features = false +features = ["async-await"] + [dev-dependencies.panic-semihosting] features = ["exit"] version = "0.6.0" diff --git a/rtic/ci/expected/async-channel-done.run b/rtic/ci/expected/async-channel-done.run new file mode 100644 index 0000000000..525962a2a0 --- /dev/null +++ b/rtic/ci/expected/async-channel-done.run @@ -0,0 +1,9 @@ +Sender 1 sending: 1 +Sender 1 done +Sender 2 sending: 2 +Sender 3 sending: 3 +Receiver got: 1 +Sender 2 done +Receiver got: 2 +Sender 3 done +Receiver got: 3 diff --git a/rtic/ci/expected/async-channel-no-receiver.run b/rtic/ci/expected/async-channel-no-receiver.run new file mode 100644 index 0000000000..34624e16ce --- /dev/null +++ b/rtic/ci/expected/async-channel-no-receiver.run @@ -0,0 +1 @@ +Sender 1 sending: 1 Err(NoReceiver(1)) diff --git a/rtic/ci/expected/async-channel-no-sender.run b/rtic/ci/expected/async-channel-no-sender.run new file mode 100644 index 0000000000..237f2f145a --- /dev/null +++ b/rtic/ci/expected/async-channel-no-sender.run @@ -0,0 +1 @@ +Receiver got: Err(NoSender) diff --git a/rtic/ci/expected/async-channel-try.run b/rtic/ci/expected/async-channel-try.run new file mode 100644 index 0000000000..c3a4092f84 --- /dev/null +++ b/rtic/ci/expected/async-channel-try.run @@ -0,0 +1,2 @@ +Sender 1 sending: 1 +Sender 1 try sending: 2 Err(Full(2)) diff --git a/rtic/ci/expected/async-channel.run b/rtic/ci/expected/async-channel.run index 3e3c232427..4e313a12e7 100644 --- a/rtic/ci/expected/async-channel.run +++ b/rtic/ci/expected/async-channel.run @@ -3,4 +3,4 @@ Sender 2 sending: 2 Sender 3 sending: 3 Receiver got: 1 Receiver got: 2 -Receiver got: 5 +Receiver got: 3 diff --git a/rtic/ci/expected/async-timeout.run b/rtic/ci/expected/async-timeout.run index a8074230ee..ca929c7b4c 100644 --- a/rtic/ci/expected/async-timeout.run +++ b/rtic/ci/expected/async-timeout.run @@ -1,5 +1,16 @@ init -hello from bar -hello from foo -foo no timeout -bar timeout +the hal takes a duration of Duration { ticks: 450 } +timeout +the hal takes a duration of Duration { ticks: 450 } +hal returned 5 +the hal takes a duration of Duration { ticks: 450 } +hal returned 5 +now is Instant { ticks: 2102 }, timeout at Instant { ticks: 2602 } +the hal takes a duration of Duration { ticks: 350 } +hal returned 5 at time Instant { ticks: 2452 } +now is Instant { ticks: 3102 }, timeout at Instant { ticks: 3602 } +the hal takes a duration of Duration { ticks: 450 } +hal returned 5 at time Instant { ticks: 3552 } +now is Instant { ticks: 4102 }, timeout at Instant { ticks: 4602 } +the hal takes a duration of Duration { ticks: 550 } +timeout diff --git a/rtic/ci/expected/common.run b/rtic/ci/expected/common.run index e69de29bb2..4f1d3509c2 100644 --- a/rtic/ci/expected/common.run +++ b/rtic/ci/expected/common.run @@ -0,0 +1,3 @@ +bar: local_to_bar = 1 +foo: local_to_foo = 1 +idle: local_to_idle = 1 diff --git a/rtic/ci/expected/message_passing.run b/rtic/ci/expected/spawn_arguments.run similarity index 100% rename from rtic/ci/expected/message_passing.run rename to rtic/ci/expected/spawn_arguments.run diff --git a/rtic/ci/expected/spawn_err.run b/rtic/ci/expected/spawn_err.run new file mode 100644 index 0000000000..97c4112c03 --- /dev/null +++ b/rtic/ci/expected/spawn_err.run @@ -0,0 +1,3 @@ +init +Cannot spawn a spawned (running) task! +foo diff --git a/rtic/ci/expected/spawn_loop.run b/rtic/ci/expected/spawn_loop.run new file mode 100644 index 0000000000..ee6e1e30a2 --- /dev/null +++ b/rtic/ci/expected/spawn_loop.run @@ -0,0 +1,7 @@ +init +foo +idle +foo +idle +foo +idle diff --git a/rtic/examples/async-channel-done.rs b/rtic/examples/async-channel-done.rs new file mode 100644 index 0000000000..1c32d32b9e --- /dev/null +++ b/rtic/examples/async-channel-done.rs @@ -0,0 +1,65 @@ +//! examples/async-channel-done.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, CAPACITY); + + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + sender2::spawn(s.clone()).unwrap(); + sender3::spawn(s).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + if val == 3 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + } + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); + hprintln!("Sender 1 done"); + } + + #[task] + async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 2 sending: 2"); + sender.send(2).await.unwrap(); + hprintln!("Sender 2 done"); + } + + #[task] + async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 3 sending: 3"); + sender.send(3).await.unwrap(); + hprintln!("Sender 3 done"); + } +} diff --git a/rtic/examples/async-channel-no-receiver.rs b/rtic/examples/async-channel-no-receiver.rs new file mode 100644 index 0000000000..ffb78e4e03 --- /dev/null +++ b/rtic/examples/async-channel-no-receiver.rs @@ -0,0 +1,40 @@ +//! examples/async-channel-no-receiver.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, _r) = make_channel!(u32, CAPACITY); + + sender1::spawn(s.clone()).unwrap(); + + (Shared {}, Local {}) + } + + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + + hprintln!("Sender 1 sending: 1 {:?}", sender.send(1).await); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + +} diff --git a/rtic/examples/async-channel-no-sender.rs b/rtic/examples/async-channel-no-sender.rs new file mode 100644 index 0000000000..58e010d585 --- /dev/null +++ b/rtic/examples/async-channel-no-sender.rs @@ -0,0 +1,40 @@ +//! examples/async-channel-no-sender.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (_s, r) = make_channel!(u32, CAPACITY); + + receiver::spawn(r).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + hprintln!("Receiver got: {:?}", receiver.recv().await); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + +} diff --git a/rtic/examples/async-channel-try.rs b/rtic/examples/async-channel-try.rs new file mode 100644 index 0000000000..4a79935e82 --- /dev/null +++ b/rtic/examples/async-channel-try.rs @@ -0,0 +1,48 @@ +//! examples/async-channel-try.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, CAPACITY); + + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + } + } + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); + hprintln!("Sender 1 try sending: 2 {:?}", sender.try_send(2)); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + +} diff --git a/rtic/examples/async-channel.rs b/rtic/examples/async-channel.rs index 3a2e491181..58eeee87a7 100644 --- a/rtic/examples/async-channel.rs +++ b/rtic/examples/async-channel.rs @@ -19,9 +19,10 @@ mod app { #[local] struct Local {} + const CAPACITY: usize = 5; #[init] fn init(_: init::Context) -> (Shared, Local) { - let (s, r) = make_channel!(u32, 5); + let (s, r) = make_channel!(u32, CAPACITY); receiver::spawn(r).unwrap(); sender1::spawn(s.clone()).unwrap(); @@ -32,30 +33,30 @@ mod app { } #[task] - async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, 5>) { + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { while let Ok(val) = receiver.recv().await { hprintln!("Receiver got: {}", val); - if val == 5 { + if val == 3 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } } #[task] - async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, 5>) { + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { hprintln!("Sender 1 sending: 1"); sender.send(1).await.unwrap(); } #[task] - async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, 5>) { + async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, CAPACITY>) { hprintln!("Sender 2 sending: 2"); sender.send(2).await.unwrap(); } #[task] - async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, 5>) { + async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, CAPACITY>) { hprintln!("Sender 3 sending: 3"); - sender.send(5).await.unwrap(); + sender.send(3).await.unwrap(); } } diff --git a/rtic/examples/async-delay.rs b/rtic/examples/async-delay.rs index 0440f774ad..6c79cf54ca 100644 --- a/rtic/examples/async-delay.rs +++ b/rtic/examples/async-delay.rs @@ -1,3 +1,5 @@ +// examples/async-delay.rs +// #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/rtic/examples/async-timeout.rs b/rtic/examples/async-timeout.rs new file mode 100644 index 0000000000..30fee43e2c --- /dev/null +++ b/rtic/examples/async-timeout.rs @@ -0,0 +1,87 @@ +// examples/async-timeout.rs +// +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; +use rtic_monotonics::systick_monotonic::*; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use super::*; + use futures::{future::FutureExt, select_biased}; + + rtic_monotonics::make_systick_timer_queue!(TIMER); + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + let systick = Systick::start(cx.core.SYST, 12_000_000); + TIMER.initialize(systick); + + foo::spawn().ok(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_cx: foo::Context) { + // Call hal with short relative timeout using `select_biased` + select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), + _ = TIMER.delay(200.millis()).fuse() => hprintln!("timeout", ), // this will finish first + } + + // Call hal with long relative timeout using `select_biased` + select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), // hal finish first + _ = TIMER.delay(1000.millis()).fuse() => hprintln!("timeout", ), + } + + // Call hal with long relative timeout using monotonic `timeout_after` + match TIMER.timeout_after(1000.millis(), hal_get(&TIMER, 1)).await { + Ok(v) => hprintln!("hal returned {}", v), + _ => hprintln!("timeout"), + } + + // get the current time instance + let mut instant = TIMER.now(); + + // do this 3 times + for n in 0..3 { + // exact point in time without drift + instant += 1000.millis(); + TIMER.delay_until(instant).await; + + // exact point it time for timeout + let timeout = instant + 500.millis(); + hprintln!("now is {:?}, timeout at {:?}", TIMER.now(), timeout); + + match TIMER.timeout_at(timeout, hal_get(&TIMER, n)).await { + Ok(v) => hprintln!("hal returned {} at time {:?}", v, TIMER.now()), + _ => hprintln!("timeout"), + } + } + + debug::exit(debug::EXIT_SUCCESS); + } +} + +// Emulate some hal +async fn hal_get(timer: &'static SystickTimerQueue, n: u32) -> u32 { + // emulate some delay time dependent on n + let d = 350.millis() + n * 100.millis(); + hprintln!("the hal takes a duration of {:?}", d); + timer.delay(d).await; + // emulate some return value + 5 +} diff --git a/rtic/examples/common.rs b/rtic/examples/common.rs new file mode 100644 index 0000000000..f07b69c54d --- /dev/null +++ b/rtic/examples/common.rs @@ -0,0 +1,86 @@ +//! examples/common.rs +#![feature(type_alias_impl_trait)] +#![deny(unsafe_code)] +#![deny(missing_docs)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local { + local_to_foo: i64, + local_to_bar: i64, + local_to_idle: i64, + } + + // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + ( + Shared {}, + // initial values for the `#[local]` resources + Local { + local_to_foo: 0, + local_to_bar: 0, + local_to_idle: 0, + }, + ) + } + + // `local_to_idle` can only be accessed from this context + #[idle(local = [local_to_idle])] + fn idle(cx: idle::Context) -> ! { + let local_to_idle = cx.local.local_to_idle; + *local_to_idle += 1; + + hprintln!("idle: local_to_idle = {}", local_to_idle); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + // error: no `local_to_foo` field in `idle::LocalResources` + // _cx.local.local_to_foo += 1; + + // error: no `local_to_bar` field in `idle::LocalResources` + // _cx.local.local_to_bar += 1; + + loop { + cortex_m::asm::nop(); + } + } + + // `local_to_foo` can only be accessed from this context + #[task(local = [local_to_foo])] + async fn foo(cx: foo::Context) { + let local_to_foo = cx.local.local_to_foo; + *local_to_foo += 1; + + // error: no `local_to_bar` field in `foo::LocalResources` + // cx.local.local_to_bar += 1; + + hprintln!("foo: local_to_foo = {}", local_to_foo); + } + + // `local_to_bar` can only be accessed from this context + #[task(local = [local_to_bar])] + async fn bar(cx: bar::Context) { + let local_to_bar = cx.local.local_to_bar; + *local_to_bar += 1; + + // error: no `local_to_foo` field in `bar::LocalResources` + // cx.local.local_to_foo += 1; + + hprintln!("bar: local_to_bar = {}", local_to_bar); + } +} diff --git a/rtic/examples/lock-free.rs b/rtic/examples/lock-free.rs new file mode 100644 index 0000000000..3e0960408e --- /dev/null +++ b/rtic/examples/lock-free.rs @@ -0,0 +1,50 @@ +//! examples/lock-free.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + #[lock_free] // <- lock-free shared resource + counter: u64, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + + (Shared { counter: 0 }, Local {}) + } + + #[task(binds = UART0, shared = [counter])] // <- same priority + fn foo(c: foo::Context) { + rtic::pend(Interrupt::UART1); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" foo = {}", counter); + } + + #[task(binds = UART1, shared = [counter])] // <- same priority + fn bar(c: bar::Context) { + rtic::pend(Interrupt::UART0); + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" bar = {}", counter); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/message_passing.rs b/rtic/examples/spawn_arguments.rs similarity index 100% rename from rtic/examples/message_passing.rs rename to rtic/examples/spawn_arguments.rs diff --git a/rtic/examples/spawn_err.rs b/rtic/examples/spawn_err.rs new file mode 100644 index 0000000000..ee9904ae19 --- /dev/null +++ b/rtic/examples/spawn_err.rs @@ -0,0 +1,40 @@ +//! examples/spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + foo::spawn().unwrap(); + match foo::spawn() { + Ok(_) => {} + Err(()) => hprintln!("Cannot spawn a spawned (running) task!"), + } + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/spawn_loop.rs b/rtic/examples/spawn_loop.rs new file mode 100644 index 0000000000..c1ad04e36c --- /dev/null +++ b/rtic/examples/spawn_loop.rs @@ -0,0 +1,42 @@ +//! examples/spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + (Shared {}, Local {}) + } + #[idle] + fn idle(_: idle::Context) -> ! { + for _ in 0..3 { + foo::spawn().unwrap(); + hprintln!("idle"); + } + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + } +} diff --git a/rtic/examples/zero-prio-task_err.no_rs b/rtic/examples/zero-prio-task_err.no_rs new file mode 100644 index 0000000000..ab67d82e3f --- /dev/null +++ b/rtic/examples/zero-prio-task_err.no_rs @@ -0,0 +1,66 @@ +//! examples/zero-prio-task.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Does not impl send +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + x: NotSend, + } + + #[local] + struct Local { + y: NotSend, + } + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task2::spawn().unwrap(); + + ( + Shared { + x: NotSend { _0: PhantomData }, + }, + Local { + y: NotSend { _0: PhantomData }, + }, + ) + } + + #[task(priority = 0, shared = [x], local = [y])] + async fn async_task(_: async_task::Context) { + hprintln!("hello from async"); + } + + #[task(priority = 0, shared = [x])] + async fn async_task2(_: async_task2::Context) { + hprintln!("hello from async2"); + } + + #[idle(shared = [x])] + fn idle(_: idle::Context) -> ! { + hprintln!("hello from idle"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } +}