Rust製組込みOSのTockを触ってみる②

はじめに

諸事情によりまとまった時間を取るのが難しいので、入門系やります。

今回は、Rust製のRTOSであるTockを触ってみます。 githubのTock organizationはこちらです。

前回はC言語で書かれたサンプルアプリケーションを動かしたので、今回はRustで書かれたサンプルアプリケーションを動かしてみます。

libtock-rs

Rustでアプリケーションを書くためのライブラリレポジトリは、libtock-rsです。 examplesは、nRF52-DKでなら動作するようです。 nRF52840-DKとは別物ですが、まぁ動くでしょう。やってみましょう。

rustup target add thumbv7em-none-eabi
git clone https://github.com/tock/libtock-rs.git
cd libtock-rs
./run_example.sh blink

無事書き込めて、動いています。

+ elf_file_name=target/tab/blink/cortex-m4.elf
+ tab_file_name=target/tab/blink.tab
+ tockloader_flags='--jlink --arch cortex-m4 --board nrf52dk --jtag-device nrf52 --app-address 0x20000'
+ hail_defined=
+ '[' -n '' ']'
+ mkdir -p target/tab/blink
+ cp target/thumbv7em-none-eabi/release/examples/blink target/tab/blink/cortex-m4.elf
+ elf2tab -n blink -o target/tab/blink.tab target/tab/blink/cortex-m4.elf --stack 2048 --app-heap 1024 --kernel-heap 1024 --protected-region-size=64
+ '[' 1 -ge 2 ']'
+ tockloader uninstall --jlink --arch cortex-m4 --board nrf52dk --jtag-device nrf52 --app-address 0x20000
Preparing to uninstall apps...
Using known arch and jtag-device for known board nrf52dk
No apps are installed on the board

+ true
+ tockloader install --jlink --arch cortex-m4 --board nrf52dk --jtag-device nrf52 --app-address 0x20000 target/tab/blink.tab
Installing apps on the board...
Using known arch and jtag-device for known board nrf52dk
Finished in 1.961 seconds

なにやら色々やっていますね。 事前にアプリケーションがインストールされていると、アンインストールするみたいです。

examplesチラ見

BLEのサンプルがあって気になります。

|> exmaples/ble_scanning.rs

#![no_std]

use libtock::ble_parser;
use libtock::led;
use libtock::simple_ble;
use libtock::simple_ble::BleCallback;
use libtock::simple_ble::BleDriver;
use libtock::syscalls;
use serde::Deserialize;

#[derive(Deserialize)]
struct LedCommand {
    pub nr: u8,
    pub st: bool,
}

// Prevents the compiler from dropping the subscription too early.
#[allow(unreachable_code)]
fn main() {
    let mut shared_buffer = BleDriver::create_scan_buffer();
    let mut my_buffer = BleDriver::create_scan_buffer();
    let shared_memory = BleDriver::share_memory(&mut shared_buffer).unwrap();

    let mut callback = BleCallback::new(|_: usize, _: usize| {
        shared_memory.read_bytes(&mut my_buffer[..]);
        ble_parser::find(&my_buffer, simple_ble::gap_data::SERVICE_DATA as u8)
            .and_then(|service_data| ble_parser::extract_for_service([91, 79], service_data))
            .and_then(|payload| corepack::from_bytes::<LedCommand>(&payload).ok())
            .and_then(|msg| led::get(msg.nr as isize).map(|led| led.set_state(msg.st)));
    });

    let _subscription = BleDriver::start(&mut callback).unwrap();

    loop {
        syscalls::yieldk();
    }
}

あー、serdeビルドしているの、なぜだろうと思ってましたが、ここで使っていたのですね…。 割とビルド時間長いのは、気になりますね。

BLE Advertising driverもkernel側に実装されています。 libtock-rsにBleAdvertisingDriverの実装がありますが、基本はシステムコールを呼ぶだけで、実態はkernel側のようですね。

おもむろにDRIVER_NUMBERが使用されていますが、これはkernel側のシステムコールで定義されています。

|> libtock-rs/src/simple_ble.rs

const DRIVER_NUMBER: usize = 0x30000;
pub const MAX_PAYLOAD_SIZE: usize = 9;
pub const BUFFER_SIZE_ADVERTISE: usize = 39;
pub const BUFFER_SIZE_SCAN: usize = 39;

