1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
# Очередь таймера
В отличие от интерфейса `spawn`, который немедленно передает программную задачу
планировщику для немедленного запуска, интерфейс `schedule` можно использовать
для планирования задачи к запуске через какое-то время в будущем.
Чтобы использовать интерфейс `schedule`, предварительно должен быть определен
монотонный таймер с помощью аргумента `monotonic` атрибута `#[app]`.
Этот аргумент принимает путь к типу, реализующему трейт [`Monotonic`].
Ассоциированный тип, `Instant`, этого трейта представляет метку времени в соответствущих
единицах измерения и широко используется в интерфейсе `schedule` -- предлагается смоделировать
этот тип позднее [один из таких есть в стандартной библиотеке][std-instant].
Хотя это не отражено в определении трейта (из-за ограничений системы типов / трейтов),
разница двух `Instant`ов должна возвращать какой-то тип `Duration` (см. [`core::time::Duration`])
и этот `Duration` должен реализовывать трейт `TryInto<u32>`.
Реализация этого трейта должна конвертировать значение `Duration`, которое
использует какую-то определенную единицу измерения времени, в единицы измерения "тактов системного таймера
(SYST)". Результат преобразований должен быть 32-битным целым.
Если результат не соответствует 32-битному целому, тогда операция должна возвращать ошибку любого типа.
[`Monotonic`]: ../../../api/rtic/trait.Monotonic.html
[std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html
[`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html
Для целевых платформ 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`.
``` rust
{{#include ../../../../examples/schedule.rs}}
```
Запусе программы на реальном оборудовании создает следующий вывод в консоли:
``` text
{{#include ../../../../ci/expected/schedule.run}}
```
Когда интерфейс `schedule` используется, среда исполнения использует внутри
обработчик прерываний `SysTick` и периферию системного таймера (`SYST`), поэтому ни
тот ни другой нельзя использовать в программе. Это гарантируется изменением типа
`init::Context.core` с `cortex_m::Peripherals` на `rtic::Peripherals`.
Последняя структура содержит все поля из предыдущей кроме `SYST`.
## Периодические задачи
Программные задачи имеют доступ к моменту времени `Instant`, в который они были запланированы
на выполнение переменной `scheduled`. Эта информация и интерфейс `schedule` можно использовать,
чтобы реализовать периодические задачи, как показано ниже.
``` rust
{{#include ../../../../examples/periodic.rs}}
```
Это вывод, создаваемый примером. Заметьте, что здесь пристствует небольшой дрейф / колебания
даже несмотря на то, что `schedule.foo` была вызвана в *конце* `foo`. Использование
`Instant::now` вместо `scheduled` вызвало бы дрейф / колебания.
``` text
{{#include ../../../../ci/expected/periodic.run}}
```
## Базовое время
Для задач, вызываемых из `init` мы имеем точную информацию о их `scheduled` времени.
Для аппаратных задач такого времени нет, поскольку они асинхронны по природе.
Для аппаратных задач среда исполнения предоставляет время запуска (`start`), которое отражает
время, в которое обработчик прерывания будет запущен.
Заметьте, что `start` **не** равно времени прихода события, которое вызывает задачу.
В зависимости от приоритета задачи и загрузки системы, время `start` может сильно отдалиться от
времени прихода события.
Какое по вашему мнению будет значение `scheduled` для программных задач, которые вызываются через
`spawn` вместо планирования? Ответ в том, что вызываемые задачи наследуют
*базовое* время того контекста, который их вызывает. Базовое время аппаратных задач -
это их время `start`, базовое время программных задач - их время `scheduled`, а
базовое время `init` - время старта системы, или нулевое
(`Instant::zero()`). `idle` на самом деле не имеет базового времени, но задачи вызываемые из нее,
используют `Instant::now()` в качестве базового.
Пример ниже демонстрирует разные смыслы *базового времени*.
``` rust
{{#include ../../../../examples/baseline.rs}}
```
Запуск программы на реальном оборудовании приведет к следующему выводу в консоли:
``` text
{{#include ../../../../ci/expected/baseline.run}}
```
|