mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-11-25 21:19:35 +01:00
Merge #563
563: Docs touchup r=korken89 a=AfoHT Unleashed some language linters on the book Co-authored-by: Henrik Tjäder <henrik@grepit.se> Co-authored-by: perlindgren <per.lindgren@ltu.se>
This commit is contained in:
commit
c78177c37e
23 changed files with 291 additions and 185 deletions
|
@ -1 +1,8 @@
|
|||
# Awesome RTIC examples
|
||||
|
||||
See the [`rtic-rs/rtic-examples`][rticexamples] repository for community
|
||||
provided complete examples.
|
||||
|
||||
Pull-requests to this repo are welcome!
|
||||
|
||||
[rticexamples]: https://github.com/rtic-rs/rtic-examples
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}}
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -2,26 +2,41 @@
|
|||
|
||||
## Priorities
|
||||
|
||||
The static priority of each handler can be declared in the `task` attribute
|
||||
using the `priority` argument. 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. When the `priority` argument is omitted, the priority is assumed to be
|
||||
`1`. The `idle` task has a non-configurable static priority of `0`, the lowest priority.
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
When several tasks are ready to be executed the one with highest static
|
||||
priority will be executed first. Task prioritization can be observed in the
|
||||
following scenario: during the execution of a low
|
||||
priority task a higher priority task is spawned; this puts the higher priority task in the pending state.
|
||||
The difference in priority results in the higher priority task preempting the
|
||||
lower priority one: the execution of the lower priority task is suspended and
|
||||
the higher priority task is executed to completion. Once the higher priority
|
||||
task has terminated the lower priority task is resumed.
|
||||
The highest static priority task takes precedence when more than one
|
||||
task are ready to execute.
|
||||
|
||||
The following example showcases the priority based scheduling of tasks.
|
||||
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
|
||||
until task A completes execution. Thus, when task A completes task B resumes execution.
|
||||
|
||||
```text
|
||||
Task Priority
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ │
|
||||
3 │ Preempts │
|
||||
2 │ A─────────► │
|
||||
1 │ B─────────► - - - - B────────► │
|
||||
0 │Idle┌─────► Resumes ┌──────────► │
|
||||
├────┴──────────────────────────────────┴────────────────┤
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────┘Time
|
||||
```
|
||||
|
||||
The following example showcases the priority based scheduling of tasks:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/preempt.rs}}
|
||||
|
@ -33,13 +48,24 @@ $ cargo run --target thumbv7m-none-eabi --example preempt
|
|||
```
|
||||
|
||||
Note that the task `bar` does *not* preempt task `baz` because its priority
|
||||
is the *same* as `baz`'s. However, once `baz` returns, the execution of
|
||||
task `bar` is prioritized over `foo` due to its higher priority. `foo`
|
||||
is resumed only after `bar` returns.
|
||||
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 compile error. Due to
|
||||
limitations in the language, the error message is currently far from helpful: it
|
||||
will say something along the lines of "evaluation of constant value failed" and
|
||||
the span of the error will *not* point out to the problematic interrupt value --
|
||||
we are sorry about this!
|
||||
supports will result in a compilation error.
|
||||
The error is cryptic due to limitations in the language,
|
||||
if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like:
|
||||
|
||||
```text
|
||||
error[E0080]: evaluation of constant value failed
|
||||
--> examples/common.rs:10:1
|
||||
|
|
||||
10 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `8_usize - 9_usize`, which would overflow
|
||||
|
|
||||
= note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
# Message passing & capacity
|
||||
|
||||
Software tasks have support for message passing, this means that they can be spawned with an argument
|
||||
as `foo::spawn(1)` which will run the task `foo` with the argument `1`. The number of arguments is not
|
||||
limited and is exemplified in the following:
|
||||
Software tasks support message passing, this means that software tasks can be spawned
|
||||
with an argument: `foo::spawn(1)` which will run the task `foo` with the argument `1`.
|
||||
|
||||
Capacity sets the size of the spawn queue for the task, if not specified capacity defaults to 1.
|
||||
|
||||
In the example below, the capacity of task `foo` is `3`, allowing three simultaneous
|
||||
pending spawns of `foo`. Exceeding this capacity is an `Error`.
|
||||
|
||||
The number of arguments to a task is not limited:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/message_passing.rs}}
|
||||
|
|
|
@ -1,33 +1,38 @@
|
|||
# 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 very useful. For this use-case the framework provides the static methods
|
||||
based on time is useful. For this use-case the framework provides the static methods
|
||||
`task::spawn_after(/* duration */)` and `task::spawn_at(/* specific time instant */)`.
|
||||
Mostly one uses `spawn_after`, but in cases where it's needed to have spawns happen without drift or
|
||||
to a fixed baseline `spawn_at` is available.
|
||||
`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.
|
||||
|
||||
To support this the `#[monotonic]` attribute exists which is applied to a type alias definition.
|
||||
The `#[monotonic]` attribute, applied to a type alias definition, exists to support this.
|
||||
This type alias must point to a type which implements the [`rtic_monotonic::Monotonic`] trait.
|
||||
This is generally some timer which handles the timing of the system. One or more monotonics can be
|
||||
used in the same system, for example a slow timer that is used to wake the system from sleep and another
|
||||
that is used for high granularity scheduling while the system is awake.
|
||||
This is generally some timer which handles the timing of the system.
|
||||
One or more monotonics can coexist in the same system, for example a slow timer that wakes the
|
||||
system from sleep and another which purpose is for fine grained scheduling while the
|
||||
system is awake.
|
||||
|
||||
[`rtic_monotonic::Monotonic`]: https://docs.rs/rtic-monotonic
|
||||
|
||||
The attribute has one required parameter and two optional parameters, `binds`, `default` and
|
||||
`priority` respectively. `binds = InterruptName` defines which interrupt vector is associated to
|
||||
the timer's interrupt, `default = true` enables a shorthand API when spawning and accessing the
|
||||
time (`monotonics::now()` vs `monotonics::MyMono::now()`), and `priority` sets the priority the
|
||||
interrupt vector has.
|
||||
`priority` respectively.
|
||||
The required parameter, `binds = InterruptName`, associates an interrupt vector to the timer's
|
||||
interrupt, while `default = true` enables a shorthand API when spawning and accessing
|
||||
time (`monotonics::now()` vs `monotonics::MyMono::now()`), and `priority` sets the priority
|
||||
of the interrupt vector.
|
||||
|
||||
> By default `priority` is set to the **maximum priority** of the system but a lower priority
|
||||
> can be selected if a high priority task cannot take the jitter introduced by the scheduling.
|
||||
> This can however introduce jitter and delays into the scheduling, making it a trade-off.
|
||||
> The default `priority` is the **maximum priority** of the system.
|
||||
> If your system has a high priority task with tight scheduling requirements,
|
||||
> it might be desirable to demote the `monotonic` task to a lower priority
|
||||
> to reduce scheduling jitter for the high priority task.
|
||||
> This however might introduce jitter and delays into scheduling via the `monotonic`,
|
||||
> making it a trade-off.
|
||||
|
||||
Finally, the monotonics must be initialized in `#[init]` and returned in the `init::Monotonic( ... )` tuple.
|
||||
This moves the monotonics into the active state which makes it possible to use them.
|
||||
The monotonics are initialized in `#[init]` and returned within the `init::Monotonic( ... )` tuple.
|
||||
This activates the monotonics making it possible to use them.
|
||||
|
||||
An example is provided below:
|
||||
See the following example:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/schedule.rs}}
|
||||
|
@ -40,8 +45,8 @@ $ cargo run --target thumbv7m-none-eabi --example message
|
|||
|
||||
## Canceling or rescheduling a scheduled task
|
||||
|
||||
Tasks spawned using `task::spawn_after` and `task::spawn_at` has as returns a `SpawnHandle`,
|
||||
where the `SpawnHandle` can be used to cancel or reschedule a task that will run in the future.
|
||||
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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -1,21 +1,31 @@
|
|||
# Software tasks & spawn
|
||||
|
||||
Software tasks, as hardware tasks, are run as interrupt handlers where all software tasks at the
|
||||
same priority shares a "free" interrupt handler to run from, called a dispatcher. These free
|
||||
interrupts are interrupt vectors not used by hardware tasks.
|
||||
Software tasks are tasks which are not directly assigned to a specific interrupt vector.
|
||||
|
||||
To declare tasks in the framework the `#[task]` attribute is used on a function.
|
||||
By default these tasks are referred to as software tasks as they do not have a direct coupling to
|
||||
an interrupt handler. Software tasks can be spawned (started) using the `task_name::spawn()` static
|
||||
method which will directly run the task given that there are no higher priority tasks running.
|
||||
They run as interrupt handlers where all software tasks at the
|
||||
same priority level shares a "free" interrupt handler acting as a dispatcher.
|
||||
Thus, what differentiates software and hardware tasks are the dispatcher versus
|
||||
bound interrupt vector.
|
||||
|
||||
To indicate to the framework which interrupts are free for use to dispatch software tasks with the
|
||||
`#[app]` attribute has a `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` argument. You need
|
||||
to provide as many dispatchers as there are priority levels used by software tasks, as an
|
||||
dispatcher is assigned per interrupt level. The framework will also give a compile error if there
|
||||
are not enough dispatchers provided.
|
||||
These free interrupts used as dispatchers are interrupt vectors not used by hardware tasks.
|
||||
|
||||
This is exemplified in the following:
|
||||
The `#[task]` attribute used on a function declare it as a software tasks.
|
||||
The static method `task_name::spawn()` spawn (start) a software task and
|
||||
given that there are no higher priority tasks running the task will start executing directly.
|
||||
|
||||
A list of “free” and usable interrupts allows the framework to dispatch software tasks.
|
||||
This list 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.
|
||||
|
||||
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.
|
||||
|
||||
See the following example:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/spawn.rs}}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
# Starting a new project
|
||||
|
||||
When starting an RTIC project from scratch it is recommended to follow RTIC's [`defmt-app-template`].
|
||||
A recommendation when starting a RTIC project from scratch is to follow RTIC's [`defmt-app-template`].
|
||||
|
||||
[`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 an multitude of examples available provided by the community:
|
||||
protection using [`flip-link`]. There are also a multitude of examples available provided by the community:
|
||||
|
||||
- [`rtic-examples`] - Multiple projects
|
||||
- [https://github.com/kalkyl/f411-rtic](https://github.com/kalkyl/f411-rtic)
|
||||
- ... More to come
|
||||
|
||||
[`defmt`]: https://github.com/knurling-rs/defmt/
|
||||
[`flip-link`]: https://github.com/knurling-rs/flip-link/
|
||||
[`rtic-examples`]: https://github.com/rtic-rs/rtic-examples
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Resource de-structure-ing
|
||||
|
||||
When having a task taking multiple resources it can help in readability to split
|
||||
up the resource struct. Here are two examples on how this can be done:
|
||||
Destructuring task resources might help readability if a task takes multiple
|
||||
resources.
|
||||
Here are two examples on how to split up the resource struct:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/destructure.rs}}
|
||||
|
|
|
@ -6,7 +6,7 @@ RTIC v0.4.0 was to allow inter-operation with other attributes. For example, the
|
|||
improve performance in some cases.
|
||||
|
||||
> **IMPORTANT**: In general, the `link_section`, `export_name` and `no_mangle`
|
||||
> attributes are very powerful but also easy to misuse. Incorrectly using any of
|
||||
> attributes are powerful but also easy to misuse. Incorrectly using any of
|
||||
> these attributes can cause undefined behavior; you should always prefer to use
|
||||
> safe, higher level attributes around them like `cortex-m-rt`'s `interrupt` and
|
||||
> `exception` attributes.
|
||||
|
@ -42,4 +42,3 @@ $ cargo nm --example ramfunc --release | grep ' foo::'
|
|||
$ cargo nm --example ramfunc --release | grep ' bar::'
|
||||
{{#include ../../../../ci/expected/ramfunc.grep.bar}}
|
||||
```
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
Message passing always involves copying the payload from the sender into a
|
||||
static variable and then from the static variable into the receiver. Thus
|
||||
sending a large buffer, like a `[u8; 128]`, as a message involves two expensive
|
||||
`memcpy`s. To minimize the message passing overhead one can use indirection:
|
||||
`memcpy`s.
|
||||
|
||||
Indirection can minimize message passing overhead:
|
||||
instead of sending the buffer by value, one can send an owning pointer into the
|
||||
buffer.
|
||||
|
||||
|
@ -23,4 +25,3 @@ Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes.
|
|||
$ cargo run --target thumbv7m-none-eabi --example pool
|
||||
{{#include ../../../../ci/expected/pool.run}}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
# Implementing a `Monotonic` timer for scheduling
|
||||
|
||||
The framework is very flexible in that it can utilize any timer which has compare-match and (optional)
|
||||
overflow interrupts for scheduling. The only thing needed to make a timer usable with RTIC is to
|
||||
implement the [`rtic_monotonic::Monotonic`] trait.
|
||||
The framework is flexible because it can use any timer which has compare-match and optionally
|
||||
supporting overflow interrupts for scheduling.
|
||||
The single requirement to make a timer usable with RTIC is implementing the
|
||||
[`rtic_monotonic::Monotonic`] trait.
|
||||
|
||||
Implementing time that supports a vast range is generally **very** difficult, and in RTIC 0.5 it was a
|
||||
common problem how to implement time handling and not get stuck in weird special cases. Moreover
|
||||
it was difficult to understand the relation between time and the timers used for scheduling. For
|
||||
RTIC 0.6 we have moved to 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 is why in RTIC 0.6
|
||||
it is almost trivial to implement the `Monotonic` trait and use any timer in a system for scheduling.
|
||||
Implementing time counting that supports large time spans is generally **difficult**, in RTIC 0.5
|
||||
implementing time handling was a common problem.
|
||||
Moreover, the relation between time and timers used for scheduling was difficult to understand.
|
||||
|
||||
The trait documents the requirements for each method, however below you can find a list of
|
||||
implementations in the wild that can be used as inspiration:
|
||||
For RTIC 0.6 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 almost trivial to implement the `Monotonic` trait allowing the use of any timer in
|
||||
the system for scheduling.
|
||||
|
||||
The trait documents the requirements for each method,
|
||||
and for inspiration here is a list of `Monotonic` implementations:
|
||||
|
||||
- [`STM32F411 series`], implemented for the 32-bit timers
|
||||
- [`Nordic nRF52 series`], implemented for the 32-bit timers
|
||||
|
@ -28,4 +31,3 @@ If you know of more implementations feel free to add them to this list.
|
|||
[`Nordic nRF52 series`]: https://github.com/kalkyl/nrf-play/blob/main/src/bin/mono.rs
|
||||
[`Systick based`]: https://github.com/rtic-rs/systick-monotonic
|
||||
[`DWT and Systick based`]: https://github.com/rtic-rs/dwt-systick-monotonic
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# 'static super-powers
|
||||
|
||||
As discussed earlier `local` resources are given `'static` lifetime in `#[init]` and `#[idle]`,
|
||||
this can be used to allocate an object and then split it up or give the pre-allocated object to a
|
||||
task, driver or some other object.
|
||||
This is very useful when needing to allocate memory for drivers, such as USB drivers, and using
|
||||
data structures that can be split such as [`heapless::spsc::Queue`].
|
||||
In `#[init]` and `#[idle]` `local` resources has `'static` lifetime.
|
||||
|
||||
In the following example an [`heapless::spsc::Queue`] is given to two different tasks for lock-free access
|
||||
to the shared queue.
|
||||
Useful when pre-allocating and/or splitting resources between tasks, drivers
|
||||
or some other object.
|
||||
This comes in handy when drivers, such as USB drivers, need to allocate memory and
|
||||
when using splittable data structures such as [`heapless::spsc::Queue`].
|
||||
|
||||
In the following example two different tasks share a [`heapless::spsc::Queue`]
|
||||
for lock-free access to the shared queue.
|
||||
|
||||
[`heapless::spsc::Queue`]: https://docs.rs/heapless/0.7.5/heapless/spsc/struct.Queue.html
|
||||
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/static.rs}}
|
||||
```
|
||||
|
|
|
@ -7,7 +7,7 @@ options:
|
|||
You can inspect the file `rtic-expansion.rs` inside the `target` directory. This
|
||||
file contains the expansion of the `#[rtic::app]` item (not your whole program!)
|
||||
of the *last built* (via `cargo build` or `cargo check`) RTIC application. The
|
||||
expanded code is not pretty printed by default so you'll want to run `rustfmt`
|
||||
expanded code is not pretty printed by default, so you'll want to run `rustfmt`
|
||||
on it before you read it.
|
||||
|
||||
``` console
|
||||
|
@ -15,7 +15,7 @@ $ cargo build --example foo
|
|||
|
||||
$ rustfmt target/rtic-expansion.rs
|
||||
|
||||
$ tail target/rtic-expansion.rs
|
||||
tail target/rtic-expansion.rs
|
||||
```
|
||||
|
||||
``` rust
|
||||
|
@ -43,6 +43,6 @@ crate and print the output to the console.
|
|||
[`cargo-expand`]: https://crates.io/crates/cargo-expand
|
||||
|
||||
``` console
|
||||
$ # produces the same output as before
|
||||
$ cargo expand --example smallest | tail
|
||||
# produces the same output as before
|
||||
cargo expand --example smallest | tail
|
||||
```
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Migration Guides
|
||||
|
||||
This section describes how to migrate between different version of RTIC.
|
||||
This section describes how to migrate between different versions of RTIC.
|
||||
It also acts as a comparing reference between versions.
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
# Migrating from v0.4.x to v0.5.0
|
||||
|
||||
This section covers how to upgrade an application written against RTIC v0.4.x to
|
||||
This section covers how to upgrade an application written against RTFM v0.4.x to
|
||||
the version v0.5.0 of the framework.
|
||||
|
||||
## Project name change RTFM -> RTIC
|
||||
|
||||
With release [v0.5.2][rtic0.5.2] the name was change to Real-Time Interrupt-driven Concurrency
|
||||
|
||||
All occurrences of `RTFM` needs to change to `RTIC`.
|
||||
|
||||
See [migration guide RTFM to RTIC](./migration_rtic.md)
|
||||
|
||||
[rtic0.5.2]: https://crates.io/crates/cortex-m-rtic/0.5.2
|
||||
|
||||
## `Cargo.toml`
|
||||
|
||||
First, the version of the `cortex-m-rtic` dependency needs to be updated to
|
||||
`"0.5.0"`. The `timer-queue` feature needs to be removed.
|
||||
Change the version of `cortex-m-rtfm` to
|
||||
`"0.5.0"`, change `rtfm` to `rtic`.
|
||||
Remove the `timer-queue` feature.
|
||||
|
||||
``` toml
|
||||
[dependencies.cortex-m-rtic]
|
||||
[dependencies.cortex-m-rtfm]
|
||||
# change this
|
||||
version = "0.4.3"
|
||||
|
||||
# into this
|
||||
[dependencies.cortex-m-rtic]
|
||||
version = "0.5.0"
|
||||
|
||||
# and remove this Cargo feature
|
||||
|
@ -23,15 +35,15 @@ features = ["timer-queue"]
|
|||
|
||||
## `Context` argument
|
||||
|
||||
All functions inside the `#[rtic::app]` item need to take as first argument a
|
||||
All functions inside the `#[rtfm::app]` item need to take as first argument a
|
||||
`Context` structure. This `Context` type will contain the variables that were
|
||||
magically injected into the scope of the function by version v0.4.x of the
|
||||
framework: `resources`, `spawn`, `schedule` -- these variables will become
|
||||
fields of the `Context` structure. Each function within the `#[rtic::app]` item
|
||||
fields of the `Context` structure. Each function within the `#[rtfm::app]` item
|
||||
gets a different `Context` type.
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
#[rtfm::app(/* .. */)]
|
||||
const APP: () = {
|
||||
// change this
|
||||
#[task(resources = [x], spawn = [a], schedule = [b])]
|
||||
|
@ -75,11 +87,11 @@ const APP: () = {
|
|||
|
||||
## Resources
|
||||
|
||||
The syntax used to declare resources has been changed from `static mut`
|
||||
The syntax used to declare resources has changed from `static mut`
|
||||
variables to a `struct Resources`.
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
#[rtfm::app(/* .. */)]
|
||||
const APP: () = {
|
||||
// change this
|
||||
static mut X: u32 = 0;
|
||||
|
@ -101,13 +113,13 @@ const APP: () = {
|
|||
|
||||
If your application was accessing the device peripherals in `#[init]` through
|
||||
the `device` variable then you'll need to add `peripherals = true` to the
|
||||
`#[rtic::app]` attribute to continue to access the device peripherals through
|
||||
`#[rtfm::app]` attribute to continue to access the device peripherals through
|
||||
the `device` field of the `init::Context` structure.
|
||||
|
||||
Change this:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
#[rtfm::app(/* .. */)]
|
||||
const APP: () = {
|
||||
#[init]
|
||||
fn init() {
|
||||
|
@ -121,7 +133,7 @@ const APP: () = {
|
|||
Into this:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */, peripherals = true)]
|
||||
#[rtfm::app(/* .. */, peripherals = true)]
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
const APP: () = {
|
||||
#[init]
|
||||
|
@ -137,13 +149,14 @@ const APP: () = {
|
|||
|
||||
## `#[interrupt]` and `#[exception]`
|
||||
|
||||
The `#[interrupt]` and `#[exception]` attributes have been removed. To declare
|
||||
hardware tasks in v0.5.x use the `#[task]` attribute with the `binds` argument.
|
||||
Remove the attributes `#[interrupt]` and `#[exception]`.
|
||||
To declare hardware tasks in v0.5.x use the `#[task]`
|
||||
attribute with the `binds` argument instead.
|
||||
|
||||
Change this:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
#[rtfm::app(/* .. */)]
|
||||
const APP: () = {
|
||||
// hardware tasks
|
||||
#[exception]
|
||||
|
@ -163,7 +176,7 @@ const APP: () = {
|
|||
Into this:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
#[rtfm::app(/* .. */)]
|
||||
const APP: () = {
|
||||
#[task(binds = SVCall)]
|
||||
// ^^^^^^^^^^^^^^
|
||||
|
@ -183,25 +196,26 @@ const APP: () = {
|
|||
|
||||
## `schedule`
|
||||
|
||||
The `schedule` API no longer requires the `timer-queue` cargo feature, which has
|
||||
been removed. To use the `schedule` API one must
|
||||
first define the monotonic timer the runtime will use using the `monotonic`
|
||||
argument of the `#[rtic::app]` attribute. To continue using the cycle counter
|
||||
(CYCCNT) as the monotonic timer, and match the behavior of version v0.4.x, add
|
||||
the `monotonic = rtic::cyccnt::CYCCNT` argument to the `#[rtic::app]` attribute.
|
||||
The `schedule` API no longer requires the `timer-queue` cargo feature.
|
||||
To use the `schedule` API one must first define the monotonic timer the
|
||||
runtime will use using the `monotonic` argument of the `#[rtfm::app]` attribute.
|
||||
To continue using the cycle counter (CYCCNT) as the monotonic timer,
|
||||
and match the behavior of version v0.4.x, add the `monotonic = rtfm::cyccnt::CYCCNT`
|
||||
argument to the `#[rtfm::app]` attribute.
|
||||
|
||||
Also, the `Duration` and `Instant` types and the `U32Ext` trait have been moved
|
||||
into the `rtic::cyccnt` module. This module is only available on ARMv7-M+
|
||||
devices. The removal of the `timer-queue` also brings back the `DWT` peripheral
|
||||
inside the core peripherals struct, this will need to be enabled by the application
|
||||
inside `init`.
|
||||
Also, the `Duration` and `Instant` types and the `U32Ext` trait moved
|
||||
into the `rtfm::cyccnt` module.
|
||||
This module is only available on ARMv7-M+ devices.
|
||||
The removal of the `timer-queue` also brings back the `DWT` peripheral
|
||||
inside the core peripherals struct, if `DWT` is required,
|
||||
ensure it is enabled by the application inside `init`.
|
||||
|
||||
Change this:
|
||||
|
||||
``` rust
|
||||
use rtic::{Duration, Instant, U32Ext};
|
||||
use rtfm::{Duration, Instant, U32Ext};
|
||||
|
||||
#[rtic::app(/* .. */)]
|
||||
#[rtfm::app(/* .. */)]
|
||||
const APP: () = {
|
||||
#[task(schedule = [b])]
|
||||
fn a() {
|
||||
|
@ -213,10 +227,10 @@ const APP: () = {
|
|||
Into this:
|
||||
|
||||
``` rust
|
||||
use rtic::cyccnt::{Duration, Instant, U32Ext};
|
||||
use rtfm::cyccnt::{Duration, Instant, U32Ext};
|
||||
// ^^^^^^^^
|
||||
|
||||
#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)]
|
||||
#[rtfm::app(/* .. */, monotonic = rtfm::cyccnt::CYCCNT)]
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
const APP: () = {
|
||||
#[init]
|
||||
|
|
|
@ -71,7 +71,7 @@ mod app {
|
|||
}
|
||||
```
|
||||
|
||||
## Move Dispatchers from `extern "C"` to app arguments.
|
||||
## Move Dispatchers from `extern "C"` to app arguments
|
||||
|
||||
Change
|
||||
|
||||
|
@ -171,7 +171,10 @@ fn b(_: b::Context) {}
|
|||
|
||||
## Symmetric locks
|
||||
|
||||
Now RTIC utilizes symmetric locks, this means that the `lock` method need to be used for all `shared` resource access. In old code one could do the following as the high priority task has exclusive access to the resource:
|
||||
Now RTIC utilizes symmetric locks, this means that the `lock` method need
|
||||
to be used for all `shared` resource access.
|
||||
In old code one could do the following as the high priority
|
||||
task has exclusive access to the resource:
|
||||
|
||||
``` rust
|
||||
#[task(priority = 2, resources = [r])]
|
||||
|
@ -354,6 +357,7 @@ Note that the attributes `spawn` and `schedule` are no longer needed.
|
|||
|
||||
### Extern tasks
|
||||
|
||||
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.
|
||||
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`.
|
||||
|
|
|
@ -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 is available [here](../../api/).
|
||||
|
||||
Formerly known as Real-Time For the Masses.
|
||||
|
||||
|
|
Loading…
Reference in a new issue