mod ble_commands {
    pub const START_ADVERTISING: usize = 0;
    pub const ALLOW_ADVERTISMENT_BUFFER: usize = 0;
    pub const BLE_PASSIVE_SCAN_SUB: usize = 0;
    pub const ALLOW_SCAN_BUFFER: usize = 1;
    pub const PASSIVE_SCAN: usize = 5;
}

|> tock/doc/syscalls/README.md

Radio

1.0 Driver Number Driver Description
0x30000 BLE Bluetooth Low Energy
0x30001 802.15.4 IEEE 802.15.4
0x30002 UDP UDP / 6LoWPAN Interface

なるほどね〜。

Rust製組込みOSのTockを触ってみる①

はじめに

諸事情によりまとまった時間を取るのが難しいので、入門系やります。

今回は、Rust製のRTOSであるTockを触ってみます。 githubのTock organizationはこちらです。

Getting started

Getting startedを参考に、何か動かしてみます。

Requirements

  • nightly Rust (開発グループはnightly-2019-04-11を使用)
  • make
  • tockloader

Rustとmakeはインストールされているので、tockloaderをインストールします。

pip3 install --upgrade tockloader --user

ttyUSBを管理者権限なしで使えるようにします。

grep -q dialout <(groups $(whoami)) || sudo usermod -a -G dialout $(whoami)

Compile kernel

git clone https://github.com/tock/tock.git
cd tock/

kernelをビルドする前に、まずボードを選びます。 手元にnRF52840-DKがあるので、それにします。

kernelをビルドして、ボードに書き込みます。

cd boards/nordic/nrf52840dk/
make flash

JLinkで何やら書き込んでいるようです。

    Finished release [optimized + debuginfo] target(s) in 2.36s
   text    data     bss     dec     hex filename
  71676    1940  260204  333820   517fc target/thumbv7em-none-eabi/release/nrf52840dk
tockloader  flash --address 0x00000 --jlink --board nrf52dk target/thumbv7em-none-eabi/release/nrf52840dk.bin
Flashing binar(y|ies) to board...
Using known arch and jtag-device for known board nrf52dk
Finished in 5.365 seconds

build example application

C言語のユーザーアプリケーションを実行してみましょう。

git clone https://github.com/tock/libtock-c.git
cd libtock-c/
cd examples/blink/
make
 DIR        ../../libtock/build/cortex-m0
  CC        ../../libtock/internal/alarm_internal.c
/bin/sh: 1: arm-none-eabi-gcc: not found
../../TockLibrary.mk:149: recipe for target '../../libtock/build/cortex-m0/alarm_internal.o' failed
make: *** [../../libtock/build/cortex-m0/alarm_internal.o] Error 127

む?C言語のアプリケーション実行には、GCCが必要みたいです。 (そういえばPC新しくしてからインストールしてなかったな)

sudo apt install gcc-arm-none-eabi

改めて、今度はビルドが通ります。

make

アプリケーションを書き込みます。

tockloader install --jlink --board nrf52dk

無事、Lチカできました!

参考

Rust製組込みOS TockでC言語アプリケーションを動かす

Rustコンパイラ組込みのlintについて調べる

はじめに

Rustコンパイラであるrustcには組込みのlintがあります。 Rustのlintツールでは、clippyが有名ですが、rustc組込みの方もソースコードの改善に役立ちます。

7月の技術書同人誌博覧会のネタ探しの一環だったのですが、思ったより世の中の役に立ちそうだったので、ドラフトを公開します。 原稿落とさなければ、「組込み / ベアメタルRustテクニック集」的な薄い本を出す予定です。

ちなみにここに書いている内容は、全てThe rustc bookに書いてあります。

lint

lintソースコードコンパイラより厳密なルールに則り、検査するためのツールです。 Rustコンパイラには、様々なlintルールが組み込まれています。 ソースコードコンパイルする時、自動的にlintによる検査が行われます。

プロジェクトの運用ルールに合わせて、適切なlintルールを設定することで、ソースコードの品質をより向上できるでしょう。

lintレベル

rustcのlintレベルは、4つに分類されます。

  1. allow (許可)
  2. warn (警告)
  3. deny (拒絶)
  4. forbid (禁止)

各lintルールには、デフォルトのlintレベルがあり、コンパイルオプションかアトリビュートで上書きできるようになっています。 まず、lintレベルについて説明します。

allow (許可)

lintルールを適用しません。 例えば、次のコードをコンパイルしても、警告は発生しません。

pub fn foo() {}
$ rustc lib.rs --crate-type=lib

