RustのRTFM (Real Time For the Masses)を試してみる⑤
はじめに
組込みRust界の伝説japaric
氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。
サンプルコードをマクロ展開して心が折れそうです。 とりあえず、当面サンプルコードの表面をなぞります。
タスク
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`
いやー、知らないと意味がわからないですよ、これ。