Атрибут
app

Это простейшая из возможных программ на RTIC:


#![allow(unused)]

fn main() {

//! examples/smallest.rs


#![no_main]

#![no_std]


use panic_semihosting as _; // panic handler

use rtic::app;


#[app(device = lm3s6965)]

mod app {

#[shared]

struct Shared {}


#[local]

struct Local {}


#[init]

fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {

(Shared {}, Local {}, init::Monotonics())

}

}

}

Все программы на 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 будет запущена с отключенными прерываниями и будет иметь эксклюзивный доступ к 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.


#![allow(unused)]

fn main() {

//! 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;


// Device specific peripherals

let _device: lm3s6965::Peripherals = cx.device;


// Locals in `init` have 'static lifetime

let _x: &'static mut u32 = cx.local.x;


// Access to the critical section token,

// to indicate that this is a critical seciton

let _cs_token: bare_metal::CriticalSection = cx.cs;


hprintln!("init").unwrap();


debug::exit(debug::EXIT_SUCCESS);


(Shared {}, Local {}, init::Monotonics())

}

}

}

Запуск примера напечатате init в консоли, а затем завершит процесс QEMU.

$ cargo run --example init

init

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.


#![allow(unused)]

fn main() {

//! examples/idle.rs


#![deny(unsafe_code)]

#![deny(warnings)]

#![no_main]

#![no_std]


use panic_semihosting as _;


#[rtic::app(device = lm3s6965)]

mod app {

use cortex_m_semihosting::{debug, hprintln};


#[shared]

struct Shared {}


#[local]

struct Local {}


#[init]

fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {

hprintln!("init").unwrap();


(Shared {}, Local {}, init::Monotonics())

}


#[idle(local = [x: u32 = 0])]

fn idle(cx: idle::Context) -> ! {

// Locals in idle have lifetime 'static

let _x: &'static mut u32 = cx.local.x;


hprintln!("idle").unwrap();


debug::exit(debug::EXIT_SUCCESS);


loop {

cortex_m::asm::nop();

}

}

}

}

$ cargo run --example idle

init

idle

Аппаратные задачи

Чтобы объявить обработчик прерывания, фреймворк предоставляет атрибут #[task], который можно применять к функциям. Этот атрибут берет аргумент binds, чье значение - это имя прерывания, которому будет назначен обработчик; функция, декорированная этим атрибутом становится обработчиком прерывания. В фреймворке такие типы задач именуются аппаратными, потому что они начинают выполняться в ответ на аппаратное событие.

Пример ниже демонстрирует использование атрибута #[task], чтобы объявить обработчик прерывания. Как и в случае с #[init] и #[idle] локальные static mut переменные безопасны для использования с аппаратной задачей.


#![allow(unused)]

fn main() {

//! examples/hardware.rs


#![deny(unsafe_code)]

#![deny(warnings)]

#![no_main]

#![no_std]


use panic_semihosting as _;


#[rtic::app(device = lm3s6965)]

mod app {

use cortex_m_semihosting::{debug, hprintln};

use lm3s6965::Interrupt;


#[shared]

struct Shared {}


#[local]

struct Local {}


#[init]

fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {

// Pends the UART0 interrupt but its handler won't run until *after*

// `init` returns because interrupts are disabled

rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend


hprintln!("init").unwrap();


(Shared {}, Local {}, init::Monotonics())

}


#[idle]

fn idle(_: idle::Context) -> ! {

// interrupts are enabled again; the `UART0` handler runs at this point


hprintln!("idle").unwrap();


rtic::pend(Interrupt::UART0);


debug::exit(debug::EXIT_SUCCESS);


loop {

cortex_m::asm::nop();

}

}


#[task(binds = UART0, local = [times: u32 = 0])]

fn uart0(cx: uart0::Context) {

// Safe access to local `static mut` variable

*cx.local.times += 1;


hprintln!(

"UART0 called {} time{}",

*cx.local.times,

if *cx.local.times > 1 { "s" } else { "" }

)

.unwrap();

}

}

}

$ cargo run --example hardware

init

UART0 called 1 time

idle

UART0 called 2 times

До сих пор все программы на 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.

Когда несколько задач готовы к запуску, задача с самым большим статическим приоритетом будет запущена первой. Приоритезацию задач можно рассматривать по такому сценарию: сигнал прерывания приходит во время выполнения задачи с низким приоритетом; сигнал переключает задачу с высоким приоритетом в режим ожидания. Разница в приоритетах приводи к тому, что задача с высоким приоритетом вытесняет задачу с низким: выполнение задачи с низким приоритетом замораживается и задача с высоким приоритетом выполняется, пока не будет завершена. Как только задача с высоким приоритетом будет остановлена, продолжится выполнение задачи с низким приоритетом.

Следующий пример демонстрирует диспетчеризацию на основе приоритетов задач.


#![allow(unused)]

fn main() {

//! examples/preempt.rs


#![no_main]

#![no_std]


use panic_semihosting as _;

use rtic::app;


#[app(device = lm3s6965)]

mod app {

use cortex_m_semihosting::{debug, hprintln};

use lm3s6965::Interrupt;


#[shared]

struct Shared {}


#[local]

struct Local {}


#[init]

fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {

rtic::pend(Interrupt::GPIOA);


(Shared {}, Local {}, init::Monotonics())

}


#[task(binds = GPIOA, priority = 1)]

fn gpioa(_: gpioa::Context) {

hprintln!("GPIOA - start").unwrap();

rtic::pend(Interrupt::GPIOC);

hprintln!("GPIOA - end").unwrap();

debug::exit(debug::EXIT_SUCCESS);

}


#[task(binds = GPIOB, priority = 2)]

fn gpiob(_: gpiob::Context) {

hprintln!(" GPIOB").unwrap();

}


#[task(binds = GPIOC, priority = 2)]

fn gpioc(_: gpioc::Context) {

hprintln!(" GPIOC - start").unwrap();

rtic::pend(Interrupt::GPIOB);

hprintln!(" GPIOC - end").unwrap();

}

}

}

$ cargo run --example preempt

GPIOA - start

GPIOC - start

GPIOC - end

GPIOB

GPIOA - end

Заметьте, что задача gpiob не вытесняет задачу gpioc, потому что ее приоритет такой же, как и у gpioc. Однако, как только gpioc возвращает результат, выполненяется задача gpiob, как более приоритетная по сравнению с gpioa. Выполнение gpioa возобновляется только после выхода из gpiob.

Еще одно замечание по поводу приоритетов: выбор приоритета большего, чем поддерживает устройство (а именно 1 << NVIC_PRIO_BITS) приведет к ошибке компиляции. Из-за ограничений языка, сообщение об ошибке далеко от понимания: вам скажут что-то похожее на "evaluation of constant value failed", а указатель на ошибку не покажет на проблемное значение прерывания -- мы извиняемся за это!

Загрузка...