しかし、このコードはmissing_docsルールを違反しています。 lintレベルを上書きしてコンパイルすると、コンパイルエラーになったり、警告が出力されるようになります。

warn (警告)

lintルール違反があった場合、警告を表示します。

fn main() {
    let x = 5;
}

このコードは次の警告が報告されます。

warning: unused variable: `x`
 --> src/main.rs:2:9
  |
2 |     let x = 5;
  |         ^ help: consider prefixing with an underscore: `_x`
  |
  = note: #[warn(unused_variables)] on by default

deny (拒絶)

lintルール違反があった場合、コンパイルエラーになります。

fn main() {
    100u8 << 10;
}

このコードは、exceeding_bitshiftsルールに違反しており、コンパイルエラーになります。

error: attempt to shift left with overflow
 --> src/main.rs:2:5
  |
2 |     100u8 << 10;
  |     ^^^^^^^^^^^
  |
  = note: #[deny(exceeding_bitshifts)] on by default

forbid (禁止)

lintルール違反があった場合、コンパイルエラーになります。 forbidは、denyより強いレベルで、上書きができません。

下のコードは、アトリビュートmissing_docsルールをallowに上書きしています。

#![allow(missing_docs)]
pub fn foo() {}

missing_dogsルールを、denyレベルに設定してコンパイルすると、このコードはコンパイルできます。

$ rustc lib.rs --crate-type=lib -D missing-docs

一方、forbidレベルに設定してコンパイルすると、コンパイルエラーになります。

$ rustc lib.rs --crate-type=lib -F missing-docs
error[E0453]: allow(missing_docs) overruled by outer forbid(missing_docs)
 --> lib.rs:1:10
  |
1 | #![allow(missing_docs)]
  |          ^^^^^^^^^^^^ overruled by previous forbid
  |
  = note: `forbid` lint level was set on command line

lintレベルの設定方法

コンパイラフラグで設定

コンパイルオプションで、-A, -W, -D, -Fのいずれかを指定して、lintレベルを設定できます。

$ rustc lib.rs --crate-type=lib -W missing-docs

もちろん、複数のフラグを同時に設定することも可能です。

$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables

Cargoの設定ファイル内で、lintレベルを設定することも可能です。

$ cat .cargo/config
[build]
rustflags = ["-D", "unsafe-code"]

アトリビュートで設定

ソースコード内のアトリビュートで、allow, warn, deny, forbidのいずれかを指定して、lintレベルを設定できます。

$ cat lib.rs
#![warn(missing_docs)]

pub fn foo() {}

1つのアトリビュートに、複数のlintルールを指定できます。

#![warn(missing_docs, unused_variables)]

fn main() {
pub fn foo() {}
}

複数のアトリビュートを組み合わせて使うこともできます。

#![warn(missing_docs)]
#![deny(unused_variables)]

pub fn foo() {}

lintルール

次のコマンドでlintルールと、デフォルトレベルの一覧が取得できます。

$ rustc -W help

デフォルトレベルごとに、サンプルコード付きでlintルールが説明されています。

知られざるlintルールたち

ちらっと目について、へー、と思ったルールを独断と偏見で紹介します。

ちょっとマニアック

自分が知らんかった枠

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`

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

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

はじめに

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

github.com

面白いので、引き続き触っていきます。

リソース管理続き

RTFMstatic変数は、通常のstatic変数と異なり、実行時に初期化できます。 このように実行時に初期化するリソースをlate resourcesと呼んでいます。 補足すると、lazy_staticとは、また異なる仕組みのようです。Optionで包んで云々、ということはやっていません。 リソースの初期化は、init関数で行う必要があり、init関数は、初期化したリソースを戻り値にしなければなりません。

何を言っているのかわからないと思うので、サンプルコードを見てみましょう。

use heapless::{
    consts::*,
    spsc::{Consumer, Producer, Queue},
};

#[app(device = lm3s6965)]
const APP: () = {
    // Late resources `()`を初期値にします
    static mut P: Producer<'static, u32, U4> = ();
    static mut C: Consumer<'static, u32, U4> = ();

    #[init]
    fn init() -> init::LateResources {
        // NOTE: we use `Option` here to work around the lack of
        // a stable `const` constructor
        static mut Q: Option<Queue<u32, U4>> = None;

        *Q = Some(Queue::new());
        let (p, c) = Q.as_mut().unwrap().split();

        // late resourcesを初期化して、関数の戻り値とします
        init::LateResources { P: p, C: c }
    }

    #[idle(resources = [C])]
    fn idle() -> ! {
        loop {
            if let Some(byte) = resources.C.dequeue() {
                hprintln!("received message: {}", byte).unwrap();

                debug::exit(debug::EXIT_SUCCESS);
            } else {
                rtfm::pend(Interrupt::UART0);
            }
        }
    }

    #[interrupt(resources = [P])]
    fn UART0() {
        resources.P.enqueue(42).unwrap();
    }
};

まず、()で初期化されたlate resourcesを宣言します。

    static mut P: Producer<'static, u32, U4> = ();
    static mut C: Consumer<'static, u32, U4> = ();

次に、init関数のシグネチャが、init::LateResourcesを返すものに変わります。 このinit::LateResourcesは、late resourcesPCとのタプルです。

    #[init]
    fn init() -> init::LateResources {

idleはC、UART0はPを、それぞれ専有して利用するため、ロックなしでリソースを利用することができます

このサンプルでは、`heaphessクレートのqueueが使われています。 このheaplessクレートは、文字通り、ヒープがない環境でコレクションを使うためのクレートです。 queueは、single producer / single consumerのモデルになっています。 このqueueの初期化をinitで行い、ProducerとConsumerのオブジェクトを共有リソースとして管理することで、うまいこと安全な機構になっているように見えます。

