Очередь таймера

Когда включена опция 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]

use cortex_m::peripheral::DWT;
use cortex_m_semihosting::hprintln;
use panic_halt as _;
use rtfm::cyccnt::{Instant, U32Ext as _};

// NOTE: does NOT work on QEMU!
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
    #[init(schedule = [foo, bar])]
    fn init(mut cx: init::Context) {
        // Initialize (enable) the monotonic timer (CYCCNT)
        cx.core.DCB.enable_trace();
        // required on Cortex-M7 devices that software lock the DWT (e.g. STM32F7)
        DWT::unlock();
        cx.core.DWT.enable_cycle_counter();

        // semantically, the monotonic timer is frozen at time "zero" during `init`
        // NOTE do *not* call `Instant::now` in this context; it will return a nonsense value
        let now = cx.start; // the start time of the system

        hprintln!("init @ {:?}", now).unwrap();

        // Schedule `foo` to run 8e6 cycles (clock cycles) in the future
        cx.schedule.foo(now + 8_000_000.cycles()).unwrap();

        // Schedule `bar` to run 4e6 cycles in the future
        cx.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]

use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
use rtfm::cyccnt::{Instant, U32Ext};

const PERIOD: u32 = 8_000_000;

// NOTE: does NOT work on QEMU!
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
    #[init(schedule = [foo])]
    fn init(cx: init::Context) {
        // omitted: initialization of `CYCCNT`

        cx.schedule.foo(Instant::now() + PERIOD.cycles()).unwrap();
    }

    #[task(schedule = [foo])]
    fn foo(cx: foo::Context) {
        let now = Instant::now();
        hprintln!("foo(scheduled = {:?}, now = {:?})", cx.scheduled, now).unwrap();

        cx.schedule.foo(cx.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]

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;

// NOTE: does NOT properly work on QEMU
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init(cx: init::Context) {
        // omitted: initialization of `CYCCNT`

        hprintln!("init(baseline = {:?})", cx.start).unwrap();

        // `foo` inherits the baseline of `init`: `Instant(0)`
        cx.spawn.foo().unwrap();
    }

    #[task(schedule = [foo])]
    fn foo(cx: foo::Context) {
        static mut ONCE: bool = true;

        hprintln!("foo(baseline = {:?})", cx.scheduled).unwrap();

        if *ONCE {
            *ONCE = false;

            rtfm::pend(Interrupt::UART0);
        } else {
            debug::exit(debug::EXIT_SUCCESS);
        }
    }

    #[task(binds = UART0, spawn = [foo])]
    fn uart0(cx: uart0::Context) {
        hprintln!("UART0(baseline = {:?})", cx.start).unwrap();

        // `foo` inherits the baseline of `UART0`: its `start` time
        cx.spawn.foo().unwrap();
    }

    extern "C" {
        fn UART1();
    }
};

#}

Запуск программы на реальном оборудовании произведет следующий вывод в консоли:

init(baseline = Instant(0))
foo(baseline = Instant(0))
UART0(baseline = Instant(904))
foo(baseline = Instant(904))