作るもの
土壌水分センサを使って、水分量を1秒ごとLCDに出力するモジュールを作ります!
また、スイッチで画面を切り替えてキャリブレーション(校正)できるようにします。
応用として、一定値を下回ったら自動でお水をやったり、通知する例が考えられます。
実際に作らずとも、Arduinoの使い方の一例として参考になれば嬉しいです!
材料・配線図
- マイコン (ESP32) ¥1,380
- 静電容量式土壌水分センサ (SEN0193) ¥1,056
- LCDモジュールシールド ¥999
- タクトスイッチ
- 抵抗 (10kΩ)
- コンデンサー (1μF 50V)
- ジャンパワイヤ
- ブレッドボード
1. マイコン (ESP32)
本記事のプログラムはインターネット通信をしていませんが、Wi-Fiモジュールを搭載しているESP-WROOM-32を使っているため、インターネット通信が可能です。
2. 静電容量式土壌水分センサ (SEN0193)
より安価な電気抵抗式の土壌水分センサでも良いのですが、今回は耐久性・精度が優れた静電容量式のセンサを使いました。
自動水やり機のように土に常に挿す場合は、耐久性のある静電容量式の方が適しています。
マイコン | 土壌水分センサ |
---|---|
3.3V | Vcc |
GND | GND |
GPIO26 | アナログ出力電圧 |
参考
製造元の解説記事
静電容量型の土壌湿度センサを使ってArduinoで土の水分量測定
3. LCDモジュール
モジュールシールドが付いており、はんだ付けが不要なものを購入しました。
LCDの裏はこのようになっています。つまみを回すことで、画面の明るさを調整できます。
マイコン | LCDモジュール |
---|---|
5V | Vcc |
GND | GND |
SDA | SDA (GPIO21) |
SCL | SCL (GPIO22) |
4. タクトスイッチ
キャリブレーションのモード切り替えのために使いました。
今回はマイコンに対してのみON/OFFを入力するため、上下の端子とも同じ配線にしています。
タクトスイッチの原理については、下記サイトさんの解説がとてもわかりやすいです。
参考
Arduino(アルディーノ)電子工作の基本⑥ スイッチの状態を読み取る
5. 抵抗 (10kΩ)
抵抗は、マイコン <-> タクトスイッチ
接続部のプルアップのために使いました。
プルアップ抵抗とは、電源やGNDとモジュール間の接続がない状況を避け、動作を安定させるために追加します。
詳しい原理については、下記サイトさんの解説がとてもわかりやすいです。
参考
プルアップ抵抗・プルダウン抵抗とは?電子回路に必須の考え方
6. コンデンサー (1μF 50V)
コンデンサーは、ESP32へ安定してスケッチを書き込むために使いました。
ESP32にそのままスケッチを書き込もうとした際、以下のように、ESP32とPCが正常に通信できなかったと怒られました_:(´ཀ`」 ∠):
A fatal error occurred: Timed out waiting for packet header
そこで、EN <-> GND
へ0.1μF~2.2μF程度のコンデンサを追加することで、正常に書き込めるようになりました。
コンデンサーを追加してもエラーが解決しない場合は、下記サイトさんをご参照ください。
参考
書込エラー対応「A fatal error occurred: xxxxx」
プログラム
#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#define SEN0193_PIN 26
#define SWITCH_PIN 27
LiquidCrystal_I2C lcd(0x27,16,4);
int ADC_AIR = 0; // 空気中の実測値
int ADC_WET = 0; // 水に浸した実測値
int mode = 0; // モード切り替え用
/* センサの測定値を水分量[%]に変換する関数 */
int getMoisture(int val) {
if (val > ADC_AIR) {
val = ADC_AIR;
}
else if (val < ADC_WET) {
val = ADC_WET;
}
int mois = 100 - map(val, ADC_WET, ADC_AIR, 0, 100);
return mois;
}
/* ADC_AIR,ADC_WATにEEPROMの値を格納する関数 */
void getEEPROM() {
EEPROM.get(0, ADC_AIR);
EEPROM.get(4, ADC_WET);
}
/* setup関数 */
void setup(void) {
Serial.begin(115200);
pinMode(SEN0193_PIN, INPUT);
pinMode(SWITCH_PIN, INPUT);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Ready...");
EEPROM.begin(4*2); // int=4byte * 2vals
delay(1000);
getEEPROM();
delay(3000);
}
/* モードを変更する関数 */
void changeMode() {
if(mode == 0) { mode = 1; }
else if(mode == 1) { mode = 2; }
else if(mode == 2) { mode = 3; }
else if(mode == 3) { mode = 0; }
}
/* loop関数 */
void loop(void) {
int vol = analogRead(SEN0193_PIN);
int moisture = getMoisture(vol);
/* ボタンが押されたらモード切り替え */
if(digitalRead(SWITCH_PIN) == LOW) {
changeMode();
}
switch(mode) {
/* mode0: 毎秒測定して水分量を表示する */
case 0:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MODE0: Measure");
lcd.setCursor(0, 1);
lcd.print("Vol : " + String(vol));
lcd.setCursor(0, 2);
lcd.print("Mois: " + String(moisture) + " %");
lcd.setCursor(0, 3);
lcd.print("Set : A=" + String(ADC_AIR) + " W=" + String(ADC_WET));
Serial.println("Vol: " + String(vol) + " Mois: " + String(moisture) + " %");
break;
/* mode1: 水分量0%をキャリブレーションする */
case 1:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MODE1: Calib AIR");
lcd.setCursor(0, 1);
lcd.print("Vol : " + String(vol));
ADC_AIR = vol;
break;
/* mode2: 水分量100%をキャリブレーションする */
case 2:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MODE2: Calib WET");
lcd.setCursor(0, 1);
lcd.print("Vol : " + String(vol));
ADC_WET = vol;
break;
/* mode3: キャリブレーションした値をEEPROMに保存する */
case 3:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MODE3: Saved ^-^");
lcd.setCursor(0, 3);
lcd.print("Set : A=" + String(ADC_AIR) + " W=" + String(ADC_WET));
EEPROM.put(0, ADC_AIR);
EEPROM.put(4, ADC_WET);
EEPROM.commit(); // EEPROMへ上書き
delay(1000);
changeMode(); // mode0に切り替え
break;
}
delay(1000);
}
4つのモード
プログラムには以下の4つのモードがあり、スイッチが押されると次のモードに推移します。(正確には、loop関数が呼び出されるタイミングで押されていたら推移します)
Mode | 実行内容 |
---|---|
0 |
水分量を測定するモード。 LCDの2行目に測定値を、3行目に水分量の計算結果を、4行目に現在設定されている0%,100%時の測定値を表示する。 |
1 |
0%時の測定値をキャリブレーションするモード。 LCDの2行目に現在の測定値を表示し、スイッチが押されたタイミングの測定値を0%として保存する。 |
2 |
100%時の測定値をキャリブレーションするモード。 LCDの2行目に現在の測定値を表示し、スイッチが押されたタイミングの測定値を100%として保存する |
3 | 測定結果をEEPROMに保存するモード。 保存後はMode0に自動で戻る。 |
水分量の計算
水分量の計算 getMoisture()
は、空気中での測定値を最小値0%、水中での測定値を最大値100%と仮定して、簡易的に線形マッピングしているだけです。
実際の土を使う場合、乾燥した状態で空気中の測定値を、十分湿らせた状態で水中の測定値をキャリブレーションすることで、より細かく水分量を算出できると思います。
EEPROM (不揮発性メモリ)
このプログラムでは、キャリブレーションした0%,100%の測定値をもとに水分量を計算していますが、これらの値が電源を切って消えてしまうと、再度キャリブレーションしなくてはなりません。
それは非常に面倒なので、電源を切っても値を保持しておけるEEPROM (不揮発性メモリ) を使用しています。
ここに格納することで、再起動しても直近のキャリブレーション時の測定値をもとに水分量を測定できます。
ただし、EEPROMの物理的な構造上書き込み回数に制限があります。
(といっても10万回くらいはいけるようです)
そのため、このプログラムでは、setup()
で1回、loop()
でMode3になった時のみ1回使用するようにしています。
このような物理的な制約があるのも、電子工作の面白いところです(´・ω・`)
終わりに
私は農業のIoT化をテーマに共同研究をしていて、その遊びがてらこのモジュールを作成しました。今回はインターネット通信をしていないのでIoTではないですが…笑
水分量(体積含水率)の正確な測定には、塩害やセンサの寿命を考慮する必要があり、土壌水分センサ1つ+今回のような簡易的なキャリブレーションでは不十分です。
現在は畑での灌水モジュールを作成中で、論文サーベイや議論をする毎日です。農学系の方と話すと、自分と全く視点が違ってとても勉強になります。
ありがとうございました>ω</