mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-11-23 20:22:51 +01:00
Docs: By-example
This commit is contained in:
parent
37facfb5bf
commit
4357d8be15
8 changed files with 90 additions and 61 deletions
|
@ -3,15 +3,15 @@
|
||||||
This part of the book introduces the Real-Time Interrupt-driven Concurrency (RTIC) framework
|
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.
|
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
|
All examples in this part of the book are accessible at the
|
||||||
the project. The examples can be run on QEMU (emulating a Cortex M3 target) so no special hardware
|
[GitHub repository][repoexamples].
|
||||||
is required to follow along.
|
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`
|
To run the examples with QEMU you will need the `qemu-system-arm` program.
|
||||||
program. Check [the embedded Rust book] for instructions on how to set up an
|
Check [the embedded Rust book] for instructions on how to set up an
|
||||||
embedded development environment that includes QEMU.
|
embedded development environment that includes QEMU.
|
||||||
|
|
||||||
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html
|
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
## Requirements on the `app` attribute
|
## Requirements on the `app` attribute
|
||||||
|
|
||||||
All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute
|
All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute
|
||||||
must be applied to a `mod`-item containing the RTIC application. The `app`
|
only applies to a `mod`-item containing the RTIC application. The `app`
|
||||||
attribute has a mandatory `device`
|
attribute has a mandatory `device` argument that takes a *path* as a value.
|
||||||
argument that takes a *path* as a value. This must be a full path pointing to a
|
This must be a full path pointing to a
|
||||||
*peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or
|
*peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or
|
||||||
newer.
|
newer.
|
||||||
|
|
||||||
The `app` attribute will expand into a suitable entry point so it's not required
|
The `app` attribute will expand into a suitable entry point and thus replaces
|
||||||
to use the [`cortex_m_rt::entry`] attribute.
|
the use of the [`cortex_m_rt::entry`] attribute.
|
||||||
|
|
||||||
[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
|
[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
|
||||||
[`svd2rust`]: https://crates.io/crates/svd2rust
|
[`svd2rust`]: https://crates.io/crates/svd2rust
|
||||||
|
@ -18,9 +18,9 @@ to use the [`cortex_m_rt::entry`] attribute.
|
||||||
|
|
||||||
## An RTIC application example
|
## 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
|
``` rust
|
||||||
{{#include ../../../../examples/common.rs}}
|
{{#include ../../../../examples/common.rs}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
# The background task `#[idle]`
|
# The background task `#[idle]`
|
||||||
|
|
||||||
A function marked with the `idle` attribute can optionally appear in the
|
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
|
module. This becomes the special *idle task* and must have signature
|
||||||
signature `fn(idle::Context) -> !`.
|
`fn(idle::Context) -> !`.
|
||||||
|
|
||||||
When present, the runtime will execute the `idle` task after `init`. Unlike
|
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
|
`init`, `idle` will run *with interrupts enabled* and must never return,
|
||||||
so it must run forever.
|
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`.
|
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}}
|
{{#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`.
|
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.
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
# App initialization and the `#[init]` task
|
# 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
|
## 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
|
``` rust
|
||||||
{{#include ../../../../examples/init.rs}}
|
{{#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.
|
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
|
$ cargo run --target thumbv7m-none-eabi --example init
|
||||||
{{#include ../../../../ci/expected/init.run}}
|
{{#include ../../../../ci/expected/init.run}}
|
||||||
```
|
```
|
||||||
|
|
||||||
> **NOTE**: You can choose target device by passing a target
|
> **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`.
|
> 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`.
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
# Defining tasks with `#[task]`
|
# 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.
|
In the coming pages we will explore both tasks and the different options available.
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
# Hardware tasks
|
# Hardware tasks
|
||||||
|
|
||||||
At its core RTIC is based on using the interrupt controller in the hardware to do scheduling and
|
At its core RTIC is using the hardware interrupt controller ([ARM NVIC on cortex-m][NVIC])
|
||||||
run tasks, as all tasks in the framework are run as interrupt handlers (except `#[init]` and
|
to perform scheduling and executing tasks, and all tasks except `#[init]` and `#[idle]`
|
||||||
`#[idle]`). This also means that you can directly bind tasks to interrupt handlers.
|
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
|
To bind an interrupt use the `#[task]` attribute argument `binds = InterruptName`.
|
||||||
value is the name of the interrupt to which the handler will be bound to; the
|
This task becomes the interrupt handler for this hardware interrupt vector.
|
||||||
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.
|
|
||||||
|
|
||||||
Providing an interrupt name that does not exist will cause a compile error to help with accidental
|
All tasks bound to an explicit interrupt are *hardware tasks* since they
|
||||||
errors.
|
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
|
The example below demonstrates the use of the `#[task]` attribute to declare an
|
||||||
interrupt handler.
|
interrupt handler.
|
||||||
|
@ -24,4 +28,3 @@ interrupt handler.
|
||||||
$ cargo run --target thumbv7m-none-eabi --example hardware
|
$ cargo run --target thumbv7m-none-eabi --example hardware
|
||||||
{{#include ../../../../ci/expected/hardware.run}}
|
{{#include ../../../../ci/expected/hardware.run}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
# Resource usage
|
# Resource usage
|
||||||
|
|
||||||
The RTIC framework manages shared and task local resources which allows data to be persistently
|
The RTIC framework manages shared and task local resources allowing persistent data
|
||||||
stored and safely accessed without the use of unsafe code.
|
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
|
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.
|
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
|
Declaration of system-wide resources are by annotating **two** `struct`s within the `#[app]` module
|
||||||
the attribute `#[local]` and `#[shared]` respectively. Each field in these structures corresponds
|
with the attribute `#[local]` and `#[shared]`.
|
||||||
to a different resource (identified by field name). The difference between these two sets of
|
Each field in these structures corresponds to a different resource (identified by field name).
|
||||||
resources will be covered below.
|
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
|
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
|
using the `local` and `shared` arguments. Each argument takes a list of resource identifiers.
|
||||||
listed resources are made available to the context under the `local` and `shared` fields of the
|
The listed resources are made available to the context under the `local` and `shared` fields of the
|
||||||
`Context` structure.
|
`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
|
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).
|
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
|
commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific
|
||||||
task.
|
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
|
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.
|
`#[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}}
|
{{#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
|
### Task local initialized resources
|
||||||
|
|
||||||
A special use-case of local resources are the ones specified directly in the resource claim,
|
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
|
`#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`, this allows for creating locals which do no need to be
|
||||||
initialized in `#[init]`.
|
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:
|
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
|
## Multi-lock
|
||||||
|
|
||||||
As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The
|
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
|
``` rust
|
||||||
{{#include ../../../../examples/multilock.rs}}
|
{{#include ../../../../examples/multilock.rs}}
|
||||||
|
@ -109,12 +109,12 @@ $ cargo run --target thumbv7m-none-eabi --example multilock
|
||||||
|
|
||||||
## Only shared (`&-`) access
|
## Only shared (`&-`) access
|
||||||
|
|
||||||
By default the framework assumes that all tasks require exclusive access (`&mut-`) to resources but
|
By default, the framework assumes that all tasks require exclusive access (`&mut-`) to resources,
|
||||||
it is possible to specify that a task only requires shared access (`&-`) to a resource using the
|
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.
|
`&resource_name` syntax in the `shared` list.
|
||||||
|
|
||||||
The advantage of specifying shared access (`&-`) to a resource is that no locks are required to
|
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,
|
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
|
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
|
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
|
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
|
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
|
`#[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
|
this is merely a convenience to reduce needless resource locking code, because even if the
|
||||||
**not** produce a critical section. Also worth noting: using `#[lock_free]` on resources shared by
|
`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`
|
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.
|
API would be a data race in that case.
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
# Preface
|
# Preface
|
||||||
|
|
||||||
This book contains user level documentation for the Real-Time Interrupt-driven Concurrency
|
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.
|
Formerly known as Real-Time For the Masses.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue