Базовое время
Для задач, вызываемых из init мы имеем точную информацию о их scheduled времени. Для аппаратных задач такого времени нет, поскольку они асинхронны по природе. Для аппаратных задач среда исполнения предоставляет время запуска (start), которое отражает время, в которое обработчик прерывания будет запущен.
Заметьте, что start не равно времени прихода события, которое вызывает задачу. В зависимости от приоритета задачи и загрузки системы, время start может сильно отдалиться от времени прихода события.
Какое по вашему мнению будет значение scheduled для программных задач, которые вызываются через spawn вместо планирования? Ответ в том, что вызываемые задачи наследуют базовое время того контекста, который их вызывает. Базовое время аппаратных задач - это их время start, базовое время программных задач - их время scheduled, а базовое время init - время старта системы, или нулевое (Instant::zero()). idle на самом деле не имеет базового времени, но задачи вызываемые из нее, используют Instant::now() в качестве базового.
Пример ниже демонстрирует разные смыслы базового времени.
#![allow(unused)]
fn main() {
{{#include ../../../../examples/baseline.rs}}
}
Запуск программы на реальном оборудовании приведет к следующему выводу в консоли:
init(baseline = Instant(0))
foo(baseline = Instant(0))
UART0(baseline = Instant(904))
foo(baseline = Instant(904))
Типы, Send и Sync
Каждая функция в модуле app принимает структуру Context в качесте первого параметра. Все поля этих структур имеют предсказуемые, неанонимные типы, поэтому вы можете написать обычные функции, принимающие их как аргументы.
Справочник по API определяет как эти типы генерируются на основе входных данных. Вы можете также сгенерировать документацию к вашему крейту программы (cargo doc --bin ); в документации вы найдете структуры Context (например init::Context и idle::Context).
Пример ниже показывает различные типы, сгенерированные атрибутом app.
#![allow(unused)]
fn main() {
{{#include ../../../../examples/types.rs}}
}
Send
Send - это маркерный трейт для "типов, которые можно передавать через границы потоков", как это определено в core. В контексте RTIC трейт Send необходим только там, где возможна передача значения между задачами, запускаемыми на разных приоритетах. Это возникает в нескольких случаях: при передаче сообщений, в разделяемых static mut ресурсах и при инициализации поздних ресурсов.
Атрибут app проверит, что Send реализован, где необходимо, поэтому вам не стоит волноваться об этом. В настоящий момент все передаваемые типы в RTIC должны быть Send, но это ограничение возможно будет ослаблено в будущем.
Sync
Аналогично, Sync - маркерный трейт для "типов, на которые можно безопасно разделять между потоками", как это определено в core. В контексте RTIC типаж Sync необходим только там, где возможно для двух или более задач, запускаемых на разных приоритетах получить разделяемую ссылку (&-) на ресурс. Это возникает только (&-) ресурсах с разделяемым доступом.
Атрибут app проверит, что Sync реализован, где необходимо, но важно знать, где ограничение Sync не требуется: в (&-) ресурсах с разделяемым доступом, за которые соперничают задачи с одинаковым приоритетом.
В примере ниже показано, где можно использовать типы, не реализующие Sync.
#![allow(unused)]
fn main() {
//! `examples/not-sync.rs`
// #![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
use core::marker::PhantomData;
use panic_semihosting as _;
pub struct NotSync {
_0: PhantomData<*const ()>,
}
unsafe impl Send for NotSync {}
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
mod app {
use super::NotSync;
use core::marker::PhantomData;
use cortex_m_semihosting::debug;
#[shared]
struct Shared {
shared: NotSync,
}
#[local]
struct Local {}
#[init]
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
debug::exit(debug::EXIT_SUCCESS);
(
Shared {
shared: NotSync { _0: PhantomData },
},
Local {},
init::Monotonics(),
)
}
#[task(shared = [&shared])]
fn foo(c: foo::Context) {
let _: &NotSync = c.shared.shared;
}
#[task(shared = [&shared])]
fn bar(c: bar::Context) {
let _: &NotSync = c.shared.shared;
}
}
}
Создание нового проекта
Теперь, когда Вы изучили основные возможности фреймворка RTIC, Вы можете попробовать его использовать на Вашем оборудовании следуя этим инструкциям.
1. Создайте экземпляр из шаблона cortex-m-quickstart.
$ # например используя `cargo-generate`
$ cargo generate \
--git https://github.com/rust-embedded/cortex-m-quickstart \
--name app
$ # следуйте остальным инструкциям
2. Добавьте крейт доступа к периферии (PAC), сгенерированный с помощьюsvd2rustv0.14.x, или крейт отладочной платы, у которой в зависимостях один из таких PAC'ов. Убедитесь, что опция rt крейта включена.
В этом примере я буду использовать крейт устройства lm3s6965. Эта библиотека не имеет Cargo-опции rt; эта опция всегда включена.
Этот крейт устройства предоставляет линковочный скрипт с макетом памяти целевого устройства, поэтому memory.x и build.rs нужно удалить.
$ cargo add lm3s6965 --vers 0.1.3
$ rm memory.x build.rs
3. Добавьте крейт cortex-m-rtic как зависимость.
$ cargo add cortex-m-rtic --allow-prerelease
4. Напишите свою RTIC программу.
Здесь я буду использовать пример init из крейта cortex-m-rtic.
Примеры находтся в папке examples, а содержание init.rs показано здесь:
//! examples/init.rs
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
use panic_semihosting as _;
#[rtic::app(device = lm3s6965, peripherals = true)]
mod app {
use cortex_m_semihosting::{debug, hprintln};
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init(local = [x: u32 = 0])]
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
// Cortex-M peripherals
let _core: cortex_m::Peripherals = cx.core;