ラズピコでRust(3) HALとPAC

  • 2023.09.22
  • HAL
ラズピコでRust(3) HALとPAC

皆さん こんにちは。

今回は、構築した環境のサンプルコードを簡単に説明した後 HAL と PAC についてお話します。

環境構築について知りたい方は こちらの記事 をご覧になってください。

それではさっそくソースコードを見ていきましょう。

main.rs

参考にしたソースコードは こちら です。

ここから設定やコードを少し省いてデバッグ環境で動かせるようにしたものがサンプルコードです。

Rustのクレートはライブラリと考えれば良く、rp-picoのクレートが こちら にあります。

その From Scratch のところに書かれているコード例にLチカする機能を追加したものがサンプルコードです。

つまり以下のコードをベースにコードを追加していけば良いことになります。

上の方にある use はクレートの構造体やトレイトを見えるようにするおまじないです。

#![no_std]
#![no_main]
use rp_pico::entry;
use panic_halt as _;
#[entry]
fn main() -> ! {
  loop {}
}

アトリビュート等については前回お話したので省略します。

それでは main() の中を見ていきます。

fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap(); // (1)
    let core = pac::CorePeripherals::take().unwrap(); // (2)
    let mut watchdog = Watchdog::new(pac.WATCHDOG); // (3)
    let sio = Sio::new(pac.SIO); // (4)

    // External high-speed crystal on the pico board is 12Mhz
    let external_xtal_freq_hz = 12_000_000u32; // (5)
    let clocks = init_clocks_and_plls( // (6)
        external_xtal_freq_hz,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); // (7)

    let pins = bsp::Pins::new( // (8)
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );
    let mut led_pin = pins.led.into_push_pull_output(); // (9)

    loop {
        led_pin.set_high().unwrap(); // (10)
        delay.delay_ms(300); // (11)
        led_pin.set_low().unwrap(); // (12)
        delay.delay_ms(50); // (13)
    }
}

まず(1)と(2)ですが、どちらも Peripherals という名前がついています。
(2)のCorePeripheralsの方ですが、これは別名を定義していて実はこれもPeripheralsという名前の構造体です。

(1)はPACのペリフェラルズの取得で、(2)はコアのペリフェラルズの取得になります。

(1)のPeripheralsをクリックして右クリックから Go to Definition を選ぶとそこに移動します。

rp2020-pac-0.4.0/src/lib.rs というファイルの1602行目に構造体が定義されています。

(2)のCorePeripheralsをクリックして右クリックから Go to Definition を選ぶとそこに移動します。

cortex-m-0.7.7/src/peripheral/mod.rs というファイルの97行目に構造体が定義されています。

どちらも同じ名前なので名前空間をつかって衝突を回避しています。

Peripherals::take()はOption型が返ります。

1回目は Some(Peripherals) が返るので unwrap() は Peripherals を取り出しますが、
2回目以降は None が返るように実装されているため panic() します。

panic()はプログラムの異常を安全に処理するための仕組みです。

このコードでは (1), (2) で panic() する可能性があります。
どちらの take() も Option型を返し None が返ってくる可能性があります。

そして None を unwrap() すると panic します。

そこで以下の use を使ってデフォルトのパニックハンドラの実装を無効にして停止するように指示しています。

use panic_halt as _;

take()はペリフェラルズを1か所でしか使えないようにするシングルトンというしくみです。

(3)はWatchdog構造体のインスタンスを生成しています。
Watchdogはウォッチドッグタイマーというペリフェラルのひとつです。
new()はC++で言うところのコンストラクタで、関連関数と呼ばれているものです。

関連関数は 構造体名::関連関数名() という形になります。

別にコンストラクタ(オブジェクトを生成する関数)の名前は(文法的に new にする必要はなく)何でも良いわけですが、意味を分かりやすくするために new() が良く使われます。
Watchdogのインスタンスは(6)の引数に必要なために生成しています。

(4)はSio構造体のインスタンスを生成しています。
SioはシングルサイクルIOブロックというGPIOとバスの仲介役の機能を持ちます。
Sioのインスタンスは(8)の引数として使うために生成しています。

