Советы и хитрости

Обобщенное программирование (Generics)

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

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

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

extern crate panic_semihosting;

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use rtfm::{app, Mutex};

#[app(device = lm3s6965)]
const APP: () = {
    static mut SHARED: u32 = 0;

    #[init]
    fn init() {
        rtfm::pend(Interrupt::UART0);
        rtfm::pend(Interrupt::UART1);
    }

    #[interrupt(resources = [SHARED])]
    fn UART0() {
        static mut STATE: u32 = 0;

        hprintln!("UART0(STATE = {})", *STATE).unwrap();

        advance(STATE, resources.SHARED);

        rtfm::pend(Interrupt::UART1);

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

    #[interrupt(priority = 2, resources = [SHARED])]
    fn UART1() {
        static mut STATE: u32 = 0;

        hprintln!("UART1(STATE = {})", *STATE).unwrap();

        // just to show that `SHARED` can be accessed directly and ..
        *resources.SHARED += 0;
        // .. also through a (no-op) `lock`
        resources.SHARED.lock(|shared| *shared += 0);

        advance(STATE, resources.SHARED);
    }
};

fn advance(state: &mut u32, mut shared: impl Mutex<T = u32>) {
    *state += 1;

    let (old, new) = shared.lock(|shared| {
        let old = *shared;
        *shared += *state;
        (old, *shared)
    });

    hprintln!("SHARED: {} -> {}", old, new).unwrap();
}
}
$ cargo run --example generics
UART1(STATE = 0)
SHARED: 0 -> 1
UART0(STATE = 0)
SHARED: 1 -> 2
UART1(STATE = 1)
SHARED: 2 -> 4```

Это также позволяет Вам изменять статические приоритеты задач без
переписывания кода. Если Вы единообразно используете `lock`-и для доступа
к данным в разделяемых ресурсах, тогда Ваш код продолжит компилироваться,
когда Вы измените приоритет задач.

## Запуск задач из ОЗУ

Главной целью переноса описания программы на RTFM в атрибуты в
RTFM v0.4.x была возможность взаимодействия с другими атрибутами.
Напримерe, атрибут `link_section` можно применять к задачам, чтобы разместить
их в ОЗУ; это может улучшить производительность в некоторых случаях.

> **ВАЖНО**: Обычно атрибуты `link_section`, `export_name` и `no_mangle`
> очень мощные, но их легко использовать неправильно. Неверное использование
> любого из этих атрибутов может вызвать неопределенное поведение;
> Вам следует всегда предпочитать использование безопасных, высокоуровневых
> атрибутов вокруг них, таких как атрибуты `interrupt` и `exception`
> из `cortex-m-rt`.
>
> В особых случаях функций RAM нет безопасной абстракции в `cortex-m-rt`
> v0.6.5 но создано [RFC] для добавления атрибута `ramfunc` в будущем релизе.

[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100

В примере ниже показано как разместить высокоприоритетную задачу `bar` в ОЗУ.

``` rust
//! examples/ramfunc.rs

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

extern crate panic_semihosting;

use cortex_m_semihosting::{debug, hprintln};
use rtfm::app;

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

    #[inline(never)]
    #[task]
    fn foo() {
        hprintln!("foo").unwrap();

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

    // run this task from RAM
    #[inline(never)]
    #[link_section = ".data.bar"]
    #[task(priority = 2, spawn = [foo])]
    fn bar() {
        spawn.foo().unwrap();
    }

    extern "C" {
        fn UART0();

        // run the task dispatcher from RAM
        #[link_section = ".data.UART1"]
        fn UART1();
    }
};

Запуск этой программы произведет ожидаемый вывод.

$ cargo run --example ramfunc
foo```

Можно посмотреть на вывод `cargo-nm`, чтобы убедиться, что `bar` расположен в ОЗУ
(`0x2000_0000`), тогда как `foo` расположен во Flash (`0x0000_0000`).

``` console
$ cargo nm --example ramfunc --release | grep ' foo::'
20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20
200000dc B foo::INPUTS::thvubs85b91dg365
000002c6 T foo::sidaht420cg1mcm8```

``` console
$ cargo nm --example ramfunc --release | grep ' bar::'
20000100 B bar::FREE_QUEUE::lk14244m263eivix
200000dc B bar::INPUTS::mi89534s44r1mnj1
20000000 T bar::ns9009yhw2dc2y25```

## `binds`

**ПРИМЕЧАНИЕ**: Требуется RTFM не ниже 0.4.2

Вы можете давать аппаратным задачам имена похожие на имена обычных задач.
Для этого нужно использовать аргумент `binds`: Вы называете функцию
по своему желанию и назначаете ей прерывание / исключение
через аргумент `binds`. `Spawn` и другие служебные типы будут размещены в модуле,
названном в соответствии с названием функции, а не прерывания / исключения.
Давайте посмотрим пример:

``` rust
//! examples/binds.rs

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

extern crate panic_semihosting;

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use rtfm::app;

// `examples/interrupt.rs` rewritten to use `binds`
#[app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init() {
        rtfm::pend(Interrupt::UART0);

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

    #[idle]
    fn idle() -> ! {
        hprintln!("idle").unwrap();

        rtfm::pend(Interrupt::UART0);

        debug::exit(debug::EXIT_SUCCESS);

        loop {}
    }

    #[interrupt(binds = UART0)]
    fn foo() {
        static mut TIMES: u32 = 0;

        *TIMES += 1;

        hprintln!(
            "foo called {} time{}",
            *TIMES,
            if *TIMES > 1 { "s" } else { "" }
        )
        .unwrap();
    }
};
$ cargo run --example binds
init
foo called 1 time
idle
foo called 2 times```