Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Embassy

ここまでは、ブロッキングモードで動作するコードを扱ってきました。これは、たとえばしばらく delay したり、ボタンが押されるのを待ったりするようプログラムに指示すると、その処理が終わるまで CPU が停止して待機し、その後で続行することを意味します。これは理解しやすく、小規模なプログラムではうまく機能しますが、センサーを読み取りながら入力も待ち受ける、といった複数のタスクを互いにブロックさせず同時に処理したい場合には制約になります。

そこで登場するのが Embassy です。Embassy は、組み込みシステム向けに設計された非同期ランタイムです。これにより、Rust の async/await 機能を使ってノンブロッキングなコードを書けるようになります。待機して CPU 時間を無駄にする代わりに、タスクは一時停止してほかのタスクに実行を譲れるため、プロセッサをより有効に活用でき、より応答性が高く省電力なアプリケーションを実現できます。

たとえば Embassy を使えば、複雑な割り込みベースのコードを手作業で書かなくても、LED を点滅させながらタッチ入力やボタン入力を同時に待ち受けることができます。

HAL

Embassy は、複数のマイクロコントローラファミリ向けに非同期対応の Hardware Abstraction Layer (HAL) を提供しており、安全で Rust らしい API を通じて、低レベルのレジスタを直接扱わずにハードウェアを操作できます。

公式 HAL には embassy-stm32 (STM32)、embassy-nrf (nRF52/53/54/91)、embassy-rp (RP2040)、embassy-mspm0 (TI MSPM0) があります。Embassy はさらに、esp-hal (ESP32)、ch32-hal (CH32V)、mpfs-hal (PolarFire)、py32-hal (Puya PY32) などのコミュニティ製 HAL とも連携できるため、多くのプラットフォームで移植性の高い非同期コードを簡単に書けます。

すぐに使える機能

Embassy には、組み込み開発を容易にする多くの組み込み機能が備わっています。たとえば、タイマーや遅延を扱う embassy-time、ネットワーク機能を提供する embassy-net、USB デバイス機能を構築するための embassy-usb など、さまざまな機能が含まれています。

Embassy を使ったコード例(公式サイトより)

use defmt::info;
use embassy_executor::Spawner;
use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull};
use embassy_nrf::Peripherals;
use embassy_time::{Duration, Timer};

// asyncタスクを宣言する
#[embassy_executor::task]
async fn blink(pin: AnyPin) {
    let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);

    loop {
        // 時間管理はグローバルに利用できるため、ハードウェアタイマーをいじる必要はありません。
        led.set_high();
        Timer::after_millis(150).await;
        led.set_low();
        Timer::after_millis(150).await;
    }
}

// Main 自体も async タスクです。
#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // embassy-nrf HAL を初期化する。
    let p = embassy_nrf::init(Default::default());

    // spawn されたタスクはバックグラウンドで並行して実行される。
    spawner.spawn(blink(p.P0_13.degrade())).unwrap();

    let mut button = Input::new(p.P0_11, Pull::Up);
    loop {
        // GPIO イベントを非同期に待機し、その間ほかのタスクを
        // 実行したり、コアをスリープさせたりできる。
        button.wait_for_low().await;
        info!("Button pressed!");
        button.wait_for_high().await;
        info!("Button released!");
    }
}

役立つリソース