Embedded HAL
embedded-hal クレートは、組み込み Rust エコシステムの中核です。これは、I/O、SPI、I2C、PWM、タイマーなどのための共通のハードウェア抽象化トレイトの基盤を提供します。これらのトレイトは標準インターフェースを作り、高レベルのドライバー、たとえばセンサーや無線デバイス向けのドライバーを、異なるハードウェアプラットフォーム間で動作させられるようにします。
ドライバーは embedded-hal の上に構築されたジェネリックなライブラリとして書かれるため、Cortex-M や AVR マイクロコントローラーから組み込み Linux システムまで、幅広いターゲットをサポートできます。
例
クイックスタートの例では、embedded-hal のトレイトを使って micro:bit ボード上のピンとタイマーを制御しました。set_low と set_high 関数は OutputPin トレイト由来で、delay_ms 関数は DelayNs トレイト由来です。どちらも embedded-hal の一部です。
それでも、なぜトレイトを使わずに set_low と set_high 関数を直接書かないのか、と疑問に思うかもしれません。これを説明するために、LED をオンまたはオフにする単純な関数を 2 つのバージョンで考えてみましょう:
#![allow(unused)] fn main() { // 具体的なピン型の例(架空の MicrobitPin) struct MicrobitPin; impl MicrobitPin { fn set_low(&mut self) { // ピンを low に設定するハードウェア固有のコード } fn set_high(&mut self) { // ピンを high に設定するハードウェア固有のコード } } }
アプリケーション/ドライバーのコード:
#![allow(unused)] fn main() { fn control_led_concrete(pin: &mut MicrobitPin, light_up: bool) { if light_up { pin.set_low(); } else { pin.set_high(); } } }
これは LED を制御するためのアプリケーションコードです。この関数は MicrobitPin 型でしか動作しません。では、アプリケーションやドライバーを移植して、ほかのマイクロコントローラーもサポートしたい場合はどうでしょうか。その場合は、新しいクレートを書くか、それらを処理するための別個のロジックを追加しなければなりません。
#![allow(unused)] fn main() { fn control_led_another_mcu(pin: &mut AnotherMcuPin, light_up: bool) { if light_up { pin.set_low(); } else { pin.set_high(); } } }
では、これを embedded-hal のトレイトベースのアプローチと比較してみましょう:
#![allow(unused)] fn main() { use embedded_hal::digital::OutputPin; // OutputPin を実装する任意のピン型でこの関数は動作する fn control_led_generic<P: OutputPin>(pin: &mut P, light_up: bool) { if light_up { let _ = pin.set_low(); } else { let _ = pin.set_high(); } } }
OutputPin トレイトを使うことで、この関数はそのトレイトを実装する任意のハードウェアプラットフォームで動作します。これにより、ボードごとに書き直さなくても、コードを再利用可能かつポータブルにできます。
このトレイトベースのアプローチこそが、embedded-hal が組み込み Rust で非常に重要である理由です - 異なるハードウェア間で機能する共通インターフェースを提供するからです。