静電容量式の降雨センサーは雨の降り始めはわかるが降り終わりがわからないので雨の強さや降り終わりもわかるボタン電池で動作する超低消費電力なセンサーが作れないものだろうかと考えてみた。
降雨センサーは何種類かあるがそれぞれ一長一短がある。
1.抵抗値(降り終わりがわからない、ヒーター付きは消費電力大)
2.静電容量(降り終わりがわからない、ヒーター付きは消費電力大)
3.電波系(ドップラー方式、消費電力大)
4.光学系(消費電力大)
6.音響系(雨音の検出が難しい?雪は検出できない)
電波系や光学系は電力消費が比較的大きいため雨粒の衝突音を検出する音響系の方法が一番消費電力が低くできる可能性がありそうだ。但し、マイクでは特性が良すぎて不必要な環境音まで拾ってしまい雨粒の衝突音だけを検出するのが少々やっかいそうなので他に使えそうな方法は?と探して見つけたのがピエゾセンサー。楽器用にも使われている振動を検出するためのセンサーであるがマイクより感度は低く周波数特性も良くないが雨粒の衝突音の検出用には使えそうだし値段も安く入手もしやすいので早速試してみた。
アマゾンで購入した安いピエゾセンサーを一般的なオペアンプを使って試したところかなり感度がいい。大きな音や息を吹きかけたりしても反応する。少し感度が良すぎるかも?とは思ったが使えそうなことがわかったのであとは低消費電力化をどうするかだ。実験の結果、超低消費電力(uA未満)で動作するオペアンプはスルーレートが低すぎて使いものにならなかったので超低消費電力(uA未満)で動作するコンパレータのみで実験してみた。
センサーを屋外に置き実際に試してみるとアンプなしではやはり感度不足っぽい気もするがしっかり濡れるぐらいの降雨であれば検出は可能だった。あまり感度が高いと風とか飛行機とか工事の音とか様々な環境音にも反応してしまうのでこれぐらいの感度のほうがいいのかも。使い物になるかどうかは暫く使い込んでみる必要があるけれどね...
【18:06-00:37までの実測データをグラフ化】
最初の山は19時頃、次の山は00時頃で実際に雨が降っていた。風速7-8m程度の比較的強い風だったため風だけでもセンサーが反応してしまっていたが1分毎のデータなので60以下なら風の影響と考えれそう。グラフ設定がうまくできなかったけど雰囲気だけは出てるっぽいな。(笑)
【追記】
2024-05-28
風通しの良い場所にセンサーを移動してみたら風の影響をかなり受けることがわかった。雨が直接当たり風通しの悪い場所に設置するのが良さそうだ。
2024-05-16
整流格子を設置してから風のない日が続いたがようやく体感的にも風の強い日がやってきた。実測はしてないが天気予報の最大値は風速10mでも反応なし。整流格子を設置する前は風速8mで頻繁に反応してたから効果はあると言える。センサーの近くで大きな音をたてたり大音量でスピーカーを鳴らす車が近くを通ると反応してしまうのが気にはなるが人為的なことなので避けようがないから気にしないことにしよう。(-_-;)
今回は実験ということで入力保護回路を省略したが実用とするなら保護回路は必須かな。でもコンパレータだけでこんなにうまく動作するなんてやってみないとわからないもんだね...
2024-04-19
整流格子の効果はまだわからないがアマゾンのアレクサに【雨降ってきた】とか【雨やんだ】とか喋らせることがようやくできた。なかなかうまくいかなくて諦めかけていたがうまくいってしまうとあれもこれも喋らせてみたいとなってしまう中毒性があるようだ。前に作った郵便受け通知もアレクサ対応にしたりして我が家のアレクサはいろんなことを教えてくれるようになったのはいいけれど妻が煩いと思い始めているようなのが気にかかる今日この頃...(-_-;)
今日は快晴だったのになぜか【雨降ってきた】とアレクサが言うので確認してみると雨センサーのすぐ横にお隣の猫ちゃんが...なんかしちゃったのかな?(笑)
2024-04-15
ブロックだけでは風の影響を避けられなかったので3Dプリンターで整流格子というのを作ってみた。適当に9x9x30mmで作ってみたがこれで風の影響が無くなるといいな。でも、ちょっと細かすぎたか?
2024-04-09
筐体がちょうど入るブロックをアマゾンで見つけたので早速設置。これで風の直接の影響は避けられるはずだが...暫くは様子見だな。
設置の時、手が滑って筐体をコンクリの上に落としてしまった。ピエゾセンサーって凄い電圧(最大10V以上も)が出るのがわかってたのに保護回路を入れてなかったので(-_-;)った。その後も動作してるがなんらかの後遺症が残ってるかもと考えたりして余計な心配事が増えちゃった。やっぱり安心のための回路はケチらないほうがいいかも。
それとTWELITEはRED(JN5169)を規定値(0dbm)の送信出力で使っていたが電波がギリ届いてるようで不安定なため高出力(+10dbm)に変更してみたところ逆に悪化してしまった。
調べてみたら高出力にすると送信時の消費電流が倍くらいに増加するためボタン電池ではちょっと厳しいみたい。さらに高出力時の電源電圧は最低2.8V以上とあるのでTWELITEでボタン電池駆動するならRED(JN5169)を選ぶ意味はないということになる。但し、BLUE(JN5164)に収まらないような巨大なファームウェアを実行するならRED(JN5169)一択ではあるが...
2024-04-07
コンパレータを交換してみたらデータシート通りの消費電流であることが確認できた。たまたま最初に使ったチップだけ消費電流が多かったみたい。これで待機時の全消費電流は1uA未満になった。はず。
2024-04-04
最高感度よりも少し下げた設定でもポツポツ降り始めた雨に反応してくれる。でも、びゅ~って突然吹く風にも反応してしまうのが気になる。風のためだけに感度を落としたくないから何か対策してみるか...
2024-04-01
誤動作を防ぐため閾値に余裕を持たせたらかなり感度が悪くなってしまった。現状の回路だと数mVの微妙な電圧調整が必要で温度の影響を受けやすいためコンパレータの閾値には余裕を持たせられるよう前段に増幅回路を入れてゲイン調整するのが適切かも...
と思ったが誤動作の原因が温度変化なら緩やかな変化のはずだから現象としては何かおかしいと思いコンパレータの入力電圧を測ってみるとゆらゆらと数mV程度ぶれている。調べてみるとピエゾセンサーの抵抗値(約2.7M)がゆらゆらと変化することがわかったので直結をやめてコンデンサー経由にしてみたところ電圧は安定。ということで不安定さの原因はピエゾセンサーだった。v(-_-;)
これで前段にオペアンプを入れたのと同程度の感度まで安定に設定できるようになった。あとはコンパレータの消費電流が想定よりも大きいのが解決できたら完璧だ!
2024-03-30
なぜかお昼ごろになると誤動作してしまう。どうやら日中に筐体内部の温度が上がると抵抗値が変化してコンパレータの閾値がかなり変化してしまうらしい。高抵抗の分圧回路で微妙な電圧調整はするなってことだね...温度変化に強い単純な回路にするにはどうするのがいいんだろう?
【回路図】
コンパレータはTS881(210nA)を使ったのだが実測すると4-5uAも流れてた。なぜだろう?
省電力化のためコンパレータ出力はスリープ中でも動作するTWELITEのパルスカウンターでカウントしパワーオンやカウントがゼロになったときにディープスリープさせ最初のパルスでウェイクアップさせる。その後はウェイクタイマーにより1分毎にウェイクアップしながらカウントの差分をNVデータ領域にバッファリングしていきカウントがゼロになるまで繰り返す。データはバッファフルになるかカウントがゼロになった時点で送信する。
【ファームウェア(TWELITE)】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
/* main.cpp - rain sensor for NXP JN516x Series Copyright (c) 2024 Sasapea's Lab. All right reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ #include <string.h> #include <limits.h> #include "device.h" #define DEBUG 1 #define WPAN_ADDRESS 10 #define WPAN_PANID 0x5169 #define WPAN_CHANNEL WPan::CHANNEL_24 #define WPAN_HOST JN516x::ADDR_OTA_BRIDGE #define TX_TIMEOUT 100 // ms #define DATA_SHIFT 0 #define MIN_PULSECOUNT 5 #define DATA_INTERVAL 60 // seconds #define PIN_PULSECOUNTER Gpio::DIO1 typedef struct __attribute__((packed)) { uint8_t command[3]; uint8_t status; union { struct __attribute__((packed)) { uint8_t shift; uint8_t count; uint16_t data[5]; } pulse; uint32_t data[3]; } nvm; } packet_t; static packet_t packet = { .command = { 'P', 'C', 'D' }}; static uint32_t pulsecount; static int txstatus; void sleep(void) { // // Sleep + Wake Timer (0.64uA + 0.05uA) // if (pulsecount) { WakeTimer::start(WakeTimer::WAKE_0, DATA_INTERVAL, empty); System::sleep(System::SLEEP_OSCON_RAMOFF); } // // Deep Sleep + Wake DIO (0.1uA) // else { Gpio::wake(PIN_PULSECOUNTER, Gpio::FALLING, empty); System::sleep(System::SLEEP_DEEP); } } void WPanTransmitCallback(uint8 u8Handle, uint8 u8Status) { switch (u8Handle) { case 0x01: txstatus = u8Status; break; } } void send(uint16_t addr, void *buf, uint8_t len) { // // Setup WPan // WPan::begin(WPAN_ADDRESS, WPAN_PANID, WPAN_CHANNEL); // // Send Data // txstatus = -1; WPan::transmit(addr, (uint8_t *)buf, len, 0x01 | WPAN_OPT_DUPLICATE); for (uint32_t t = System::millis(); (txstatus == -1) && (System::millis() - t < TX_TIMEOUT); ) yield(); // // End WPan // WPan::end(); // // Increment Packet SEQ Number // packet.nvm.pulse.shift += 0x10; } void clearNVM(bool all = false) { uint8_t b = all ? 0 : packet.nvm.pulse.shift & 0xF0; memset(&packet.nvm, 0, sizeof(packet.nvm)); packet.nvm.pulse.shift = b; } void loadNVM(void) { for (size_t i = 0; i < sizeof(packet.nvm.data) / sizeof(packet.nvm.data[0]); ++i) packet.nvm.data[i] = System::readNVData(i); } void saveNVM(void) { for (size_t i = 0; i < sizeof(packet.nvm.data) / sizeof(packet.nvm.data[0]); ++i) System::writeNVData(i, packet.nvm.data[i]); } void setup(void) { // // Setup Pin Mode // Gpio::pinMode(PIN_PULSECOUNTER, Gpio::INPUT); // // Power On Reset Delay // System::POWER poweron = System::powerStatus(); if ((poweron & (System::POWER_SLEEP_WAKEUP | System::POWER_DEEP_SLEEP)) == 0) System::delay(100); // // Setup Pulse Counter // uint32_t count; PulseCounter::configure(PulseCounter::COUNTER_0, true, PulseCounter::SAMPLES_0, PulseCounter::COMBINE_ON0); PulseCounter::read32(count); PulseCounter::start(PulseCounter::COUNTER_0); // // Read Pulse Counter // if (poweron & System::POWER_SLEEP_WAKEUP) pulsecount = count - System::readNVData(3); System::writeNVData(3, count); // // Load NV Data // loadNVM(); // // Wakeup from Deep Sleep (DIO Pin) // if (poweron & System::POWER_DEEP_SLEEP) { clearNVM(); pulsecount = 1; } // // Wakeup from Sleep (WakeTimer) // else if (poweron & System::POWER_SLEEP_WAKEUP) { uint8_t datacount = packet.nvm.pulse.count; if (pulsecount < MIN_PULSECOUNT) pulsecount = 0; if ((pulsecount == 0) || (packet.nvm.pulse.count >= sizeof(packet.nvm.pulse.data) / sizeof(packet.nvm.pulse.data[0]))) { if (datacount != 0) { packet.status = pulsecount ? 1 : 2; send(WPAN_HOST, &packet, sizeof(packet)); clearNVM(); } } if (pulsecount) { if (datacount == 0) { packet.status = 0; send(WPAN_HOST, &packet, sizeof(packet)); } uint8_t shift = 0; pulsecount >>= packet.nvm.pulse.shift & 0x0F; while (pulsecount > USHRT_MAX) { pulsecount >>= 1; ++shift; } if (shift) { packet.nvm.pulse.shift = (packet.nvm.pulse.shift & 0xF0) | ((packet.nvm.pulse.shift + shift) & 0x0F); for (uint8_t i = 0; i < packet.nvm.pulse.count; ++i) { if ((packet.nvm.pulse.data[i] >>= shift) == 0) packet.nvm.pulse.data[i] = 1; } } packet.nvm.pulse.data[packet.nvm.pulse.count++] = pulsecount ? pulsecount : 1; } } // // Power On Reset // else { clearNVM(true); } // // Save NV data // saveNVM(); } void loop(void) { sleep(); } |