Rust の組込み用非同期フレームワーク Embassy (1)
個人ブログをあまりに放置するのも良くないですね! 最近組込み Rust の情報発信もご無沙汰*1なので、自分のモチベーションのためにもちょいちょいメモ書きながら Embassy やっていこうと思います。
リポジトリはこちら。 github.com
Web ページもありますね。
ということで今日は GitHub の README から。
README.md
はい、 Rust + async は組込みにて最強。そういうことがですね。書いてありますね。 仕組み上、素直に考えれば同じタスク上でステートマシン使って処理を切り替えるので、RTOS のコンテキストスイッチより早いですよね、と。
Rust async と RTOS とを比較した記事へのリンクがあります。Rust の async の簡単な説明からありそうなので明日にでも読んでみましょう。
Batteries included
HAL を提供しているのは
- STM32
- nRF
- Raspberry Pi RP2040
- ESP32 (esp-rs の方)
リアルタイム処理用の機能も提供していて、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:半年前のインターフェース書いたっきり