8.3 KiB
Очередь таймера
В отличие от интерфейса spawn, который немедленно передает программную задачу
планировщику для немедленного запуска, интерфейс schedule можно использовать
для планирования задачи к запуске через какое-то время в будущем.
Чтобы использовать интерфейс schedule, предварительно должен быть определен
монотонный таймер с помощью аргумента monotonic атрибута #[app].
Этот аргумент принимает путь к типу, реализующему трейт Monotonic.
Ассоциированный тип, Instant, этого трейта представляет метку времени в соответствущих
единицах измерения и широко используется в интерфейсе schedule -- предлагается смоделировать
этот тип позднее один из таких есть в стандартной библиотеке.
Хотя это не отражено в определении трейта (из-за ограничений системы типов / трейтов),
разница двух Instantов должна возвращать какой-то тип Duration (см. core::time::Duration)
и этот Duration должен реализовывать трейт TryInto<u32>.
Реализация этого трейта должна конвертировать значение Duration, которое
использует какую-то определенную единицу измерения времени, в единицы измерения "тактов системного таймера
(SYST)". Результат преобразований должен быть 32-битным целым.
Если результат не соответствует 32-битному целому, тогда операция должна возвращать ошибку любого типа.
Для целевых платформ ARMv7+ крейт rtic предоставляет реализацию Monotonic, основанную на
встроенном CYCle CouNTer (CYCCNT). Заметьте, что это 32-битный таймер, работающий на
частоте центрального процессора, и поэтому не подходит для отслеживания интервалов времени в секундах.
Когда планируется задача, (определенный пользователем) Instant, в который задача должна быть
выполнена, должен передаваться в качестве первого аргумента вызова schedule.
К тому же, выбранный monotonic таймер, необходимо сконфигурировать и инициализировать в
фазе работы #[init]. Заметьте, что также касается случая использования CYCCNT,
предоставляемого крейтом cortex-m-rtic.
Пример ниже планирует к выполнению две задачи из init: foo и bar. foo запланирована
к запуску через 8 миллионов циклов в будущем. Далее, bar запланировано запустить через
4 миллиона циклов в будущем. Таким образом, bar запустится до foo, так как и запланировано.
DF:YJ: Примеры, использующие интерфейс
scheduleили абстракциюInstantне будут правильно работать на эмуляторе QEMU, поскольку счетчик циклов Cortex-M функционально не был реализован вqemu-system-arm.
{{#include ../../../../examples/schedule.rs}}
Запусе программы на реальном оборудовании создает следующий вывод в консоли:
{{#include ../../../../ci/expected/schedule.run}}
Когда интерфейс schedule используется, среда исполнения использует внутри
обработчик прерываний SysTick и периферию системного таймера (SYST), поэтому ни
тот ни другой нельзя использовать в программе. Это гарантируется изменением типа
init::Context.core с cortex_m::Peripherals на rtic::Peripherals.
Последняя структура содержит все поля из предыдущей кроме SYST.
Периодические задачи
Программные задачи имеют доступ к моменту времени Instant, в который они были запланированы
на выполнение переменной scheduled. Эта информация и интерфейс schedule можно использовать,
чтобы реализовать периодические задачи, как показано ниже.
{{#include ../../../../examples/periodic.rs}}
Это вывод, создаваемый примером. Заметьте, что здесь пристствует небольшой дрейф / колебания
даже несмотря на то, что schedule.foo была вызвана в конце foo. Использование
Instant::now вместо scheduled вызвало бы дрейф / колебания.
{{#include ../../../../ci/expected/periodic.run}}
Базовое время
Для задач, вызываемых из init мы имеем точную информацию о их scheduled времени.
Для аппаратных задач такого времени нет, поскольку они асинхронны по природе.
Для аппаратных задач среда исполнения предоставляет время запуска (start), которое отражает
время, в которое обработчик прерывания будет запущен.
Заметьте, что start не равно времени прихода события, которое вызывает задачу.
В зависимости от приоритета задачи и загрузки системы, время start может сильно отдалиться от
времени прихода события.
Какое по вашему мнению будет значение scheduled для программных задач, которые вызываются через
spawn вместо планирования? Ответ в том, что вызываемые задачи наследуют
базовое время того контекста, который их вызывает. Базовое время аппаратных задач -
это их время start, базовое время программных задач - их время scheduled, а
базовое время init - время старта системы, или нулевое
(Instant::zero()). idle на самом деле не имеет базового времени, но задачи вызываемые из нее,
используют Instant::now() в качестве базового.
Пример ниже демонстрирует разные смыслы базового времени.
{{#include ../../../../examples/baseline.rs}}
Запуск программы на реальном оборудовании приведет к следующему выводу в консоли:
{{#include ../../../../ci/expected/baseline.run}}