mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-25 03:19:34 +01:00
Merge #686
686: Book: Editorial review r=korken89 a=AfoHT Continuation of https://github.com/rtic-rs/cortex-m-rtic/pull/618 Better late than never... A big thanks to `@jvanderk` ! Co-authored-by: John van der Koijk <33966414+jvanderk@users.noreply.github.com>
This commit is contained in:
commit
a5e18cd529
13 changed files with 81 additions and 53 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue