非同期タスクで LED を点滅させる
Embassy フレームワークを使って、STM32F767ZI NUCLEO-144 ボードの 3 色LED をそれぞれ異なる周期で点滅させましょう。 この記事を通じて、Embassy-rs の基本的な使い方を学びます。
この記事で学ぶこと
Section titled “この記事で学ぶこと”- Embassy の
#[embassy_executor::main]マクロによるスレッドモード・エグゼキュータの自動初期化 - GPIO 出力の設定と LED のトグル制御
Tickerを使った周期タイマーによる非同期待機Spawnerによる複数タスクの並行実行
なぜこの知識が必要か
Section titled “なぜこの知識が必要か”LED の点滅は直接GPIOを制御し、 loop { on; delay; off; delay; } を実行することで可能です。
しかし、複数の LED を独立した周期で同時に制御するにはスーパーループによる制御ではなく、非同期ランタイムによる制御が適切なケースが多いです。
Embassy では async/await を使って複数のタスクを独立したタイミングで実行できます。
これにより、スーパーループで用いられるようなポーリングループで時間管理をする煩雑さから解放されます。
- ボード: NUCLEO-F767ZI(STM32F767ZI、Cortex-M7 216MHz)
- ツール: probe-rs、Rust ツールチェーン +
thumbv7em-none-eabihfターゲット - LED ピン: PB0(緑 LD1)、PB7(青 LD2)、PB14(赤 LD3)
ピン配置は NUCLEO-144 ユーザーマニュアル (UM1974) の Chapter 7.5 を参照してください。
sequenceDiagram
participant Main as main task
participant S as Spawner
participant LED1 as led_task_1 (Green)
participant LED2 as led_task_2 (Blue)
participant LED3 as led_task_3 (Red)
Main->>Main: embassy_stm32::init()
Note over Main: ペリフェラル初期化
Main->>S: spawner.spawn(led_task_1)
S-->>LED1: タスク生成
activate LED1
Main->>S: spawner.spawn(led_task_2)
S-->>LED2: タスク生成
activate LED2
Main->>S: spawner.spawn(led_task_3)
S-->>LED3: タスク生成
activate LED3
par 非同期並行実行
LED1->>LED1: 500ms 周期で点滅
and
LED2->>LED2: 1000ms 周期で点滅
and
LED3->>LED3: 1500ms 周期で点滅
end
ペリフェラルの初期化
Section titled “ペリフェラルの初期化”use embassy_executor::Spawner;
#[embassy_executor::main]async fn main(spawner: Spawner) { let config = Config::default(); let p = embassy_stm32::init(config);
spawner.spawn(led_task(p.PB0.into(), LedName::Green, 300)).ok(); spawner.spawn(led_task(p.PB7.into(), LedName::Blue, 1000)).ok(); spawner.spawn(led_task(p.PB14.into(), LedName::Red, 2000)).ok();}LED 制御タスク
Section titled “LED 制御タスク”use embassy_stm32::gpio::{AnyPin, Level, Output, Speed};
#[embassy_executor::task(pool_size = 3)]async fn led_task( pin: Peri<'static, AnyPin>, name: LedName, period_ms: u64,) { let mut led = Output::new(pin, Level::Low, Speed::Low); let mut ticker = Ticker::every(Duration::from_millis(period_ms)); loop { ticker.next().await; led.toggle(); }}コードの読み解き
Section titled “コードの読み解き”use embassy_executor::Spawner;
Section titled “use embassy_executor::Spawner;”Spawner は Embassy のタスクを生成するための構造体です。
#[embassy_executor::main]
Section titled “#[embassy_executor::main]”このマクロは main 関数の周りに Embassy の ThreadModeExecutor を自動的に初期化します。手動でエグゼキュータを設定する必要がありません。
embassy_stm32::init(config)
Section titled “embassy_stm32::init(config)”MCU のペリフェラルを一括取得します。戻り値の Peripherals 構造体には GPIO、タイマー、通信ペリフェラルなどが全て含まれています。各ピン(p.PB0 等)は所有権が移動するため、誤った二重使用をコンパイル時に防止できます。
Spawner と #[embassy_executor::task]
Section titled “Spawner と #[embassy_executor::task]”spawner.spawn() は非同期タスクをエグゼキュータに登録します。#[embassy_executor::task] 属性をつけた関数は spawn 可能なタスクに変換されます。pool_size = 3 は同時に生成できるタスクの上限です。
Ticker::every() と .await
Section titled “Ticker::every() と .await”Ticker は指定した間隔で next() を呼ぶと、次のタイミングまで非同期的に待機します。await で他のタスクに実行を譲るため、3 つの LED タスクが独立した周期で同時に動作します。
実行結果・確認ポイント
Section titled “実行結果・確認ポイント”cargo run -p blinky --features multitaskprobe-rs 経由で書き込むと、以下のログが確認できます。
[Green]: On[Blue]: On[Green]: Off[Red]: On[Green]: On- 緑 LED が 300ms 周期で点滅すること
- 青 LED が 1000ms 周期で点滅すること
- 赤 LED が 2000ms 周期で点滅すること
- 3 つの LED が互いに干渉せず独立して点滅すること
ハマりどころ
Section titled “ハマりどころ”- ピン番号の間違い: NUCLEO-F767ZI の LED は PA5 ではなく PB0/PB7/PB14 です(Arduino ヘッダの PA5 とは別)
pool_size不足:spawnするタスク数よりpool_sizeが小さいとパニックしますConfig::default()のクロック: デフォルト設定では HSI(内部発振器 16MHz)が使われます。216MHz で動かすには RCC の手動設定が必要です(後の記事で解説)