Real-Time Interrupt-driven Concurrency — страница 3 из 23


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

Ресурсы

Фреймворк предоставляет абстракцию для разделения данных между любыми контекстами, с которыми мы встречались в предыдущей главе (задачами-обработчиками, init и idle): ресурсы.

Ресурсы - это данные, видимые только функциями, определенными внутри модуля #[app]. Фреймворк дает пользователю полный контроль за тем, какой контекст может получить доступ к какому ресурсу.

Все ресурсы определены в одной структуре внутри модуля #[app]. Каждое поле структуры соответствует отдельному ресурсу. struct-ура должна быть аннотирована следующим атрибутом: #[resources].

Ресурсам могут быть опционально даны начальные значения с помощью атрибута #[init]. Ресурсы, которым не передано начально значение, называются поздними ресурсами, более детально они описаны в одном из разделов на этой странице.

Каждый контекс (задача-обработчик, init или idle) должен указать ресурсы, к которым он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя аргумент resources. Этот аргумент принимает список имен ресурсов в качестве значения. Перечисленные ресурсы становятся доступны в контексте через поле resources структуры Context.

Пример программы, показанной ниже содержит два обработчика прерывания, которые разделяют доступ к ресурсу под названием shared.


#![allow(unused)]

fn main() {

//! examples/resource.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 {

local_to_uart0: i64,

local_to_uart1: i64,

}


#[init]

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

rtic::pend(Interrupt::UART0);

rtic::pend(Interrupt::UART1);


(

Shared {},

// initial values for the `#[local]` resources

Local {

local_to_uart0: 0,

local_to_uart1: 0,

},

init::Monotonics(),

)

}


// `#[local]` resources cannot be accessed from this context

#[idle]

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

debug::exit(debug::EXIT_SUCCESS);


// error: no `local` field in `idle::Context`

// _cx.local.local_to_uart0 += 1;


// error: no `local` field in `idle::Context`

// _cx.local.local_to_uart1 += 1;


loop {

cortex_m::asm::nop();

}

}


// `local_to_uart0` can only be accessed from this context

// defaults to priority 1

#[task(binds = UART0, local = [local_to_uart0])]

fn uart0(cx: uart0::Context) {

*cx.local.local_to_uart0 += 1;

let local_to_uart0 = cx.local.local_to_uart0;


// error: no `local_to_uart1` field in `uart0::LocalResources`

// cx.local.local_to_uart1 += 1;


hprintln!("UART0: local_to_uart0 = {}", local_to_uart0).unwrap();

}


// `shared` can only be accessed from this context

// explicitly set to priority 2

#[task(binds = UART1, local = [local_to_uart1], priority = 2)]

fn uart1(cx: uart1::Context) {

*cx.local.local_to_uart1 += 1;

let local_to_uart1 = cx.local.local_to_uart1;


// error: no `local_to_uart0` field in `uart1::LocalResources`

// cx.local.local_to_uart0 += 1;


hprintln!("UART1: local_to_uart1 = {}", local_to_uart1).unwrap();

}

}

}

$ cargo run --example resource

UART1: local_to_uart1 = 1

UART0: local_to_uart0 = 1

Заметьте, что к ресурсу shared нельзя получить доступ из idle. Попытка сделать это приведет к ошибке компиляции.

lock

Критические секции необходимы для разделения изменяемых данных таким образом, чтобы избежать гонок данных.