2018-11-03 17:02:41 +01:00
|
|
|
# Software tasks
|
|
|
|
|
2019-08-21 10:17:27 +02:00
|
|
|
In addition to hardware tasks, which are invoked by the hardware in response to
|
2020-06-11 19:18:29 +02:00
|
|
|
hardware events, RTIC also supports *software* tasks which can be spawned by the
|
2019-08-21 10:17:27 +02:00
|
|
|
application from any execution context.
|
|
|
|
|
|
|
|
Software tasks can also be assigned priorities and, under the hood, are
|
2020-06-11 19:18:29 +02:00
|
|
|
dispatched from interrupt handlers. RTIC requires that free interrupts are
|
2019-08-21 10:17:27 +02:00
|
|
|
declared in an `extern` block when using software tasks; some of these free
|
|
|
|
interrupts will be used to dispatch the software tasks. An advantage of software
|
|
|
|
tasks over hardware tasks is that many tasks can be mapped to a single interrupt
|
|
|
|
handler.
|
|
|
|
|
|
|
|
Software tasks are also declared using the `task` attribute but the `binds`
|
|
|
|
argument must be omitted. To be able to spawn a software task from a context
|
|
|
|
the name of the task must appear in the `spawn` argument of the context
|
|
|
|
attribute (`init`, `idle`, `task`, etc.).
|
2018-11-03 17:02:41 +01:00
|
|
|
|
|
|
|
The example below showcases three software tasks that run at 2 different
|
2019-08-21 10:17:27 +02:00
|
|
|
priorities. The three software tasks are mapped to 2 interrupts handlers.
|
2018-11-03 17:02:41 +01:00
|
|
|
|
|
|
|
``` rust
|
2019-02-11 21:40:53 +01:00
|
|
|
{{#include ../../../../examples/task.rs}}
|
2018-11-03 17:02:41 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
``` console
|
|
|
|
$ cargo run --example task
|
2019-02-11 21:40:53 +01:00
|
|
|
{{#include ../../../../ci/expected/task.run}}```
|
2018-11-03 17:02:41 +01:00
|
|
|
|
|
|
|
## Message passing
|
|
|
|
|
|
|
|
The other advantage of software tasks is that messages can be passed to these
|
|
|
|
tasks when spawning them. The type of the message payload must be specified in
|
|
|
|
the signature of the task handler.
|
|
|
|
|
|
|
|
The example below showcases three tasks, two of them expect a message.
|
|
|
|
|
|
|
|
``` rust
|
2019-02-11 21:40:53 +01:00
|
|
|
{{#include ../../../../examples/message.rs}}
|
2018-11-03 17:02:41 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
``` console
|
|
|
|
$ cargo run --example message
|
2019-02-11 21:40:53 +01:00
|
|
|
{{#include ../../../../ci/expected/message.run}}```
|
2018-11-03 17:02:41 +01:00
|
|
|
|
|
|
|
## Capacity
|
|
|
|
|
2020-06-11 19:18:29 +02:00
|
|
|
RTIC does *not* perform any form of heap-based memory allocation. The memory
|
2019-08-21 10:17:27 +02:00
|
|
|
required to store messages is statically reserved. By default the framework
|
|
|
|
minimizes the memory footprint of the application so each task has a message
|
|
|
|
"capacity" of 1: meaning that at most one message can be posted to the task
|
|
|
|
before it gets a chance to run. This default can be overridden for each task
|
|
|
|
using the `capacity` argument. This argument takes a positive integer that
|
|
|
|
indicates how many messages the task message buffer can hold.
|
2018-11-03 17:02:41 +01:00
|
|
|
|
|
|
|
The example below sets the capacity of the software task `foo` to 4. If the
|
|
|
|
capacity is not specified then the second `spawn.foo` call in `UART0` would
|
2019-08-21 10:17:27 +02:00
|
|
|
fail (panic).
|
2018-11-03 17:02:41 +01:00
|
|
|
|
|
|
|
``` rust
|
2019-02-11 21:40:53 +01:00
|
|
|
{{#include ../../../../examples/capacity.rs}}
|
2018-11-03 17:02:41 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
``` console
|
|
|
|
$ cargo run --example capacity
|
2019-02-11 21:40:53 +01:00
|
|
|
{{#include ../../../../ci/expected/capacity.run}}```
|
2019-08-21 10:17:27 +02:00
|
|
|
|
|
|
|
## Error handling
|
|
|
|
|
|
|
|
The `spawn` API returns the `Err` variant when there's no space to send the
|
|
|
|
message. In most scenarios spawning errors are handled in one of two ways:
|
|
|
|
|
|
|
|
- Panicking, using `unwrap`, `expect`, etc. This approach is used to catch the
|
|
|
|
programmer error (i.e. bug) of selecting a capacity that was too small. When
|
|
|
|
this panic is encountered during testing choosing a bigger capacity and
|
|
|
|
recompiling the program may fix the issue but sometimes it's necessary to dig
|
|
|
|
deeper and perform a timing analysis of the application to check if the
|
|
|
|
platform can deal with peak payload or if the processor needs to be replaced
|
|
|
|
with a faster one.
|
|
|
|
|
|
|
|
- Ignoring the result. In soft real time and non real time applications it may
|
|
|
|
be OK to occasionally lose data or fail to respond to some events during event
|
|
|
|
bursts. In those scenarios silently letting a `spawn` call fail may be
|
|
|
|
acceptable.
|
|
|
|
|
|
|
|
It should be noted that retrying a `spawn` call is usually the wrong approach as
|
|
|
|
this operation will likely never succeed in practice. Because there are only
|
|
|
|
context switches towards *higher* priority tasks retrying the `spawn` call of a
|
|
|
|
lower priority task will never let the scheduler dispatch said task meaning that
|
|
|
|
its message buffer will never be emptied. This situation is depicted in the
|
|
|
|
following snippet:
|
|
|
|
|
|
|
|
``` rust
|
2020-06-11 19:18:29 +02:00
|
|
|
#[rtic::app(..)]
|
2019-08-21 10:17:27 +02:00
|
|
|
const APP: () = {
|
|
|
|
#[init(spawn = [foo, bar])]
|
|
|
|
fn init(cx: init::Context) {
|
|
|
|
cx.spawn.foo().unwrap();
|
|
|
|
cx.spawn.bar().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[task(priority = 2, spawn = [bar])]
|
|
|
|
fn foo(cx: foo::Context) {
|
|
|
|
// ..
|
|
|
|
|
|
|
|
// the program will get stuck here
|
|
|
|
while cx.spawn.bar(payload).is_err() {
|
|
|
|
// retry the spawn call if it failed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[task(priority = 1)]
|
|
|
|
fn bar(cx: bar::Context, payload: i32) {
|
|
|
|
// ..
|
|
|
|
}
|
|
|
|
};
|
|
|
|
```
|