Monotonic & spawn_
The understanding of time is an important concept in embedded systems, and to be able to run tasks
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.
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 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.
The attribute has one required parameter and two optional parameters, binds
, default
and
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.
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 themonotonic
task to a lower priority to reduce scheduling jitter for the high priority task. This however might introduce jitter and delays into scheduling via themonotonic
, making it a trade-off.
The monotonics are initialized in #[init]
and returned within the init::Monotonic( ... )
tuple.
This activates the monotonics making it possible to use them.
See the following example:
#![allow(unused)] fn main() { //! examples/schedule.rs #![deny(unsafe_code)] #![deny(warnings)] #![deny(missing_docs)] #![no_main] #![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0])] mod app { use cortex_m_semihosting::{debug, hprintln}; use systick_monotonic::*; #[monotonic(binds = SysTick, default = true)] type MyMono = Systick<100>; // 100 Hz / 10 ms granularity #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { let systick = cx.core.SYST; // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); hprintln!("init"); // Schedule `foo` to run 1 second in the future foo::spawn_after(1.secs()).unwrap(); ( Shared {}, Local {}, init::Monotonics(mono), // Give the monotonic to RTIC ) } #[task] fn foo(_: foo::Context) { hprintln!("foo"); // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) bar::spawn_after(1.secs()).unwrap(); } #[task] fn bar(_: bar::Context) { hprintln!("bar"); // Schedule `baz` to run 1 seconds from now, but with a specific time instant. baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); } #[task] fn baz(_: baz::Context) { hprintln!("baz"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } }
$ cargo run --target thumbv7m-none-eabi --example schedule
init
foo
bar
baz
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:
#![allow(unused)] fn main() { //! examples/cancel-reschedule.rs #![deny(unsafe_code)] #![deny(warnings)] #![deny(missing_docs)] #![no_main] #![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0])] mod app { use cortex_m_semihosting::{debug, hprintln}; use systick_monotonic::*; #[monotonic(binds = SysTick, default = true)] type MyMono = Systick<100>; // 100 Hz / 10 ms granularity #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { let systick = cx.core.SYST; // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); hprintln!("init"); // Schedule `foo` to run 1 second in the future foo::spawn_after(1.secs()).unwrap(); ( Shared {}, Local {}, init::Monotonics(mono), // Give the monotonic to RTIC ) } #[task] fn foo(_: foo::Context) { hprintln!("foo"); // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) let spawn_handle = baz::spawn_after(2.secs()).unwrap(); bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true } #[task] fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { hprintln!("bar"); if do_reschedule { // Reschedule baz 2 seconds from now, instead of the original 1 second // from now. baz_handle.reschedule_after(2.secs()).unwrap(); // Or baz_handle.reschedule_at(/* time */) } else { // Or cancel it baz_handle.cancel().unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } #[task] fn baz(_: baz::Context) { hprintln!("baz"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } }
$ cargo run --target thumbv7m-none-eabi --example cancel-reschedule
init
foo
bar