😽

Rust で Raspberry Pi Pico 2 W で L チカする

に公開

とりあえず状況を確認したということで、L チカのサンプルコードでも動かしてみるか~、と思ったら意外と大変でした。

https://zenn.dev/yutannihilation/articles/56a8c08e2f649b

tl;dr

Rust で L チカして満足したいだけなら「W」じゃないやつを買いましょう。

Raspberry Pi Pico 2 W とは

Raspberry Pi Pico 2 に無線モジュール(CYW43439)がついたバージョンです。

https://www.switch-science.com/products/10053

Raspberry Pi Pico 2 と Raspberry Pi Pico 2 W の L チカの違い

まず衝撃の事実ですが、Raspberry Pi Pico シリーズを扱うための定番 crate・rp-hal では、Pico W や Pico 2W の L チカはできません。

https://github.com/rp-rs/rp-hal/issues/525

誤解を避けるために言っておくと、

  • C/C++ で Raspberry Pi Pico 2 W で L チカする
  • Rust で Raspberry Pi Pico 2 で L チカする

のなら簡単です。これが、「Rust」で「W」だと難しくなります(今回は Pico 2 W を使っていますが、Pico W でもおそらく同じです)。なぜかというと、「W」の LED は RP2350 の GPIO ではなく CYW43439 についているからです。公式ウェブサイトに載っているピン表を並べるとこうなっています。左が Pico 2 W、右が Pico 2 です。


(引用元:Pico 2 W のピンPico 2 のピン

これが C/C++ であれば、このあたりが実際どういう風になっているかはライブラリが隠ぺいしてくれていて、関数をひとつ呼び出すだけで済みます。

https://github.com/raspberrypi/pico-examples/blob/84e8d489ca321a4be90ee49e36dc29e5c645da08/blink/blink.c#L39-L40

ところが、Rust だとそういうのがないので、もうちょっと低レベルの処理を書くことになります。具体的に言えば、RP2350 は CYW43439 を SPI 通信で制御していて、その処理を書きます。

この処理を rp-hal でできない理由はちゃんと理解できていないのですが、ちゃんとモジュールを制御するには async が必要になり、rp-hal には async のサポートはないので難しい、という感じみたいです。

Rust から CYW43439 を使う

async ということは、Embassy か RTIC ですが、CYW43439 を制御する crate はおそらく以下しかなく、これが Embassy が開発しているものなので Embassy 一択っぽいです。

https://crates.io/crates/cyw43

上に書いたように、RP2350 と CYW43439 は SPI で通信します。SPI にはピンが4本必要ですが、回路図によれば、GPIO の 23、24、25、29 がつながっているようです。

で、さらに、これを PIO で制御するようになっているらしく、PIO0 も使います。コードとしてはこんな感じです。Embassy のレポジトリにあるコード例を少し変えたものです。

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let p = embassy_rp::init(Default::default());

    // わかりやすくするため変数に入れる
    let pin_wl_on = p.PIN_23;
    let pin_wl_d = p.PIN_24;
    let pin_wl_cs = p.PIN_25;
    let pin_wl_clk = p.PIN_29;

    // 出力用のピンの設定
    let outpin_wl_cs = Output::new(pin_wl_cs, Level::High);
    let outpin_wl_pwr = Output::new(pin_wl_on, Level::Low);

    // PIO の設定(Irqs は別の場所で定義している)
    let mut pio = Pio::new(p.PIO0, Irqs);

    // SPI の設定
    let spi = PioSpi::new(
        &mut pio.common,
        pio.sm0,
        DEFAULT_CLOCK_DIVIDER,
        pio.irq0,
        outpin_wl_cs,
        pin_wl_d,
        pin_wl_clk,
        p.DMA_CH0,
    );

    // 無線モジュールの状態
    static STATE: StaticCell<cyw43::State> = StaticCell::new();
    let state = STATE.init(cyw43::State::new());

    let fw = include_bytes!("../cyw43-firmware/43439A0.bin");
    let (_net_device, mut control, runner) = cyw43::new(state, outpin_wl_pwr, spi, fw).await;

    // 処理を捌くやつ?(cyw43_task は別の場所で定義している)
    unwrap!(spawner.spawn(cyw43_task(runner)));

    let clm = include_bytes!("../cyw43-firmware/43439A0_clm.bin");
    control.init(clm).await;

    ...
}

これで、control を介して CYW43439 を制御できるようになります。例えば、Lチカしたいのであれば、CYW43439 の GPIO ピン0を HIGH にしたり LOW にしたりします。

    loop {
        info!("led on!");
        control.gpio_set(0, true).await;
        Timer::after(delay).await;

        info!("led off!");
        control.gpio_set(0, false).await;
        Timer::after(delay).await;
    }

また、こうした低レベルの処理だけでなく、Wifi 関連の操作もできるみたいです。他のコード例を見れば使い方が分かると思うんですが、まだ見れていません。

https://docs.embassy.dev/cyw43/git/default/struct.Control.html

つまづいた点

Embassy のレポジトリにあるコード例を写経して実行してみると、以下のエラーで動きませんでした。

task arena is full

これは、何らかのメモリ領域?の大きさを自分で指定する必要があるみたいなんですが、レポジトリにある開発版のバージョンだと自動でいい感じになるようになってました。ということで、crates.io 版ではなく開発版を使うようにしたところ動きました。

https://github.com/embassy-rs/embassy/pull/4020

感想

なんかムダに大変でしたが、まあ Embassy を触るきっかけになってよかったです。

Embassy のレポジトリにあるコード例とほとんど同じですが、L チカのコードは以下に置きました。

https://github.com/yutannihilation/rp2350-blinky

Discussion