コンテンツにスキップ

非同期タスクで LED を点滅させる

Embassy フレームワークを使って、STM32F767ZI NUCLEO-144 ボードの 3 色LED をそれぞれ異なる周期で点滅させましょう。 この記事を通じて、Embassy-rs の基本的な使い方を学びます。


  • Embassy の #[embassy_executor::main] マクロによるスレッドモード・エグゼキュータの自動初期化
  • GPIO 出力の設定と LED のトグル制御
  • Ticker を使った周期タイマーによる非同期待機
  • Spawner による複数タスクの並行実行

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
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();
}
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();
}
}

Spawner は Embassy のタスクを生成するための構造体です。

このマクロは main 関数の周りに Embassy の ThreadModeExecutor を自動的に初期化します。手動でエグゼキュータを設定する必要がありません。

MCU のペリフェラルを一括取得します。戻り値の Peripherals 構造体には GPIO、タイマー、通信ペリフェラルなどが全て含まれています。各ピン(p.PB0 等)は所有権が移動するため、誤った二重使用をコンパイル時に防止できます。

spawner.spawn() は非同期タスクをエグゼキュータに登録します。#[embassy_executor::task] 属性をつけた関数は spawn 可能なタスクに変換されます。pool_size = 3 は同時に生成できるタスクの上限です。

Ticker は指定した間隔で next() を呼ぶと、次のタイミングまで非同期的に待機します。await で他のタスクに実行を譲るため、3 つの LED タスクが独立した周期で同時に動作します。


Terminal window
cargo run -p blinky --features multitask

probe-rs 経由で書き込むと、以下のログが確認できます。

Terminal window
[Green]: On
[Blue]: On
[Green]: Off
[Red]: On
[Green]: On
  • 緑 LED が 300ms 周期で点滅すること
  • 青 LED が 1000ms 周期で点滅すること
  • 赤 LED が 2000ms 周期で点滅すること
  • 3 つの LED が互いに干渉せず独立して点滅すること

  • ピン番号の間違い: NUCLEO-F767ZI の LED は PA5 ではなく PB0/PB7/PB14 です(Arduino ヘッダの PA5 とは別)
  • pool_size 不足: spawn するタスク数より pool_size が小さいとパニックします
  • Config::default() のクロック: デフォルト設定では HSI(内部発振器 16MHz)が使われます。216MHz で動かすには RCC の手動設定が必要です(後の記事で解説)