Docs: By-example

This commit is contained in:
Henrik Tjäder 2021-12-14 22:46:15 +01:00
parent 37facfb5bf
commit 4357d8be15
8 changed files with 90 additions and 61 deletions

View file

@ -3,15 +3,15 @@
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.
All examples in this part of the book can be found in the GitHub [repository] of
the project. The examples can be run on QEMU (emulating a Cortex M3 target) so no special hardware
is required to follow along.
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.
[repository]: https://github.com/rtic-rs/cortex-m-rtic
[repoexamples]: https://github.com/rtic-rs/cortex-m-rtic/tree/master/examples
To run the examples on your computer you'll need the `qemu-system-arm`
program. Check [the embedded Rust book] for instructions on how to set up an
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
embedded development environment that includes QEMU.
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html

View file

@ -3,14 +3,14 @@
## Requirements on the `app` attribute
All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute
must be applied 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
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 so it's not required
to use 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
@ -18,9 +18,9 @@ to use the [`cortex_m_rt::entry`] attribute.
## An RTIC application example
To give a flavor of RTIC, the following example contains commonly used features. In the following sections we will go through each feature in detail.
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}}
```

View file

@ -1,14 +1,18 @@
# The background task `#[idle]`
A function marked with the `idle` attribute can optionally appear in the
module. This function is used as the special *idle task* and must have
signature `fn(idle::Context) -> !`.
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 it's not allowed to return
so it must run forever.
`init`, `idle` will run *with interrupts enabled* and must never return,
as the `-> !` function signature indicates.
[The Rust type `!` means “never”][nevertype].
Like in `init`, locally declared resources will have `'static` lifetimes that are safe to access.
[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.
The example below shows that `idle` runs after `init`.
@ -21,9 +25,9 @@ $ cargo run --target thumbv7m-none-eabi --example idle
{{#include ../../../../ci/expected/idle.run}}
```
By default the RTIC `idle` task does not try to optimise for any specific targets.
By default, the RTIC `idle` task does not try to optimize for any specific targets.
A common useful optimisation is to enable the [SLEEPONEXIT] and allow the MCU
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.

View file

@ -1,14 +1,22 @@
# App initialization and the `#[init]` task
An RTIC application is required an `init` task setting up the system. The corresponding function must have the signature `fn(init::Context) -> (Shared, Local, init::Monotonics)`, where `Shared` and `Local` are the resource structures defined by the user.
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 the resource
structures defined by the user.
On system reset, the `init` task is executed (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`.
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`.
## 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. As we will see later, such variables can later be delegated from `init` 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 `true` (which is the default). In the rare case you want to implement an ultra-slim application you can explicitly set `peripherals` to `false`.
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}}
@ -16,13 +24,13 @@ The `device` field is only available when the `peripherals` argument is set to `
Running the example will print `init` to the console and then exit the QEMU process.
``` console
``` console
$ cargo run --target thumbv7m-none-eabi --example init
{{#include ../../../../ci/expected/init.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
> 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`.
> For running the examples, we use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`.

View file

@ -1,7 +1,18 @@
# Defining tasks with `#[task]`
Tasks, defined with `#[task]`, are the main mechanism of getting work done in RTIC. Every task can be spawned, now or later, be sent messages (message passing) and be given priorities for preemptive multitasking.
Tasks, defined with `#[task]`, are the main mechanism of getting work done in RTIC.
There are two kinds of tasks, software tasks and hardware tasks, and the difference is that hardware tasks 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 the UART's RX interrupt the task will run every time a character is received.
Tasks can
* 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”.
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 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.

View file

@ -1,17 +1,21 @@
# Hardware tasks
At its core RTIC is based on using the interrupt controller in the hardware to do scheduling and
run tasks, as all tasks in the framework are run as interrupt handlers (except `#[init]` and
`#[idle]`). This also means that you can directly bind tasks to 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 tasks except `#[init]` and `#[idle]`
run as interrupt handlers.
This also means that you can manually bind tasks to interrupt handlers.
To declare interrupt handlers the `#[task]` attribute takes a `binds = InterruptName` argument whose
value is the name of the interrupt to which the handler will be bound to; the
function used with this attribute becomes the interrupt handler. Within the
framework these type of tasks are referred to as *hardware* tasks, because they
start executing in reaction to a hardware event.
To bind an interrupt use the `#[task]` attribute argument `binds = InterruptName`.
This task becomes the interrupt handler for this hardware interrupt vector.
Providing an interrupt name that does not exist will cause a compile error to help with accidental
errors.
All tasks bound to an explicit interrupt are *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.
[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]` attribute to declare an
interrupt handler.
@ -24,4 +28,3 @@ interrupt handler.
$ cargo run --target thumbv7m-none-eabi --example hardware
{{#include ../../../../ci/expected/hardware.run}}
```

View file

@ -1,22 +1,22 @@
# Resource usage
The RTIC framework manages shared and task local resources which allows data to be persistently
stored and safely accessed 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.
System wide resources are declared as **two** `struct`'s within the `#[app]` module annotated with
the attribute `#[local]` and `#[shared]` respectively. 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 are 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
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]`)
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).
@ -27,6 +27,9 @@ access the resource and does so without locks or critical sections. This allows
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 singular task.
Attempting to assign the same `#[local]` resource to more than one task is a compile-time error.
The example application shown below contains two tasks where each task has access to its own
`#[local]` resource, plus that the `idle` task has its own `#[local]` as well.
@ -39,15 +42,12 @@ $ cargo run --target thumbv7m-none-eabi --example locals
{{#include ../../../../ci/expected/locals.run}}
```
A `#[local]` resource cannot be accessed from outside the task it was associated to in a `#[task]` attribute.
Assigning the same `#[local]` resource to more than one task is a compile-time error.
### Task local initialized resources
A special use-case of local resources are the ones specified directly in the resource claim,
`#[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.
Moreover, local resources in `#[init]` and `#[idle]` have `'static` lifetimes, this is safe since both are not re-entrant.
In the example below the different uses and lifetimes are shown:
@ -96,7 +96,7 @@ $ cargo run --target thumbv7m-none-eabi --example lock
## Multi-lock
As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The
following examples shows this in use:
following examples show this in use:
``` rust
{{#include ../../../../examples/multilock.rs}}
@ -109,12 +109,12 @@ $ cargo run --target thumbv7m-none-eabi --example multilock
## 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
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.
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 several tasks running at different
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
@ -142,8 +142,11 @@ $ cargo run --target thumbv7m-none-eabi --example only-shared-access
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: if you do use the `lock` API, at runtime the framework will
**not** produce a critical section. Also worth noting: using `#[lock_free]` on resources shared by
this is merely a convenience to reduce needless resource locking code, because even if the
`lock` API is used, at runtime the framework will **not** produce a critical section due to how
the underlying resource-ceiling preemption works.
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.

View file

@ -8,7 +8,7 @@
# Preface
This book contains user level documentation for the Real-Time Interrupt-driven Concurrency
(RTIC) framework. The API reference can be found [here](../../api/).
(RTIC) framework. The API reference available [here](../../api/).
Formerly known as Real-Time For the Masses.