(5)は外部水晶の周波数の定義(=12MHz)です。
(6)は(7)の引数として使うために生成しています。

(7)はDelay構造体のインスタンスを生成しています。
(8)はPins構造体のインスタンスを生成しています。

(9)はLEDを制御するピンを出力に設定しています。
(10)はLEDを制御するピンをHレベルに設定します。
(11)は300msec待ちます。
(12)はLEDを制御するピンをLレベルに設定します。
(13)は50msec待ちます。

HALとPAC

まず PAC は Peripheral Access Crate の略です。

組込みRustでは拡張子svdのファイルを用意すれば svd2rust により PAC に変換してくれます。

ソースコードでは次の1行でPACのペリフェラルズを取得しています。

let mut pac = pac::Peripherals::take().unwrap(); // (1)

pac は use bsp::hal::pac です。
hal は rp2040_hal です。
bsp は use rp_pico as bsp です。

たいていのものはマウスをそのキーワードに合わせると対象を表示してもらえます。(便利ですね)

pac::Peripherals::take().unwrap(); の Peripherals をマウスでクリック後、右クリックして Go to Definition するとその構造体定義に移動します。

私の環境では C:\Users\xxxxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\rp2040-pac-0.4.0\src\lib.rs

のファイルが開き、その 1602行目に pub struct Peripherals の定義がありました。

ディレクトリツリーの src の上の rp2040-pac-0.4.0 が PAC であることを示しています。

0.4.0 はPACのバージョンです。

PACは文字通りペリフェラルにアクセスする手段を提供するクレートになります。

直接ペリフェラルのレジスタをアクセスするイメージになります。

ビット操作をしたり値を書いたり、操作は単純ですが具体的に何をしているのかわかりにくいです。

レジスタに値を書く場合のイメージは以下の通りです。

pac.WATCHDOG.ctrl.write(|w| w.bits(…));

マイコンに密着している操作のために、抽象度が下がり次へ展開する場合に応用が利かない短所があります。
ただし細かい(かゆい)ところに手が届くという長所もあります。
それからHALにいろいろな機能がついている場合には遅くなるのでスピード重視ならPACによるアクセスの方に軍配が上がります。

続いてHALです。

HAL は Hardware Abstruct Layer の略です。

let mut watchdog = Watchdog::new(pac.WATCHDOG); // (3)

このコードを辿っていくと Watchdog は rp2040-hal::watchdog::Watchdog になります。

ラズピコの HALクレートは rp2040-hal ということになります。

この構造体のメソッド名から、どんなことをやるのか ある程度想像がつきます。

マイコンのレジスタと切り離して考えられるので抽象度が上がり次に展開しやすくなる長所があります。

ただしこのHALを私達ユーザーが変更することはできません。

そこで何か機能を追加したい場合には embedded_hal のトレイトを組み合わせて使います。

例えば Watchdog構造体を例に挙げると

構造体のメソッド:

new(), load_counter(), enable() などがあります。

embedded_halのトレイト:

feed(), start(), disable() などがあります。

おそらく構造体のメソッドは、そのマイコンにしか存在しない機能に対するものも含まれるのだと思いますが、トレイトのメソッドはその構造体に対してなくてはならないものが割り当てられます。

以上 HALとPACについて簡単に説明しました。

ついでに BSP についても軽く説明しておきます。

BSP は Board Support Package の略です。

use rp_pico as bsp にある通り、ラズピコのBSPクレートは rp_pico になります。

BSPはプリント基板上の機能に対するクレートになります。

例えば(私が他の記事を書いている)Wio Terminal は基板上にいろいろな機能がついているのでBSPクレートの内容も豊富ですが、ラズピコは小さな基板なのでGPIOのピンにLEDが割り当てられている程度になります。

いかがでしたか。
言葉だけではわかりにくいので、次回はウォッチドッグタイマーにトレイトを追加して具体的なところを見ていきます。

お疲れさまでした。

HALカテゴリの最新記事