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" の文字列が継続的にスクロール表示されます。ボタンを押して音の開始や停止を行うことができ、スクロールを中断することなくバックグラウンドでスムーズに動作します。