リソースがただのstaticで、可変参照にならない場合も、ロックなしでリソースにアクセスできます。 これは、late resourcesにも適用されます。

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

    #[init]
    fn init() -> init::LateResources {
        rtfm::pend(Interrupt::UART0);
        rtfm::pend(Interrupt::UART1);

        init::LateResources { KEY: 0xdeadbeef }
    }

    #[interrupt(resources = [KEY])]
    fn UART0() {
        hprintln!("UART0(KEY = {:#x})", resources.KEY).unwrap();

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

    #[interrupt(priority = 2, resources = [KEY])]
    fn UART1() {
        hprintln!("UART1(KEY = {:#x})", resources.KEY).unwrap();
    }
};

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

はじめに

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

github.com

軽い気持ちで触り始めたのですが、読み進めるうちに、手続きマクロでゴリゴリ静的解析している変態実装であることがわかってきました。 少しずつフレームワークの実装にも突っ込んでいきたいですね。

リソース管理

cortex-m-rtクレートを使用する場合、割り込みとメイン関数間でのデータ共有に制限があります。 常に、全ての割り込みを無効化するcortex_m::interrupt::Mutexを使わなければなりません。 しかし、全ての割り込みを無効化することは、常に求められる条件ではありません。

例えば、2つの割り込みハンドラがデータを共有する場合、両者の優先度が同じで、プリエンプションが発生しないとすると、ロックは不要です。 RTFMでは、ソースコードを静的に解析することで、不要なロックをせずに、共有データにアクセスできるようになっています。 このような解析が可能な理由は、appアトリビュート内にアプリケーションの実装を、全て書くためです。

また、RTFMでは、動的な割り込み優先度の変更をサポートしていません。 そのため、全ての割り込みハンドラ間の優先度は静的に決定します。 これがうまいこと生きており、ある共有データを使用する割り込みハンドラ同士で、最も優先度の高いハンドラはロックを取得しなくても共有データにアクセスできます。

そろそろサンプルコードを見てみましょう。 共有データは、static変数として宣言します。

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

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

    #[idle]
    fn idle() -> ! {
        debug::exit(debug::EXIT_SUCCESS);

        // error: `SHARED` can't be accessed from this context
        // SHARED += 1;

        loop {}
    }

    // `SHARED` can be access from this context
    #[interrupt(resources = [SHARED])]
    fn UART0() {
        *resources.SHARED += 1;

        hprintln!("UART0: SHARED = {}", resources.SHARED).unwrap();
    }

    // `SHARED` can be access from this context
    #[interrupt(resources = [SHARED])]
    fn UART1() {
        *resources.SHARED += 1;

        hprintln!("UART1: SHARED = {}", resources.SHARED).unwrap();
    }
};

共有データにアクセスする関数は、アトリビュート内でresources = [<変数名>]を引数にします。 ここで、2つの割り込みハンドラUART0UART1は共に共有データSHAREDを使用します。 2つの割り込みは、割り込み優先度が同じで、プリエンプションも発生しません。 そのため、2つの割り込みハンドラでは、Mutexなしで共有データにアクセスできます。

割り込み優先度を設定する場合、同じ共有データを使用する割り込みハンドラのうち、優先度が最も高いハンドラだけはロックが不要です。

