TEMP ペリフェラルの割り込みハンドラー
最初に浮かぶ疑問は、こうかもしれません。そもそも、ここでなぜ割り込みハンドラーが必要なのでしょうか? なぜ今この話をしているのでしょうか?
温度センサーも、単なる別のペリフェラルにすぎません。温度を測定したい場合、CPU はセンサーに測定開始を要求し、その後結果を待つ必要があります。しかし、測定には少し時間がかかるため、CPU が何もしないまま値の準備ができたかを確認し続けるのは無駄です(これをポーリングと呼びます)。よりよい方法は、準備ができたときにセンサー側から割り込みで通知してもらうことです。
割り込みの設定には複数の手順がありますが、ここではこのケースに直接関係する部分だけに注目します。
まず、TEMP 割り込みをそのハンドラーにバインドしました
embassy-nrf クレートには、bind_interrupts! というマクロが用意されており、特定の Interrupt を対応するハンドラーに接続するのに役立ちます。
一般的な使い方は次のようになります。
#![allow(unused)] fn main() { bind_interrupts!(struct Irqs{ INTERRUPT_NAME => INTERRUPT_HANDLER; INTERRUPT_NAME2 => INTERRUPT_HANDLER2; }); }
このケースでは、TEMP 割り込みを temp::InterruptHandler ハンドラーにバインドします。これも embassy-nrf が提供しています。
#![allow(unused)] fn main() { bind_interrupts!(struct Irqs { TEMP => temp::InterruptHandler; }); }
ここでは基本的に、「TEMP ペリフェラルから割り込みが来たら、temp::InterruptHandler に処理させる」と伝えているわけです。
次に、Temp 構造体を初期化しました
先ほど定義した Irqs 構造体と p.TEMP ペリフェラルを渡して、Temp ドライバーを作成しました。
#![allow(unused)] fn main() { let mut temp = Temp::new(p.TEMP, Irqs); }
こう思ったかもしれません――「ちょっと待って、この Irqs ってユニット構造体には見えないよね」と。その通りで、そうは見えません。でも実際にはユニット構造体です。先ほど使ったマクロが、そのユニット構造体を生成しています。マクロ展開後にどのような形になるのかは、すぐ後でお見せします。
最後に、温度を非同期に読み取りました
あとは、次のように温度を読み取るだけです。
#![allow(unused)] fn main() { let value = temp.read().await; }
nRF52833 のドキュメントより: TEMP は START タスクをトリガーすることで開始されます。温度測定が完了すると、DATARDY イベントが生成され、測定結果を TEMP レジスタから読み取ることができます。
この関数は内部で、温度測定を開始するようセンサーへ要求を送ります。その後、データの準備ができたときにセンサーが通知できるよう、割り込みを有効にします。
そして、結果を非同期に待ちます。センサーが測定を終えると、割り込みを発生させます。その割り込みは temp::InterruptHandler によって処理され、待機していた read() 関数を起こします。これにより先へ進み、温度の値を読み取れるようになります。
温度割り込みハンドラー
temp::InterruptHandler 構造体の定義を見ると、これが Handler トレイトを実装していることがわかります。中心となるのは on_interrupt 関数で、TEMP 割り込みを受信したときに何を行うかを定義しています。
先ほど述べたように、ここで行うことはあまり多くありません。単に、センサーを待っていた read() 関数を起こすよう Embassy に知らせるだけです。
#![allow(unused)] fn main() { impl interrupt::typelevel::Handler<interrupt::typelevel::TEMP> for InterruptHandler { unsafe fn on_interrupt() { let r = pac::TEMP; r.intenclr().write(|w| w.set_datardy(true)); WAKER.wake(); } } }
この関数はまず、再度発火しないように割り込みを無効化します。その後 WAKER.wake() を呼び出して、温度の値を待っていた async タスクを再開します。これにより、read() 関数は処理を続行して結果を読み取れるようになります