Mostly editorial review.

This commit is contained in:
John van der Koijk 2022-02-20 19:21:25 +01:00 committed by Henrik Tjäder
parent 3240fb332a
commit 04189cc684
13 changed files with 81 additions and 53 deletions

View file

@ -15,6 +15,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
### Fixed
- Attempt to handle docs generation enabling `deny(missing_docs)`
- Book: Editorial review
- Use native GHA rustup and cargo
- Distinguish between thumbv8m.base and thumbv8m.main for basepri usage.

View file

@ -1,13 +1,18 @@
# 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 the resource
signature `fn(init::Context) -> (Shared, Local, init::Monotonics)`, where `Shared` and `Local` are resource
structures defined by the user.
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`.
The `init` task executes after system reset, [after an optionally defined `pre-init` code section][pre-init] and an always occurring internal RTIC
initialization.
[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
@ -15,7 +20,7 @@ The example below shows the types of the `core`, `device` and `cs` fields, and s
variable with `'static` lifetime.
Such variables can be delegated from the `init` task to other tasks of the RTIC application.
The `device` field is available when the `peripherals` argument is set to the default value `true`.
The `device` field is only 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

View file

@ -18,8 +18,8 @@ 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 pends
task A. Task A has higher priority thus preempting task B which gets suspended
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.
```text
@ -53,7 +53,8 @@ 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 language,
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

View file

@ -4,15 +4,18 @@ Tasks, defined with `#[task]`, are the main mechanism of getting work done in RT
Tasks can
* Be spawned (now or in the future)
* Receive messages (message passing)
* Prioritized allowing preemptive multitasking
* Be spawned (now or in the future, also by themselves)
* Receive messages (passing messages between tasks)
* Be 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.
*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.
In the coming pages we will explore both tasks and the different options available.

View file

@ -1,24 +1,26 @@
# Hardware tasks
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]`
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.
This also means that you can manually bind tasks 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.
Hardware tasks are explicitly bound to interrupt handlers.
All tasks bound to an explicit interrupt are *hardware tasks* since they
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 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.
Any available interrupt vector should work, but different hardware might have
added special properties to select interrupt priority levels, such as the
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 re-purposing interrupt vectors used internally by hardware features,
Beware of using interrupt vectors that are used internally by hardware features;
RTIC is unaware of such hardware specific details.
[pacorhal]: https://docs.rust-embedded.org/book/start/registers.html

View file

@ -1,7 +1,7 @@
# Monotonic & spawn_{at/after}
The understanding of time is an important concept in embedded systems, and to be able to run tasks
based on time is useful. For this use-case the framework provides the static methods
based on time is essential. The framework provides the static methods
`task::spawn_after(/* duration */)` and `task::spawn_at(/* specific time instant */)`.
`spawn_after` is more commonly used, but in cases where it's needed to have spawns happen
without drift or to a fixed baseline `spawn_at` is available.
@ -43,10 +43,14 @@ $ cargo run --target thumbv7m-none-eabi --example schedule
{{#include ../../../../ci/expected/schedule.run}}
```
A key requirement of a Monotonic is that it must deal gracefully with
hardware timer overruns.
## Canceling or rescheduling a scheduled task
Tasks spawned using `task::spawn_after` and `task::spawn_at` returns a `SpawnHandle`,
which allows canceling or rescheduling of the task scheduled to run in the future.
If `cancel` or `reschedule_at`/`reschedule_after` returns an `Err` it means that the operation was
too late and that the task is already sent for execution. The following example shows this in action:

View file

@ -30,13 +30,13 @@ 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.
Types of `#[local]` resources must implement [`Send`] trait as they are being sent from `init`
to target task and thus crossing the thread boundary.
Types of `#[local]` resources must implement a [`Send`] trait as they are being sent from `init`
to a target task, crossing a 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, plus that the `idle` task has its own `#[local]` as well.
`#[local]` resource; the `idle` task has its own `#[local]` as well.
``` rust
{{#include ../../../../examples/locals.rs}}
@ -49,12 +49,14 @@ $ cargo run --target thumbv7m-none-eabi --example locals
{{#include ../../../../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
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
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]`.
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.
@ -92,9 +94,9 @@ preempting the critical section. This synchronization protocol is known as the
[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 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.
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.
``` rust
{{#include ../../../../examples/lock.rs}}

View file

@ -2,29 +2,33 @@
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 a “dispatcher” interrupt vector running
at the same priority as the software task.
interrupt vector, but rather bound to a “dispatcher” interrupt vector running
at the intended priority of the software task (see below).
Thus, software tasks are tasks which are not directly assigned to a specific interrupt vector.
Thus, software tasks are tasks which are not *directly* bound to an interrupt vector.
The `#[task]` attribute used on a function declare it as a software tasks.
Observe the absence of a `binds = InterruptName` argument to the attribute.
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 `#[task]` attributes used on a function determine if it is
software tasks, specifically the absence of a `binds = InterruptName`
argument to the attribute definition.
All software tasks at the same priority level shares an interrupt handler acting as a dispatcher.
What differentiates software and hardware tasks are the dispatcher versus bound interrupt vector.
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 interrupt vectors used as dispatchers can not be used by hardware 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.
A list of “free” (not in use by hardware tasks) and usable interrupts allows the framework
to dispatch software tasks.
The interrupt vectors used as dispatchers cannot be used by hardware tasks.
This list of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an
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 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.

View file

@ -8,7 +8,7 @@ If you are targeting ARMv6-M or ARMv8-M-base architecture, check out the section
[`defmt-app-template`]: https://github.com/rtic-rs/defmt-app-template
This will give you an RTIC application with support for RTT logging with [`defmt`] and stack overflow
protection using [`flip-link`]. There are also a multitude of examples available provided by the community:
protection using [`flip-link`]. There is also a multitude of examples provided by the community:
- [`rtic-examples`] - Multiple projects
- [https://github.com/kalkyl/f411-rtic](https://github.com/kalkyl/f411-rtic)

View file

@ -9,12 +9,16 @@ Indirection can minimize message passing overhead:
instead of sending the buffer by value, one can send an owning pointer into the
buffer.
One can use a global allocator to achieve indirection (`alloc::Box`,
One can use a global memory allocator to achieve indirection (`alloc::Box`,
`alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.37.0,
or one can use a statically allocated memory pool like [`heapless::Pool`].
[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html
As this example of approach goes completely outside of RTIC resource
model with shared and local the program would rely on the correctness
of the memory allocator, in this case `heapless::pool`.
Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes.
``` rust

View file

@ -11,7 +11,7 @@ Moreover, the relation between time and timers used for scheduling was difficult
For RTIC 1.0 we instead assume the user has a time library, e.g. [`fugit`] or [`embedded_time`],
as the basis for all time-based operations when implementing `Monotonic`.
This makes it much easier to correctly implement the `Monotonic` trait allowing the use of
These libraries make it much easier to correctly implement the `Monotonic` trait, allowing the use of
almost any timer in the system for scheduling.
The trait documents the requirements for each method,

View file

@ -1,6 +1,6 @@
# 'static super-powers
In `#[init]` and `#[idle]` `local` resources has `'static` lifetime.
In `#[init]` and `#[idle]` `local` resources have `'static` lifetime.
Useful when pre-allocating and/or splitting resources between tasks, drivers
or some other object.

View file

@ -368,3 +368,5 @@ Both software and hardware tasks can now be defined external to the `mod app`.
Previously this was possible only by implementing a trampoline calling out the task implementation.
See examples `examples/extern_binds.rs` and `examples/extern_spawn.rs`.
This enables breaking apps into multiple files.