//! examples/lock.rs

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

extern crate panic_semihosting;

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

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

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

    // when omitted priority is assumed to be `1`
    #[interrupt(resources = [SHARED])]
    fn GPIOA(mut c: GPIOA::Context) {
        hprintln!("A").unwrap();

        // the lower priority task requires a critical section to access the data
        c.resources.SHARED.lock(|shared| {
            // data can only be modified within this critical section (closure)
            *shared += 1;

            // GPIOB will *not* run right now due to the critical section
            rtfm::pend(Interrupt::GPIOB);

            hprintln!("B - SHARED = {}", *shared).unwrap();

            // GPIOC does not contend for `SHARED` so it's allowed to run now
            rtfm::pend(Interrupt::GPIOC);
        });

        // critical section is over: GPIOB can now start

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

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

    #[interrupt(priority = 2, resources = [SHARED])]
    fn GPIOB(mut c: GPIOB::Context) {
        // the higher priority task does *not* need a critical section
        *c.resources.SHARED += 1;

        hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap();
    }

    #[interrupt(priority = 3)]
    fn GPIOC(_: GPIOC::Context) {
        hprintln!("C").unwrap();
    }
};

ドキュメントによると、このプログラムの実行結果は、次のようになるようです。

$ cargo run --example lock
A
B - SHARED = 1
C
D - SHARED = 2
E

note: 手元の環境で実行すると、次の結果になります。要調査。レポジトリのCIは通っているようなので、手元環境の問題?

A D - SHARED = 2 B - SHARED = 2 C E

GPIOAの共有データアクセスは、クリティカルセクションのMutexを取得しており、割り込み禁止状態で行われます。

少し上記プログラムを改変して、どうなるか試してみます。

const APP: () = {
    static mut SHARED: u32 = 0;

// ...

    #[interrupt(resources = [SHARED])]
    fn GPIOA() {
        hprintln!("A").unwrap();

        // GPIOBと同じ方法でアクセスしてみます
        *c.resources.SHARED += 1;

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

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

    #[interrupt(priority = 2, resources = [SHARED])]
    fn GPIOB(mut c: GPIOB::Context) {
        *c.resources.SHARED += 1;

        hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap();
    }

// ...
};

ビルドすると次のコンパイルエラーになります。

$ cargo run --example lock
error[E0614]: type `resources::SHARED<'_>` cannot be dereferenced
  --> examples/lock.rs:41:9
   |
41 |         *c.resources.SHARED += 1;
   |         ^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0614`.
error: Could not compile `cortex-m-rtfm`.

To learn more, run the command again with --verbose.

どうも無名の型になっているようで、詳細がつかみにくいですが、GPIOA()GPIOB()とで、resource::SHARED`の型が違う、ということがわかります。

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

はじめに

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

github.com

前回変なところで切ってしまったため、今回は軽めです。

idleタスク

idleタスクは、initタスクの後に実行することができます。 initタスクと異なり、割り込みが有効化された状態で実行します。

関数シグネチャ[unsafe] fn() - > !であり、関数が戻ることは許されません。

下記のようにプログラムを書くことで、initの後にidleを実行します。

#[app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init() {
        hprintln!("init").unwrap();
    }

    #[idle]
    fn idle() -> ! {
        static mut X: u32 = 0;

        // Safe access to local `static mut` variable
        let _x: &'static mut u32 = X;

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

        debug::exit(debug::EXIT_SUCCESS);

        loop {}
    }
};

interrupt / exception

cortex-m-rtクレートを利用する時と同じ方法で、interruptexceptionを使うことができます。

#[app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init() {
        // Pends the UART0 interrupt but its handler won't run until *after*
        // `init` returns because interrupts are disabled
        rtfm::pend(Interrupt::UART0);

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

    #[idle]
    fn idle() -> ! {
        // interrupts are enabled again; the `UART0` handler runs at this point

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

        rtfm::pend(Interrupt::UART0);

        debug::exit(debug::EXIT_SUCCESS);

        loop {}
    }

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

        // Safe access to local `static mut` variable
        *TIMES += 1;

        hprintln!(
            "UART0 called {} time{}",
            *TIMES,
            if *TIMES > 1 { "s" } else { "" }
        )
        .unwrap();
    }
};

このプログラムを実行すると、2回割り込みをpendしているので、UART0が2回呼ばれます。

実行結果

$ cargo run --example interrupt
init
UART0 called 1 time
idle
UART0 called 2 times