Rust の組込み用非同期フレームワーク Embassy (1)

個人ブログをあまりに放置するのも良くないですね! 最近組込み Rust の情報発信もご無沙汰*1なので、自分のモチベーションのためにもちょいちょいメモ書きながら Embassy やっていこうと思います。

リポジトリはこちら。 github.com

Web ページもありますね。

embassy.dev

ということで今日は GitHub の README から。

README.md

はい、 Rust + async は組込みにて最強。そういうことがですね。書いてありますね。 仕組み上、素直に考えれば同じタスク上でステートマシン使って処理を切り替えるので、RTOSコンテキストスイッチより早いですよね、と。

Rust async と RTOS とを比較した記事へのリンクがあります。Rust の async の簡単な説明からありそうなので明日にでも読んでみましょう。

tweedegolf.nl

Batteries included

HAL を提供しているのは

リアルタイム処理用の機能も提供していて、priority の異なる複数の executor を作ることができる。 普通の cooperative な仕組みだとできないからね。

async executor はやることがなくと自動的にコアをスリープする。

Network / Bluetooth もサポートがある。なぜか LoRa も。 embassy-net は smoltcp を使っている。smol も見ないと、と思いつつずっと放置しているので見よう。 Bluetooth は nrf-softdevice と embassy-stm32-wpan とがある。 試してみるときは nrf-softdevice をこっそり試してみよう。

USB ドライバがあるのも良い話だ! ブートローダーがあるのは謎いな。なんでだ。

Sneak peek

ざっくりこういうコードになる、という話。 タイマーの初期化とかは embassy_nrf::init() でやってるのかな?

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

// Declare async tasks
#[embassy_executor::task]
async fn blink(pin: AnyPin) {
    let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);

    loop {
        // Timekeeping is globally available, no need to mess with hardware timers.
        led.set_high();
        Timer::after(Duration::from_millis(150)).await;
        led.set_low();
        Timer::after(Duration::from_millis(150)).await;
    }
}

// Main is itself an async task as well.
#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let p = embassy_nrf::init(Default::default());

    // Spawned tasks run in the background, concurrently.
    spawner.spawn(blink(p.P0_13.degrade())).unwrap();

    let mut button = Input::new(p.P0_11, Pull::Up);
    loop {
        // Asynchronously wait for GPIO events, allowing other tasks
        // to run, or the core to sleep.
        button.wait_for_low().await;
        info!("Button pressed!");
        button.wait_for_high().await;
        info!("Button released!");
    }
}

Timer::after()Future を実装した Timer オブジェクトを作ってるだけって感じか。なるほど。

    pub fn after(duration: Duration) -> Self {
        Self {
            expires_at: Instant::now() + duration,
            yielded_once: false,
        }
    }
impl Future for Timer {
    type Output = ();
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.yielded_once && self.expires_at <= Instant::now() {
            Poll::Ready(())
        } else {
            schedule_wake(self.expires_at, cx.waker());
            self.yielded_once = true;
            Poll::Pending
        }
    }
}

Why the name?

EMBedded ASYnc! :)

なるほど!

*1:半年前のインターフェース書いたっきり