Программные задачи

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

Программным задачам также можно назначать приоритет и диспетчеризовать из обработчиков прерываний. RTIC требует определения свободных прерываний в блоке extern, когда используются программные задачи; эти свободные прерывания будут использованы, чтобы диспетчеризовать программные задачи. Преимущество программных задач перед аппаратными в том, что на один обработчик прерывания можно назначить множество задач.

Программные задачи определяются заданием функциям атрибута task. Чтобы было возможно вызывать программные задачи, имя задачи нужно передать в аргументе spawn контекста атрибута (init, idle, interrupt, etc.).

В примере ниже продемонстрированы три программных задачи, запускаемые на 2-х разных приоритетах. Трем задачам назначены 2 обработчика прерываний.

#![allow(unused)]
fn main() {
//! examples/task.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;

#[rtic::app(device = lm3s6965)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init(c: init::Context) {
        c.spawn.foo().unwrap();
    }

    #[task(spawn = [bar, baz])]
    fn foo(c: foo::Context) {
        hprintln!("foo - start").unwrap();

        // spawns `bar` onto the task scheduler
        // `foo` and `bar` have the same priority so `bar` will not run until
        // after `foo` terminates
        c.spawn.bar().unwrap();

        hprintln!("foo - middle").unwrap();

        // spawns `baz` onto the task scheduler
        // `baz` has higher priority than `foo` so it immediately preempts `foo`
        c.spawn.baz().unwrap();

        hprintln!("foo - end").unwrap();
    }

    #[task]
    fn bar(_: bar::Context) {
        hprintln!("bar").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[task(priority = 2)]
    fn baz(_: baz::Context) {
        hprintln!("baz").unwrap();
    }

    // RTIC requires that unused interrupts are declared in an extern block when
    // using software tasks; these free interrupts will be used to dispatch the
    // software tasks.
    extern "C" {
        fn SSI0();
        fn QEI0();
    }
};
}
$ cargo run --example task
foo - start
foo - middle
baz
foo - end
bar```

## Передача сообщений

Другое преимущество программных задач - возможность передавать сообщения задачам
во время их вызова. Тип полезной нагрузки сообщения должен быть определен в
сигнатуре обработчика задачи.

Пример ниже демонстрирует три задачи, две из которых ожидают сообщения.

``` rust
//! examples/message.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;

#[rtic::app(device = lm3s6965)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init(c: init::Context) {
        c.spawn.foo(/* no message */).unwrap();
    }

    #[task(spawn = [bar])]
    fn foo(c: foo::Context) {
        static mut COUNT: u32 = 0;

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

        c.spawn.bar(*COUNT).unwrap();
        *COUNT += 1;
    }

    #[task(spawn = [baz])]
    fn bar(c: bar::Context, x: u32) {
        hprintln!("bar({})", x).unwrap();

        c.spawn.baz(x + 1, x + 2).unwrap();
    }

    #[task(spawn = [foo])]
    fn baz(c: baz::Context, x: u32, y: u32) {
        hprintln!("baz({}, {})", x, y).unwrap();

        if x + y > 4 {
            debug::exit(debug::EXIT_SUCCESS);
        }

        c.spawn.foo().unwrap();
    }

    // RTIC requires that unused interrupts are declared in an extern block when
    // using software tasks; these free interrupts will be used to dispatch the
    // software tasks.
    extern "C" {
        fn SSI0();
    }
};
$ cargo run --example message
foo
bar(0)
baz(1, 2)
foo
bar(1)
baz(2, 3)```

## Ёмкость

Диспетчеры задач *не* используют динамическое выделение памяти. Память
необходимая для размещения сообщений, резервируется статически. Фреймворк
зарезервирует достаточно памяти для каждого контекста, чтобы можно было вызвать
каждую задачу как минимум единожды. Это разумно по умолчанию, но
"внутреннюю" ёмкость каждой задачи можно контролировать используя аргумент
`capacity` атрибута `task`.

В примере ниже установлена ёмкость программной задачи `foo` на 4. Если ёмкость
не определена, тогда второй вызов `spawn.foo` в `UART0` вызовет ошибку.

``` rust
//! examples/capacity.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;

#[rtic::app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init(_: init::Context) {
        rtic::pend(Interrupt::UART0);
    }

    #[task(binds = UART0, spawn = [foo, bar])]
    fn uart0(c: uart0::Context) {
        c.spawn.foo(0).unwrap();
        c.spawn.foo(1).unwrap();
        c.spawn.foo(2).unwrap();
        c.spawn.foo(3).unwrap();

        c.spawn.bar().unwrap();
    }

    #[task(capacity = 4)]
    fn foo(_: foo::Context, x: u32) {
        hprintln!("foo({})", x).unwrap();
    }

    #[task]
    fn bar(_: bar::Context) {
        hprintln!("bar").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    // RTIC requires that unused interrupts are declared in an extern block when
    // using software tasks; these free interrupts will be used to dispatch the
    // software tasks.
    extern "C" {
        fn SSI0();
    }
};
$ cargo run --example capacity
foo(0)
foo(1)
foo(2)
foo(3)
bar```