Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Embassy の美しさ

このセクションでは、バックグラウンドタスクを導入して、音を鳴らす演習を拡張します。これにより、Embassy の async タスクモデルの強力さを実感できます。

メインタスクは、ディスプレイ上に "EMBASSY" という文字列を継続的にスクロール表示します。同時に、バックグラウンドタスクはボタンが押されるのを待機します。どちらのボタンが押されたかに応じて、前の例と同じように音の再生を開始または停止します。

Embassy を使わずに同じ結果を実現することも可能ですが、その場合ははるかに多くの手間と複雑さが必要になります。Embassy は組み込み開発をシンプルにします。

前の演習を変更してもよいですし、最初からプロジェクトを作成してもかまいません。

テンプレートからプロジェクトを作成する

このプロジェクトでは、microbit-bsp(Embassy あり)を使用します。テンプレートを使って新しいプロジェクトを生成するには、次のコマンドを実行します。

cargo generate --git https://github.com/ImplFerris/mb2-template.git --rev 3d07b56
  • プロジェクト名の入力を求められたら、"background-tasks" のような名前を入力してください。

  • async を使用するかどうかを尋ねられたら、"true" を選択してください。

  • "BSP""HAL" のどちらを選ぶか尋ねられたら、"BSP" を選択してください。

初期化

いつものように、まずボード、ディスプレイ、スピーカーを初期化します。

#![allow(unused)]
fn main() {
let board = Microbit::default();
let mut display = board.display;
let speaker = PwmSpeaker::new(SimplePwm::new_1ch(board.pwm0, board.speaker)); // ここでは Speaker を mut にしていません
}

Spawner 引数を使う

これまで、main 関数に渡される Spawner 引数は使っておらず、その目的についても説明していませんでした。このセクションでは、これを使い始めます。そのため、使えるように引数名からアンダースコアを外してください。

// async fn main(_spawner: Spawner) -> ! {
// 変更後:
async fn main(spawner: Spawner) -> ! {

Spawner を使うと、バックグラウンドタスクを起動できます。これを使って、まもなく定義する button_task を開始します。

Button Task

では、ボタン入力を処理するバックグラウンドタスクを定義しましょう。これは #[embassy_executor::task] 属性が付いたシンプルな async タスクです。2 つのボタンとスピーカーの所有権を受け取ります。

ループの中では、まず Button A が押されるのを待ちます(low になる)。それが起きたら、音の再生を開始します。次に Button B が押されるのを待ち、それが押されたら音を停止します。

#![allow(unused)]

fn main() {
#[embassy_executor::task]
async fn button_task(
    mut button_a: Input<'static>,
    mut button_b: Input<'static>,
    mut speaker: PwmSpeaker<'static, PWM0>,
) {
    loop {
        button_a.wait_for_low().await;
        speaker.start_note(Pitch::Named(NamedPitch::A4));

        button_b.wait_for_low().await;
        speaker.stop();
    }
}
}

タスクを起動する

button_task を定義したので、Spawner を使って main 関数からこれを起動できます。

#![allow(unused)]
fn main() {
 spawner
        .spawn(button_task(board.btn_a, board.btn_b, speaker))
        .unwrap();
}

これで完了です。たったこの 1 行で、ボタンタスクはバックグラウンドで動き始め、ボタン入力を待ちながら、メインタスクは自身の処理を続けます。

メインタスクのループ

メインタスクはシンプルです。ディスプレイ上に "EMBASSY" という文字列をループでスクロールし続けます。各スクロールの後に、タイマーを使って短い待ち時間を入れます。

#![allow(unused)]
fn main() {
 loop {
        display
            .scroll_with_speed("EMBASSY", Duration::from_secs(10))
            .await;
        Timer::after_millis(300).await;
    }
}

このループが継続的に実行されている間、先ほど起動したボタンタスクはバックグラウンドで動作し、メインタスクをブロックしません。これこそが Embassy と async の美しさです。

完全なコード

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_nrf::{gpio::Input, peripherals::PWM0, pwm::SimplePwm};
use embassy_time::{Duration, Timer};
use microbit_bsp::{
    Microbit,
    speaker::{NamedPitch, Pitch, PwmSpeaker},
};
use {defmt_rtt as _, panic_probe as _};

#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
    let board = Microbit::default();
    let mut display = board.display;

    let speaker = PwmSpeaker::new(SimplePwm::new_1ch(board.pwm0, board.speaker));

    spawner
        .spawn(button_task(board.btn_a, board.btn_b, speaker))
        .unwrap();

    loop {
        display
            .scroll_with_speed("EMBASSY", Duration::from_secs(10))
            .await;
        Timer::after_millis(300).await;
    }
}

#[embassy_executor::task]
async fn button_task(
    mut button_a: Input<'static>,
    mut button_b: Input<'static>,
    mut speaker: PwmSpeaker<'static, PWM0>,
) {
    loop {
        button_a.wait_for_low().await;
        speaker.start_note(Pitch::Named(NamedPitch::A4));

        button_b.wait_for_low().await;
        speaker.stop();
    }
}

既存のプロジェクトを clone する

自分が作成したプロジェクトを clone(または参照)して、bsp-embassy/background-tasks フォルダへ移動することもできます。

git clone https://github.com/ImplFerris/microbit-projects
cd microbit-projects/bsp-embassy/background-tasks

書き込み

これで、プログラムを micro:bit に書き込めます。

cargo flash

書き込みが完了すると、ディスプレイ上に "EMBASSY" の文字列が継続的にスクロール表示されます。ボタンを押して音の開始や停止を行うことができ、スクロールを中断することなくバックグラウンドでスムーズに動作します。