rtic/book/ru/src/by-example/app.md
2021-08-03 22:40:33 +03:00

12 KiB
Raw Blame History

Атрибут 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", а указатель на ошибку не покажет на проблемное значение прерывания -- мы извиняемся за это!