RustのRTFM (Real Time For the Masses)を試してみる⑤

はじめに

組込みRust界の伝説japaric氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。

github.com

サンプルコードをマクロ展開して心が折れそうです。 とりあえず、当面サンプルコードの表面をなぞります。

タスク

RTFMでは、割り込みと例外をハードウェアタスクとして扱います。 RTFMでは、ソフトウェアタスクも提供しています。

ソフトウェアタスクは、割り込みハンドラから、優先度を指定してディスパッチできます。 ソフトウェアタスクをディスパッチする割り込みハンドラは、externブロックの中で宣言しておく必要があります。

ソフトウェアタスクになる関数は、taskアトリビュートをつけます。 ソフトウェアタスクをspawnする関数は、contextアトリビュートspawn引数をつけます。

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

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

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

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

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

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

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

    // Interrupt handlers used to dispatch software tasks
    extern "C" {
        fn UART0();
        fn UART1();
    }
};

spawnの結果は、Resultで返ってきます。 これは、タスクが起動できる数 (capacity) が指定できるようになっており、その数以上のタスクを起動できないからです。 capacityはデフォルトでは、1になっています。

    #[interrupt(spawn = [foo, bar])]
    fn UART0() {
        spawn.foo(0).unwrap();
        spawn.foo(1).unwrap();
        spawn.foo(2).unwrap();
        spawn.foo(3).unwrap();

        spawn.bar().unwrap();
        // runtime error
        spawn.bar().unwrap();
    }

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

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

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

ソフトウェアタスクには、メッセージを渡すことができます。 メッセージは、タスクの引数として、シグネチャに含める必要があります。

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

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

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

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

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

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

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

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

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

fooのコードで、static mut COUNTを使用する際、デリファレンスしているのが気になりました。

    fn foo(c: foo::Context) {
        static mut COUNT: u32 = 0;
// ...
        c.spawn.bar(*COUNT).unwrap();
        *COUNT += 1;
    }

どうも、static mutを参照にマクロ展開しているようです。

次の手順でマクロ展開したコードを確認しました。

$ cargo rustc --example message -- -Z unstable-options --pretty=expanded > message_expanded.rs
# フォーマットしないとツラいです
$ rustfmt message_expanded.rs

fooは次のように展開されます。

#[allow(non_snake_case)]
fn foo(__locals: foo::Locals, c: foo::Context) {
    use rtfm::Mutex as _;
    let COUNT = __locals.COUNT;
    ::cortex_m_semihosting::export::hstdout_str("foo\n").unwrap();
    c.spawn.bar(*COUNT).unwrap();
    *COUNT += 1;
}

foo::Localsという型で引数を受け取っており、そこからCOUNTを取得しています。 foo::Localsは、次の通りです。

#[allow(non_snake_case)]
#[doc(hidden)]
pub struct fooLocals<'a> {
    COUNT: &'a mut u32,
    __marker__: core::marker::PhantomData<&'a mut ()>,
}

COUNTが参照になっていますね。

試しに、COUNTデリファレンスせずに使うと、次のコンパイルエラーになります。

error[E0308]: mismatched types
  --> examples/message.rs:25:21
   |
25 |         c.spawn.bar(COUNT).unwrap();
   |                     ^^^^^
   |                     |
   |                     expected u32, found &mut u32
   |                     help: consider dereferencing the borrow: `*COUNT`
   |
   = note: expected type `u32`
              found type `&mut u32`

いやー、知らないと意味がわからないですよ、これ。