Wio BG770A + Grove水センサーで水漏れを割り込み(ISR)で検知する

前の記事では、Grove水センサーのアナログ値を1秒ごとにポーリングする実装を試しました。

ポーリングは実装がシンプルで良いのですが、毎回センサーの値を読み込むことになるので、バッテリー駆動の場合は消費電力が気になります。

特に、水漏れのような滅多に起こらない事象(イベント)を検知する場合、ほとんどが意味のないセンサー読み出しになります。

これを解決する方法として、ArduinoのISR(Interrupt Service Routine)を用いた割り込みでセンサーの変化を取得する方法を実装しました。

この記事でわかること

  • Grove水センサーのデジタル出力の仕組み
  • attachInterrupt を使った割り込み検知の実装パターン
  • ISR(Interrupt Service Routine)内で volatile フラグを使う理由

使用機材・環境

機材詳細
IoTデバイスWio BG770A(Seeed Studio)
センサーGrove - Water Sensor

使用したコード

コード全体はGithubに置いてあります。

wio-bg770a-water-sensor-interrupt — iotbuilds-samples

Wio BG770A 水センサー 割り込み版スケッチ(PlatformIO)

github.com

デジタル出力の仕組み

Grove水センサーのSIGピンは、アナログ値(0〜1023)としてもデジタル値(HIGH/LOW)としても読み取れます。

デジタルで読んだ場合の挙動はシンプルです。

状態SIG電圧digitalRead()
乾燥時高いHIGH(1)
水濡れ時低いLOW(0)

アナログ版では「値が小さいほど濡れている」でしたが、デジタル版では LOW = 濡れている と読みかえるだけです。

ピン:A4とD28は同じGroveコネクタ

アナログ版では A4 を使いましたが、今回は D28 を使います。

constexpr uint8_t kDigitalSensorPin = D28; // Same Grove - Analog (P1)

A4とD28は同じGrove Analog(P1)コネクタの同じ物理ピンです。 前回は、このピンにanalogRead(A4)を実行し、0〜1023(アナログ)の値で水濡れ具合を取得しました。

今回は代わりに、digitalRead(D28) と書いて、HIGH/LOWのデジタル値を取得します。

Wio BG770AにGrove水センサーを接続した様子
Wio BG770AにGrove水センサーを接続した様子

割り込みの実装

ポーリングは「変化がなくてもループが回り続ける」構造です。 割り込みは「変化があったときだけ処理が走る」ため、変化がない間はCPUを止めておけます。

実装のポイントは3つです。

1. ISRはフラグを立てるだけにする

ISR(Interrupt Service Routine)とは、割り込みが発生したときに呼ばれる関数のことです。 ISRの中では、Serial.print() などの重い処理を呼んではいけません。 代わりに volatile bool のフラグを立てるだけにして、実際の処理はメインループに任せます。

volatile bool gDigitalStateChanged = false;
 
void onDigitalStateChanged() {
  gDigitalStateChanged = true;
}

volatile は「この変数はいつでも書き換わる可能性がある」とコンパイラに伝えるキーワードです。 ISRとメインループは実行タイミングが非同期のため、volatile なしだとコンパイラの最適化によって変化を見落とすことがあります。

2. CHANGEで両エッジを受け取る

attachInterrupt(digitalPinToInterrupt(kDigitalSensorPin), onDigitalStateChanged, CHANGE);

CHANGE を指定すると、LOW→HIGHとHIGH→LOWの両方の変化で割り込みが発生します。 これで水が触れた瞬間だけでなく、乾いた瞬間も検知できます。

定義を見ると、以下のようにFALLINGRISINGも使用できます。 FALLINGは、HIGH->LOW、RISINGは、LOW->HIGHの際だけ割り込みを発生させます。

//      LOW 0
//      HIGH 1
#define CHANGE 2
#define FALLING 3
#define RISING 4

3. フラグの読み取りは割り込みを一時停止して行う

if (gDigitalStateChanged) {
  noInterrupts();
  gDigitalStateChanged = false;
  interrupts();
 
  printSensorState("digital-change", digitalRead(kDigitalSensorPin));
}

noInterrupts() でフラグをリセットしている間だけ割り込みを止めます。 止めないとフラグのリセット中に別の割り込みが入り、そのイベントを取りこぼす可能性があります。

実験結果

起動時(startup)には、乾いた状態(HIGH)で、その後センサーに水を垂らす->水を拭いたときのログです。

HIGH(startup)→ LOW(水濡れ)→ HIGH(拭いた後)と変化していることが見て取れます。

Wio BG770A Water Sensor Interrupt Sample
Grove P1 digital input: D28(28)
Interrupt mode: detect digital HIGH/LOW changes
[startup] digital=HIGH(1)
[digital-change] digital=LOW(0)
[digital-change] digital=HIGH(1)

ポーリング版と違い、状態が変化したときだけログが出力されます。 変化がない間はループが回っているだけで、シリアルには何も出力されません。

まとめ

Grove水センサーのデジタル出力をCHANGEで受け取り、水の検知と乾燥の両方をイベントとして処理できました。

ISRの中でフラグを立て、メインループで処理するパターンは、Arduinoの割り込み実装の基本形です。 volatilenoInterrupts() の2点を守るだけで、シンプルに動作します。

ポーリング版と割り込み版でどれだけ消費電力が変わるかは、次の記事でPPK2を使って実測します。

関連記事