🎶

Rust✖️組み込みで非同期処理(async)をしてみた(Embassy)

に公開

はじめに

Rustの組み込み開発の非同期処理のデファクトスタンダードになりつつあるEmbassyを動かしてみました。また動かすハードウェアは、手軽にRust組み込みを試せる開発ボードであるBaker link. Devを利用しました。

非同期とは

非同期処理とは、1つの処理(関数など)の完了を待たずに、他の処理(関数など)を先に進める仕組みです。これにより、1つのコアで複数の処理を同時に実行することができます。たとえば、温度センサーのデータ取得中にLEDの点滅を続けるような場面で使われます。非同期処理はasyncawaitなどのキーワードを使って書かれ、効率的な処理を実現します。現代のソフトウェア開発では非常に重要な技術です。

Embassyとは

Embassyは、Rustで組み込み開発を行う際に、非同期処理を安全かつ効率的に書けるようにするフレームワークです。これを利用することで、複雑な同時実行処理をシンプルに記述できます。リアルタイム性を保ちつつ、Rustの型安全性やゼロコスト抽象化の恩恵を受けられるのが大きな特徴です。これにより、マイコン上でも信頼性の高い非同期アプリケーションを構築できます。

使用するハードウェア

プロジェクトを作成

  1. Baker link. Envを起動し、プロジェクト名を入力、createをクリック、プロジェクト保存先のフォルダを選択してください。

  2. Visual Studio Code起動後は、左下のコンテナで再度開くをクリックしてください。しばらくすると、Dockerの環境がビルドされ、環境構築が完了します!

コーディング(コードを書く)

  1. Cargo.tomlを以下のように記載ください。
[package]
name = "embassy_blinky"
version = "0.1.0"
edition = "2021"

[dependencies]
static_cell = "2.1"
cortex-m = "0.7"
cortex-m-rt = "0.7"
defmt = "1.0.1"
defmt-rtt = "1.0.0"
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
critical-section = "1.1.0"
embassy-rp = { version = "0.4.0", features = [
    "defmt",
    "unstable-pac",
    "time-driver",
    "critical-section-impl",
    "rp2040",
] }
embassy-executor = { version = "0.7.0", features = [
    "arch-cortex-m",
    "executor-thread",
    "executor-interrupt",
    "defmt",
] }
portable-atomic = { version = "1.5", features = ["critical-section"] }
embassy-time = { version = "0.4.0", features = [
    "defmt",
    "defmt-timestamp-uptime",
] }
embassy-sync = { version = "0.7.0", features = ["defmt"] }


# cargo build/run
[profile.dev]
codegen-units = 1
debug = true
debug-assertions = true
incremental = false
opt-level = 0
overflow-checks = true

# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 3
overflow-checks = false
  1. src/main.rsを次のコードに書き換えてください。
    このコードは、緑とオレンジのLEDを別々のタスクで点滅させるプログラムです。
#![no_std]
#![no_main]

use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::gpio;
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Ticker};
use gpio::{Level, Output};
use {defmt_rtt as _, panic_probe as _};

type LedType = Mutex<ThreadModeRawMutex, Option<Output<'static>>>;
static GREEN_LED: LedType = Mutex::new(None);
static ORANGE_LED: LedType = Mutex::new(None);

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

    // Initialize the GPIO pins for the LEDs
    {
        let green_led = Output::new(p.PIN_22, Level::High);
        let orange_led = Output::new(p.PIN_21, Level::High);

        *(GREEN_LED.lock().await) = Some(green_led);
        *(ORANGE_LED.lock().await) = Some(orange_led);
    }

    unwrap!(spawner.spawn(toggle_led(&GREEN_LED, Duration::from_millis(600))));
    unwrap!(spawner.spawn(toggle_led(&ORANGE_LED, Duration::from_millis(900))));
}

#[embassy_executor::task(pool_size = 2)]
async fn toggle_led(led: &'static LedType, delay: Duration) {
    let mut ticker = Ticker::every(delay);
    loop {
        {
            let mut led_unlocked = led.lock().await;
            if let Some(pin_ref) = led_unlocked.as_mut() {
                pin_ref.toggle();
            }
        }
        ticker.next().await;
    }
}

ハードウェアの準備

※Baker link. Devであれば、特に配線は不要です。

プログラムをRUN

  1. 配線とコーディングが終わったら、Baker link. EnvのRunをクリックしてください。するとバックグラウンドで、probe-rsのDAP Serverが起動します。

  2. Visual Studio CodeでF5キーを押してください。すると、以下のようなアイコンが表示されます。

  3. もう一度、F5キーを押すと、プログラムが動作します。

すると緑とオレンジのLEDが点滅します!

最後に

以上の手順で、Rustで非同期処理のLチカを動作させることができました。Embassyは、他にもUSBやEthernet等の通信をサポートしていたりと幅広い機能をサポートしています。ぜひ試してみてください!

今回のプログラム

https://github.com/Baker-Tanaka/embassy_blinky

Discussion