Timer queue
When the timer-queue
feature is enabled the RTFM framework includes a global
timer queue that applications can use to schedule software tasks to run at
some time in the future.
NOTE: The timer-queue feature can't be enabled when the target is
thumbv6m-none-eabi
because there's no timer queue support for ARMv6-M. This may change in the future.
NOTE: When the
timer-queue
feature is enabled you will not be able to use theSysTick
exception as a hardware task because the runtime uses it to implement the global timer queue.
To be able to schedule a software task the name of the task must appear in the
schedule
argument of the context attribute. When scheduling a task the
Instant
at which the task should be executed must be passed as the first
argument of the schedule
invocation.
The RTFM runtime includes a monotonic, non-decreasing, 32-bit timer which can be
queried using the Instant::now
constructor. A Duration
can be added to
Instant::now()
to obtain an Instant
into the future. The monotonic timer is
disabled while init
runs so Instant::now()
always returns the value
Instant(0 /* clock cycles */)
; the timer is enabled right before the
interrupts are re-enabled and idle
is executed.
The example below schedules two tasks from init
: foo
and bar
. foo
is
scheduled to run 8 million clock cycles in the future. Next, bar
is scheduled
to run 4 million clock cycles in the future. bar
runs before foo
since it
was scheduled to run first.
IMPORTANT: The examples that use the
schedule
API or theInstant
abstraction will not properly work on QEMU because the Cortex-M cycle counter functionality has not been implemented inqemu-system-arm
.
# #![allow(unused_variables)] #fn main() { //! examples/schedule.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::hprintln; use rtfm::Instant; // NOTE: does NOT work on QEMU! #[rtfm::app(device = lm3s6965)] const APP: () = { #[init(schedule = [foo, bar])] fn init(c: init::Context) { let now = Instant::now(); hprintln!("init @ {:?}", now).unwrap(); // Schedule `foo` to run 8e6 cycles (clock cycles) in the future c.schedule.foo(now + 8_000_000.cycles()).unwrap(); // Schedule `bar` to run 4e6 cycles in the future c.schedule.bar(now + 4_000_000.cycles()).unwrap(); } #[task] fn foo(_: foo::Context) { hprintln!("foo @ {:?}", Instant::now()).unwrap(); } #[task] fn bar(_: bar::Context) { hprintln!("bar @ {:?}", Instant::now()).unwrap(); } extern "C" { fn UART0(); } }; #}
Running the program on real hardware produces the following output in the console:
init @ Instant(0)
bar @ Instant(4000236)
foo @ Instant(8000173)
Periodic tasks
Software tasks have access to the Instant
at which they were scheduled to run
through the scheduled
variable. This information and the schedule
API can be
used to implement periodic tasks as shown in the example below.
# #![allow(unused_variables)] #fn main() { //! examples/periodic.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::hprintln; use rtfm::Instant; const PERIOD: u32 = 8_000_000; // NOTE: does NOT work on QEMU! #[rtfm::app(device = lm3s6965)] const APP: () = { #[init(schedule = [foo])] fn init(c: init::Context) { c.schedule.foo(Instant::now() + PERIOD.cycles()).unwrap(); } #[task(schedule = [foo])] fn foo(c: foo::Context) { let now = Instant::now(); hprintln!("foo(scheduled = {:?}, now = {:?})", c.scheduled, now).unwrap(); c.schedule.foo(c.scheduled + PERIOD.cycles()).unwrap(); } extern "C" { fn UART0(); } }; #}
This is the output produced by the example. Note that there is zero drift /
jitter even though schedule.foo
was invoked at the end of foo
. Using
Instant::now
instead of scheduled
would have resulted in drift / jitter.
foo(scheduled = Instant(8000000), now = Instant(8000196))
foo(scheduled = Instant(16000000), now = Instant(16000196))
foo(scheduled = Instant(24000000), now = Instant(24000196))
Baseline
For the tasks scheduled from init
we have exact information about their
scheduled
time. For hardware tasks there's no scheduled
time because these
tasks are asynchronous in nature. For hardware tasks the runtime provides a
start
time, which indicates the time at which the task handler started
executing.
Note that start
is not equal to the arrival time of the event that fired
the task. Depending on the priority of the task and the load of the system the
start
time could be very far off from the event arrival time.
What do you think will be the value of scheduled
for software tasks that are
spawned instead of scheduled? The answer is that spawned tasks inherit the
baseline time of the context that spawned it. The baseline of hardware tasks
is start
, the baseline of software tasks is scheduled
and the baseline of
init
is start = Instant(0)
. idle
doesn't really have a baseline but tasks
spawned from it will use Instant::now()
as their baseline time.
The example below showcases the different meanings of the baseline.
# #![allow(unused_variables)] #fn main() { //! examples/baseline.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; // NOTE: does NOT properly work on QEMU #[rtfm::app(device = lm3s6965)] const APP: () = { #[init(spawn = [foo])] fn init(c: init::Context) { hprintln!("init(baseline = {:?})", c.start).unwrap(); // `foo` inherits the baseline of `init`: `Instant(0)` c.spawn.foo().unwrap(); } #[task(schedule = [foo])] fn foo(c: foo::Context) { static mut ONCE: bool = true; hprintln!("foo(baseline = {:?})", c.scheduled).unwrap(); if *ONCE { *ONCE = false; rtfm::pend(Interrupt::UART0); } else { debug::exit(debug::EXIT_SUCCESS); } } #[interrupt(spawn = [foo])] fn UART0(c: UART0::Context) { hprintln!("UART0(baseline = {:?})", c.start).unwrap(); // `foo` inherits the baseline of `UART0`: its `start` time c.spawn.foo().unwrap(); } extern "C" { fn UART1(); } }; #}
Running the program on real hardware produces the following output in the console:
init(baseline = Instant(0))
foo(baseline = Instant(0))
UART0(baseline = Instant(904))
foo(baseline = Instant(904))