12 KiB
Атрибут app
Это простейшая из возможных программ на RTIC:
{{#include ../../../../examples/smallest.rs}}
Все программы на RTIC используют атрибут app (#[app(..)]). Этот атрибут
должен применяться к элементу mod. Атрибут app имеет обязательный аргумент device,
который принимает путь как значение. Это должен быть полный путь, указывающий на
крейт доступа к периферии (PAC), сгенерированный с помощью svd2rust версии v0.14.x
или новее. Более подробно в разделе Создание нового проекта.
Атрибут app будет раскрыт в подходящую точку входа программы, поэтому
атрибут cortex_m_rt::entry не нужен.
init
Внутри модуля app атрибут ожидает найти функцию инициализации, помеченную
атрибутом init. Эта функция должна иметь сигнатуру
fn(init::Context) (-> init::LateResources, init::Monotonics).
Эта функция инициализации будет первой частью программы, выполняемой при запуске.
Функция init будет запущена с отключенными прерываниями и будет иметь эксклюзивный доступ
к Cortex-M, в котором токен bare_metal::CriticalSection доступен как cs.
Опционально, устройство-специфичные периферия доступна через поля core и device структуры
init::Context.
static mut переменные, определенные в начале init будут преобразованы в
&'static mut ссылки, безопасные для доступа. Обратите внимание, данная возможность может
быть удалена в следующем релизе, см. task_local ресурсы.
Пример ниже показывает типы полей core, device и cs, и демонстрирует
безопасный доступ к static mut переменной. Поле device доступно только
когда аргумент peripherals установлен в true (по умолчанию).
В редких случаях, когда вы захотите создать приложение с минимальным потреблением ресурсов,
можно явно установить peripherals в false.
{{#include ../../../../examples/init.rs}}
Запуск примера напечатате init в консоли, а затем завершит процесс QEMU.
$ cargo run --example init
{{#include ../../../../ci/expected/init.run}}
ПРИМЕЧАНИЕ: Не забывайте указывать выбранное вами целевое устройство, передавая параметр target в cargo (например
cargo run --example init --target thumbv7m-none-eabi) или настроив устройство, используемое по умолчанию для сборки примеров в.cargo/config.toml. В нашем случае используется Cortex M3, эмулируемый с помощью QEMU, поэтому пишемthumbv7m-none-eabi. СмотритеСоздание нового проектадля большей информации.
idle
Функцию, помеченную атрибутом idle может опционально добавить в модуль.
Эта функция используется как специальная задача ожидания и должна иметь сигнатуру
fn(idle::Context) - > !.
Если она присутствует, задача idle будет запущена после init. В отличие от
init, idle будет запущена с включенными прерываниями и она не может вернуть результат,
а значит должна работать вечно.
Если функция idle не определена, среда вполнения устанавливает бит SLEEPONEXIT, а затем
отправляет микроконтроллер в сон после запуска init.
Как и в init, static mut переменные будут трансформированы в &'static mut ссылки,
безопасные для доступа. Обратите внимание, данная возможность может
быть удалена в следующем релизе, см. task_local ресурсы.
Пример ниже показывает, что idle запускается после init.
Примечание: Цикл loop {} в функци ожидания не может быть пустым, так как это сломает
микроконтроллер, из-за того, что LLVM компилирует пустые циклы в инструкцию UDF в release mode.
Чтобы избежать неопределенного поведения, цикл должен включать "side-effect"
путем вставки ассемблерной инструкции (например, WFI) или ключевого слова continue.
{{#include ../../../../examples/idle.rs}}
$ cargo run --example idle
{{#include ../../../../ci/expected/idle.run}}
Аппаратные задачи
Чтобы объявить обработчик прерывания, фреймворк предоставляет атрибут #[task],
который можно применять к функциям. Этот атрибут берет аргумент binds, чье значение -
это имя прерывания, которому будет назначен обработчик;
функция, декорированная этим атрибутом становится обработчиком прерывания.
В фреймворке такие типы задач именуются аппаратными, потому что они начинают
выполняться в ответ на аппаратное событие.
Пример ниже демонстрирует использование атрибута #[task], чтобы объявить
обработчик прерывания. Как и в случае с #[init] и #[idle] локальные static mut переменные безопасны для использования с аппаратной задачей.
{{#include ../../../../examples/hardware.rs}}
$ cargo run --example hardware
{{#include ../../../../ci/expected/hardware.run}}
До сих пор все программы на RTIC, которые мы видели, не отличались от программ,
которые можно написать, используя лишь крейт cortex-m-rt. С этого момента мы
начинаем представлять возможности, уникальные для RTIC.
Приоритеты
Статический приоритет каждого обработчика можно оределить в атрибуте task, используя
аргумент priority. Задачи могут иметь приоритет в диапазоне 1..=(1 << NVIC_PRIO_BITS),
где NVIC_PRIO_BITS - это константа, определенная в крейте устройства.
Когда аргумент priority не указан, предполагается, что приоритет равен 1.
Задача idle имеет ненастраиваемый приоритет 0, наименьший из возможных.
Более высокое значение означает более высокий приоритет в RTIC, что противоположно тому, что указано в периферии NVIC Cortex-M. Точнее, это значит, что число
10обозначает приоритет выше, чем число9.
Когда несколько задач готовы к запуску, задача с самым большим статическим приоритетом будет запущена первой. Приоритезацию задач можно рассматривать по такому сценарию: сигнал прерывания приходит во время выполнения задачи с низким приоритетом; сигнал переключает задачу с высоким приоритетом в режим ожидания. Разница в приоритетах приводи к тому, что задача с высоким приоритетом вытесняет задачу с низким: выполнение задачи с низким приоритетом замораживается и задача с высоким приоритетом выполняется, пока не будет завершена. Как только задача с высоким приоритетом будет остановлена, продолжится выполнение задачи с низким приоритетом.
Следующий пример демонстрирует диспетчеризацию на основе приоритетов задач.
{{#include ../../../../examples/preempt.rs}}
$ cargo run --example preempt
{{#include ../../../../ci/expected/preempt.run}}
Заметьте, что задача gpiob не вытесняет задачу gpioc, потому что ее приоритет
такой же, как и у gpioc. Однако, как только gpioc возвращает результат,
выполненяется задача gpiob, как более приоритетная по сравнению с gpioa.
Выполнение gpioa возобновляется только после выхода из gpiob.
Еще одно замечание по поводу приоритетов: выбор приоритета большего, чем поддерживает устройство
(а именно 1 << NVIC_PRIO_BITS) приведет к ошибке компиляции.
Из-за ограничений языка, сообщение об ошибке далеко от понимания:
вам скажут что-то похожее на "evaluation of constant value failed", а указатель на ошибку
не покажет на проблемное значение прерывания --
мы извиняемся за это!