Sparkfun Weather Meters 雨量計の誤検知をごまかすファームウェアを作った


 以前、雨量計を別途ベランダに直置きして誤検知が発生するか試すと書いたが、結果から言えば誤検知は発生した。どうやら、雨量計につながるケーブルにノイズが乗っているのが原因らしい。そこで、ノイズが乗ることを前提にファームウェア側で処理することにした。

 雨量計の信号は常時 HIGH になっていて、マスが転倒する際にマスについた磁石がリードスイッチを通過することで一瞬 LOW になる。Sparkfunのファームウェアの場合、HIGH → LOW のタイミングで割り込みを発生させ、割り込みが発生すれば雨量計が転倒したものとしてカウントアップする。ただし、マスが転倒していある間は信号にノイズが乗るので、前回のカウント時から 100 μ秒以内の割り込みについてはカウントアップしないものとして処理している。キーとなる点は、雨量計の中のマスが転倒する際、ノイズと比べると転倒に比較的時間がかかるということ。

 新しいファームウェアは信号が LOW になったら割り込みでカウントアップするのではなく、信号が変化したときに割り込みを発生させ、LOW になった時間と HIGH になった時間を micros() 関数で取得する。これにより、転倒時に LOW となってから HIGH に戻るまでの時間を算出し、その時間が 80 ミリ秒以上ならマスが転倒したものとして計測する。これを図示すると以下のような感じになる。

 この実装でも LOW になったタイミングでノイズが乗るため、100 μ秒以内の信号は無視している。ファームウェアにデバッグコードを組み込んだものを USB Weather Board にアップロードして実測してみると、LOW から HIGH に戻るまでに必要な時間は 120 〜 200 ミリ秒程度だった。当初はこの閾値を 50 ミリ秒にしていたのだが、稀にノイズが 50 ミリ秒以上続くケースがある。そのため、若干閾値を上げることにした。

void rainIRQ()
// if the Weather Meters are attached, count rain gauge bucket tips as they occur
// activated by the magnet and reed switch in the rain gauge, attached to input D2
{
  raintime = micros();
  if (digitalRead(RAIN) == LOW)
  {
    if (rainstate != 1) {
      rainstate     = 1;
      rainpulsetime = raintime;
    }
  } else {
    if (rainstate == 1)
    {
      rainpulseinterval = raintime;
      if (rainpulseinterval < rainpulsetime) {
        rainpulseinterval = 0xffffffff - raintime + rainpulseinterval;
      } else {
        rainpulseinterval = rainpulseinterval - rainpulsetime;
      }
      if (rainpulseinterval > 80000)
      {
        rain++;
        rainlast  = raintime;
        rainstate = 0;
      }
    } else {
      rainlast  = raintime;
      rainstate = 0;
    }
  }
}

 rainstate は信号線が LOW になったことを検知すれば 1 に、それ以外の場合は 0 にして処理を分岐させている。LOW になってから HIGH に戻るまでの時間を格納する rainpulseinterval の計算が多少複雑になっているが、これは micros() 関数がそもそも 1 時間 11 分 35 秒近くまでしか計測されないことから、桁溢れを考慮する必要があるため。ただ、割り込み関数で使っている変数の見直しが必要で、例えば rainstate は unsigned long である必要は全くなかったりする。このへんの見直しは処理速度にも関わり、遅くなる要素は排除しておかないと割り込みの取りこぼしに繋がるので早いうちにやっていきたい。

 ただ、この実装でも問題はある。雨量計と同時に風力計も回っており、こちらも同様に LOW になるタイミングで割り込みを発生させて計測している。風力計は雨量計と違って計測頻度も大幅に多いことからノイズはある程度許容されるとは思うが、問題は割り込み処理中には他の割り込みが停止することにある。具体的には、以下のタイミングで風力計の割り込みが発生した場合に問題が発生する。

 上記のような場合、LOW から HIGH に戻ったタイミングで割り込みが発生しないため、次のマスの転倒まで計測されないことになる。結果、計測される雨量が減ってしまう。そのため、次の計測の直前で計測ルーチンが LOW のまま止まっているか確認し、LOW になってからの経過時間が 3 秒以上経っていればマスが転倒としたものとしてカウントアップしている。ただし、この方法では計測間隔が比較的長い状態で集中豪雨が発生した場合、データを取りこぼす恐れがある。そのため、計測間隔は 3 〜 5 秒程度が望ましい。そもそも、計測間隔の計測そのものはループを回しているだけなので

  // we're done sampling all the sensors and printing out the results
  // now wait in a loop for the next sample time
  // while we're waiting, we'll check the serial port to see if the user has pressed CTRL-Z to activate the menu
  do // this is a rare instance of do-while - we need to run through this loop at least once to see if CTRL-Z has been pressed
  {
    while (Serial.available())
    {
      if (Serial.read() == 0x1A) // CTRL-Z
      {
        menu(); // display the menu and allow settings to be changed
        loopend = millis(); // we're done with the menu, break out of the do-while
      }
    }
  }

ここでマスの補正を行なってもよさそう。

 そんな修正版ファームウェアだが、github で公開している。(https://github.com/meihong/USB-Weather-Board-V3-Firmware) ソースをダウンロードの上、以前説明したとおりにファームウェアを更新すればよい。

 なお、今回はファームウェアで解決したが、Sparkfun の商品コメント欄で聞いてみたところ、ローパスフィルタの追加で解決するらしい。こちらのほうが根本的な解決だと思うので、この方法もありだと思う。また、雨量計と風力計がそれぞれ割り込みを使って計測しているため、どちらのデータも割り込みがかぶることによるデータの取りこぼしの問題を抱えている。そのため、本当に正確に計測したいのであれば、風力と雨量を別の Arduino で計測する必要がある。ただし、風力計も雨量計も単純に Arduino + ハードウェア割り込みなだけなので、USB Weather Board では風力を、雨量は Arduino Uno やサイズでアドバンテージのある Arduino Mini で計測する、といった方法でもいいのではないだろうか。