Очередь таймера
Когда включена опция timer-queue
, фреймворк RTFM включает
глобальную очередь таймера, которую приложения могут использовать, чтобы
планировать программные задачи на запуск через некоторое время в будущем.
Чтобы была возможность планировать программную задачу, имя задачи должно
присутствовать в аргументе schedule
контекста атрибута. Когда задача
планируется, момент (Instant
), в который задачу нужно запустить, нужно передать
как первый аргумент вызова schedule
.
Рантайм RTFM включает монотонный, растущий только вверх, 32-битный таймер,
значение которого можно запросить конструктором Instant::now
. Время (Duration
)
можно передать в Instant::now()
, чтобы получить Instant
в будущем. Монотонный
таймер отключен пока запущен init
, поэтому Instant::now()
всегда возвращает
значение Instant(0 /* циклов тактовой частоты */)
; таймер включается сразу перед
включением прерываний и запуском idle
.
В примере ниже две задачи планируются из init
: foo
и bar
. foo
-
запланирована на запуск через 8 миллионов тактов в будущем. Кроме того, bar
запланирован на запуск через 4 миллиона тактов в будущем. bar
запустится раньше
foo
, т.к. он запланирован на запуск первым.
ВАЖНО: Примеры, использующие API
schedule
или абстракциюInstant
не будут правильно работать на QEMU, потому что функциональность счетчика тактов Cortex-M не реализована вqemu-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(); } }; #}
Запуск программы на реальном оборудовании производит следующий вывод в консоли:
init @ Instant(0)
bar @ Instant(4000236)
foo @ Instant(8000173)
Периодические задачи
Программные задачи имеют доступ к Instant
в момент, когда были запланированы
на запуск через переменную scheduled
. Эта информация и API schedule
могут
быть использованы для реализации периодических задач, как показано в примере ниже.
# #![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(); } }; #}
Это вывод, произведенный примером. Заметьте, что есть смещение / колебание нуля
даже если schedule.foo
была вызвана в конце foo
. Использование
Instant::now
вместо scheduled
имело бы влияние на смещение / колебание.
foo(scheduled = Instant(8000000), now = Instant(8000196))
foo(scheduled = Instant(16000000), now = Instant(16000196))
foo(scheduled = Instant(24000000), now = Instant(24000196))
Базовое время
Для задач, планируемых из init
мы имеем точную информацию о их планируемом
(scheduled
) времени. Для аппаратных задач нет scheduled
времени, потому
что эти задачи асинхронны по природе. Для аппаратных задач рантайм предоставляет
время старта (start
), которе отражает время, в которое обработчик прерывания
был запущен.
Заметьте, что start
не равен времени возникновения события, вызвавшего
задачу. В зависимости от приоритета задачи и загрузки системы время
start
может быть сильно отдалено от времени возникновения события.
Какое по Вашему мнению будет значение scheduled
для программных задач которые
вызываются, вместо того чтобы планироваться? Ответ в том, что вызываемые
задачи наследуют базовое время контекста, в котором вызваны. Бызовым для
аппаратных задач является start
, базовым для программных задач - scheduled
и базовым для init
- start = Instant(0)
. idle
на сомом деле не имеет
базового времени но задачи, вызванные из него будут использовать Instant::now()
как их базовое время.
Пример ниже демонстрирует разное значение базового времени.
# #![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(); } }; #}
Запуск программы на реальном оборудовании произведет следующий вывод в консоли:
init(baseline = Instant(0))
foo(baseline = Instant(0))
UART0(baseline = Instant(904))
foo(baseline = Instant(904))