抽象化レイヤー
組み込み Rust に取り組むと、PAC、HAL、BSP といった用語をよく目にします。これらは、ハードウェアとやり取りするのを助ける異なるレイヤーです。各レイヤーは、柔軟性と使いやすさのバランスがそれぞれ異なります。
まずは、最も高い抽象化レベルから最も低いレベルへと見ていきましょう。
注: 本書全体を通して、各演習の要件に最も適したクレートを使用します。演習によっては Board Support Package (BSP) を使い、別の演習では Hardware Abstraction Layer (HAL) を直接使うことがあります。
Board Support Package (BSP)
Rust では Board Support Crate とも呼ばれる BSP は、特定の開発ボード向けに調整されたものです。これは HAL とボード固有の設定を組み合わせ、LED、ボタン、センサーなどのオンボードコンポーネントに対するすぐに使えるインターフェースを提供します。これにより、開発者は低レベルのハードウェア詳細を扱うのではなく、アプリケーションロジックに集中できます。micro:bit にも Board support crate があり、こちらから確認できます。
クイックスタートの章では、実際にこれを使いました。
BSP のコード例
// 1 行目の最初の LED を点灯する use cortex_m_rt::entry; use embedded_hal::digital::OutputPin; use microbit::board::Board; #[entry] fn main() -> ! { let mut board = Board::take().unwrap(); let _ = board.display_pins.col1.set_low(); let mut row1 = board.display_pins.row1; let _ = row1.set_high(); loop {} }
Hardware Abstraction Layer (HAL)
HAL は BSP のすぐ下のレベルに位置します。Raspberry Pi Pico や ESP32 ベースのボードのようなものを扱う場合、主に使うのは HAL レベルです。本書では、いくつかの BSP の例を見たあと、より HAL に重点を置いていきます。
HAL は PAC の上に構築されており、マイクロコントローラーの周辺機能に対して、より単純で高水準なインターフェースを提供します。低レベルのレジスタを直接扱う代わりに、HAL はタイマーの設定、シリアル通信のセットアップ、GPIO ピンの制御といった作業を簡単にするメソッドやトレイトを提供します。
HAL は通常、embedded-hal のトレイトを実装しています。これらは GPIO、SPI、I2C、UART のような周辺機能に対する標準的でプラットフォーム非依存のインターフェースです。これにより、互換性のある HAL を使う限り、異なるハードウェア間で動作するドライバーやライブラリを書きやすくなります。
後ほど、nrf52833-hal を見ていきます。ご覧のとおり、このクレートはもはや特定の開発ボード向けではなく、nRF52833 チップに結び付いています。そのため、別の開発ボードが同じチップを使っていれば、ほぼ同じコードを使えます。
HAL のコード例
// 1 行目の最初の LED を点灯する use cortex_m_rt::entry; use embedded_hal::digital::OutputPin; use nrf52833_hal::gpio::{p0, Level}; use nrf52833_hal::pac::Peripherals; #[entry] fn main() -> ! { let p = Peripherals::take().unwrap(); let port0 = p0::Parts::new(p.P0); let mut col1 = port0.p0_28.into_push_pull_output(Level::High); let mut row1 = port0.p0_21.into_push_pull_output(Level::Low); col1.set_low().unwrap(); row1.set_high().unwrap(); loop {} }
これを BSP のコードと比べると、BSP のコードのほうが読みやすいことがわかるでしょう。しかし、HAL レベルでは物事がより複雑になります。組み込みプログラミングや電子回路の背景知識がないと、こうした用語は奇妙に思えるかもしれません。心配はいりません。これらは後で順を追って説明していきます。
注記:
HAL より下のレイヤーを直接使うことはめったにありません。多くの場合、PAC は単体で使うのではなく、HAL を介してアクセスします。利用可能な HAL がないチップを扱っているのでなければ、通常は下位レイヤーを直接操作する必要はありません。本書では、BSP と HAL のレイヤーに焦点を当てます。
Peripheral Access Crate (PAC)
PAC は最も低いレベルの抽象化です。これは、マイクロコントローラーの周辺機能に型安全なアクセスを提供する自動生成されたクレートです。これらのクレートは通常、メーカーの SVD (System View Description) ファイルから、svd2rust のようなツールを使って生成されます。PAC は、ハードウェアレジスタを直接扱うための、構造化された安全な方法を提供します。
PAC のコード例
// 1 行目の最初の LED を点灯する use cortex_m_rt::entry; use nrf52833_pac::Peripherals; #[entry] fn main() -> ! { let p = Peripherals::take().unwrap(); let gpio0 = p.P0; gpio0.pin_cnf[21].write(|w| { w.dir().output(); w.input().disconnect(); w.pull().disabled(); w.drive().s0s1(); w.sense().disabled(); w }); gpio0.pin_cnf[28].write(|w| { w.dir().output(); w.input().disconnect(); w.pull().disabled(); w.drive().s0s1(); w.sense().disabled(); w }); gpio0.outclr.write(|w| w.pin28().clear()); gpio0.outset.write(|w| w.pin21().set()); loop {} }
Raw MMIO
Raw MMIO (memory-mapped IO) とは、特定のメモリアドレスを読み書きすることでハードウェアレジスタを直接扱うことを意味します。このアプローチは従来の C スタイルのレジスタ操作に相当し、伴う潜在的なリスクのため、Rust では unsafe ブロックの使用が必要です。この領域には触れません。このアプローチを使っている人は見たことがありませんし、仮に使っていたとしても、本書の範囲外です。
コード例
// 1 行目の最初の LED を点灯する #![no_main] #![no_std] extern crate panic_halt as _; use nrf52833_pac as _; use core::mem::size_of; use cortex_m_rt::entry; const GPIO_P0: usize = 0x5000_0000; const PIN_CNF: usize = 0x700; const OUTSET: usize = 0x508; const OUTCLR: usize = 0x50c; const DIR_OUTPUT: u32 = 0x1; const INPUT_DISCONNECT: u32 = 0x1 << 1; const PULL_DISABLED: u32 = 0x0 << 2; const DRIVE_S0S1: u32 = 0x0 << 8; const SENSE_DISABLED: u32 = 0x0 << 16; #[entry] fn main() -> ! { let pin_cnf_21 = (GPIO_P0 + PIN_CNF + 21 * size_of::<u32>()) as *mut u32; let pin_cnf_28 = (GPIO_P0 + PIN_CNF + 28 * size_of::<u32>()) as *mut u32; unsafe { pin_cnf_21.write_volatile( DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISABLED, ); pin_cnf_28.write_volatile( DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISABLED, ); } let gpio0_outset = (GPIO_P0 + OUTSET) as *mut u32; let gpio0_outclr = (GPIO_P0 + OUTCLR) as *mut u32; unsafe { gpio0_outclr.write_volatile(1 << 28); gpio0_outset.write_volatile(1 << 21); } loop {} }
参考
- PAC と Raw MMIO のコードスニペットは、Google の Comprehensive Rust book から引用しています