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

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

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


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

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

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

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    struct Resources {
        #[init(0)]
        shared: u32,
    }

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

    #[task(binds = UART0, resources = [shared])]
    fn uart0(c: uart0::Context) {
        static mut STATE: u32 = 0;

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

        // second argument has type `resources::shared`
        advance(STATE, c.resources.shared);

        rtfm::pend(Interrupt::UART1);

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

    #[task(binds = UART1, priority = 2, resources = [shared])]
    fn uart1(c: uart1::Context) {
        static mut STATE: u32 = 0;

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

        // just to show that `shared` can be accessed directly
        *c.resources.shared += 0;

        // second argument has type `Exclusive<u32>`
        advance(STATE, Exclusive(c.resources.shared));
    }
};

// the second parameter is generic: it can be any type that implements the `Mutex` trait
fn advance(state: &mut u32, mut shared: impl Mutex<T = u32>) {
    *state += 1;

    let (old, new) = shared.lock(|shared: &mut u32| {
        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 в будущем релизе.

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


# #![allow(unused_variables)]
#fn main() {
//! examples/ramfunc.rs

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

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

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

    #[inline(never)]
    #[task]
    fn foo(_: foo::Context) {
        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(c: bar::Context) {
        c.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).

$ cargo nm --example ramfunc --release | grep ' foo::'
00000162 t ramfunc::foo::h30e7789b08c08e19```

``` console
$ cargo nm --example ramfunc --release | grep ' bar::'
20000000 t ramfunc::bar::h9d6714fe5a3b0c89```

## `binds`

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

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

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

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

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

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

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

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

        rtfm::pend(Interrupt::UART0);

        debug::exit(debug::EXIT_SUCCESS);

        loop {}
    }

    #[task(binds = UART0)]
    fn foo(_: foo::Context) {
        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