5-2. no_stdクレート

no_stdや組込みで利用可能なクレートを紹介します。Rustのクレートが登録されているcrates.ioには、crates.io No standard libraryカテゴリがあります。2019/5/16現在、840ものクレートが登録されています。

組込みに関連するものは、awesome-embedded-rustに主要なものがまとめられています。

rt (runtime) クレート

rtクレートは、ターゲットアーキテクチャ用の最小限のスタートアップ / ランタイムを提供するクレートです。cortex-m-rtmsp430-rtriscv-rtの3つのターゲットアーキテクチャに対して実装が存在しています。

これらのクレートは、以下の機能を提供します。

  • .bss.dataセクションの初期化
  • FPUの初期化
  • プログラムのエントリポイントを指定するための#[entry]アトリビュート
  • static変数が初期化される前に呼ばれるコードを指定するための#[pre_init]アトリビュート
  • 一般的なターゲットアーキテクチャ用のリンカスクリプト
  • ヒープ領域の開始アドレスを表す_sheapシンボル

このクレートを使用することで、次のようにアプリケーションのmainコードからプログラムを記述することができます。

#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m_rt::entry;

// `main`をこのアプリケーションのエントリポイントであるかのように利用できます。
// `main`は戻れません
#[entry]
fn main() -> ! {
    // ここに処理を書きます
    loop { }
}

Embedonomiconは、このようなrtクレートの実装方法を解説しています。

embedded HAL

embedded HALは、組込みRustで共通して利用できるtraitを定義しているクレートです。例えば、SPIやシリアル、といったトレイトが定義されています。

このクレートの抽象を利用して、デバイスドライバを書くことで、アプリケーションの再利用性が向上します。組込みRustの多くのプロジェクトが、このembedded HALを利用しています。

その他

lazy_static

lazy_staticは、実行時にしか初期化できない (new()関数でのみオブジェクトが構築できる) ような、複雑なオブジェクトのstatic変数を作るために使います。通常、new()関数でオブジェクトを作るような構造体は、コンパイル時に値が計算できないため、static変数の初期化には使えません。また、lazy_staticは、static変数を1度だけ初期化する機能も提供します。lazy_staticマクロで作られたstatic変数は、その変数が実行時に最初に使用される時に、初期化されます。

例えば、Writing an OS in RustのVGA Text mode Lazy Staticsでは、VGAにテキストを描画するグローバルインタフェースWRITERの実装で使用しています。


#![allow(unused)]
fn main() {
use lazy_static::lazy_static;
use spin::Mutex;

lazy_static! {
    pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
        column_position: 0,
        color_code: ColorCode::new(Color::Yellow, Color::Black),
        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
    });
}
}

Mutex<Writer>という初期化が非常に複雑なオブジェクトの参照がstatic変数になっていることがわかります。このように実行時にしか構築できない値もstatic変数にできる上、どこで初期化するかに悩まなくて済みます。

bitflags

bitflagsは、型安全なビットマスクフラグを提供するクレートです。型安全であることがポイントで、誤ったビット操作を起こしにくいです。AndやOrのオペレータも実装されており、bits()メソッドで生の値を取り出すことができます。

#[macro_use]
extern crate bitflags;

bitflags! {
    struct Flags: u32 {
        const A = 0b00000001;
        const B = 0b00000010;
        const C = 0b00000100;
        const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
    }
}

fn main() {
    let e1 = Flags::A | Flags::C;
    let e2 = Flags::B | Flags::C;
    assert_eq!((e1 | e2), Flags::ABC);   // union
    assert_eq!((e1 & e2), Flags::C);     // intersection
    assert_eq!((e1 - e2), Flags::A);     // set difference
    assert_eq!(!e2, Flags::A);           // set complement
    assert_eq!(e1.bits(), 5u32);         // get raw value
}

bit_field

bit_fieldは、ビットフィールドへのアクセスを簡単にするためのクレートです。BitFieldトレイトを提供しており、i8, u8, usizeなどの整数型が、トレイトを実装しています。

ビットフィールドへのアクセスは、次のように書けます。

let mut value: u32 = 0b110101;

assert_eq!(value.get_bit(1), false);
assert_eq!(value.get_bit(2), true);
assert_eq!(value.get_bits(2..6), 0b1101);

value.set_bit(2, true);
assert_eq!(value, 0b110111);

value.set_bits(0..2, 0b00);
assert_eq!(value, 0b110100);

bitfield

bitfieldは、ビットフィールドを定義するマクロを提供するクレートです。bit_fieldとクレート名が似ていますが、別物です。

下のように、ビットフィールドを定義します。

bitfield! {
    #[derive(Clone, Copy, Debug)]
    pub struct PinSelect(u32);
    pub connected, set_connected: 31;
    reserved, _: 30, 6;
    pub port, set_port: 5;
    pub pin, set_pin: 4, 0;
};

fn main() {
    let mut reg = PinSelect(0);

    reg.set_pin(5);
    reg.set_port(0);
    reg.set_connected(1);
    assert_eq!(0x1000_0005, reg.all_bits());
}

micromath

micromathは、軽量な数値計算ライブラリです。三角関数などがあります。加速度計など、センサドライバでの計算に利用できます。

register-rs

register-rsは、Rust製のRTOSであるTockで利用されているMMIO / CPUレジスタインタフェースです。読み書き可能、読み込み専用、書き込み専用、を表現するジェネリック構造体を提供します。

volatile_register

volatile_registerは、メモリマップドレジスタへのvolatileアクセスを提供します。register-rsの簡易版、と言った印象です。

use volatile_register::RW;

// メモリマップドレジスタブロックを表現するstructを作ります
/// Nested Vector Interrupt Controller
#[repr(C)]
pub struct Nvic {
    /// Interrupt Set-Enable
    pub iser: [RW<u32>; 8],
    reserved0: [u32; 24],
    /// Interrupt Clear-Enable
    pub icer: [RW<u32>; 8],
    reserved1: [u32; 24],
    // .. more registers ..
}

// ベースアドレスをキャストしてアクセスします
let nvic = 0xE000_E100 as *const Nvic;
// unsafeブロックが必要です
unsafe { (*nvic).iser[0].write(1) }

embedded-graphics

embedded-graphicsは、2Dグラフィックを簡単に描画するためのクレートです。次の機能を提供します。

  • 1ビット / ピクセルの画像
  • 8ビット / ピクセルの画像
  • 16ビット / ピクセル画像
  • プリミティブ
    • 行、四角、丸、三角
  • テキスト

このクレートは、メモリアロケータも事前の巨大なメモリ領域確保も必要としません。