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

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

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


# #![allow(unused_variables)]
#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::Mutex;

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

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

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

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

        advance(STATE, c.resources.SHARED);

        rtfm::pend(Interrupt::UART1);

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

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

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

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

        advance(STATE, c.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 в будущем релизе.

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


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

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

extern crate panic_semihosting;

use cortex_m_semihosting::{debug, hprintln};

#[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::'
20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20
200000dc B foo::INPUTS::thvubs85b91dg365
000002c6 T foo::sidaht420cg1mcm8
$ 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 и другие служебные типы будут размещены в модуле, названном в соответствии с названием функции, а не прерывания / исключения. Давайте посмотрим пример:


# #![allow(unused_variables)]
#fn main() {
//! 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;

// `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 {}
    }

    #[interrupt(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