静電容量型の土壌湿度センサを使ってArduinoで土の水分量測定
静電容量型の土壌湿度センサを使ってArduinoやESP32で土の水分量が測定できます。植物や野菜菜園などの水やり管理に使用したり、日々の水分量をロギング、グラフで管理すれば植物の発育研究にも。
防水加工を施した静電容量型土壌湿度センサ
静電容量型の土壌湿度センサです。市販の Capacitive Soil Moisture Sensor を、実際の現場で使いやすいよう改造しました。
- 基板の防水加工
- センサの反応速度の改善
- ブレッドボードで刺せるジャンプワイヤーの実装
- 2Mのセンサケーブルを延長
- 植物の根を痛めないよう先端を丸く加工
ジャンプワイヤーのカラー対応は次のとおり。
ジャンプワイヤー | 役割 |
---|---|
赤 | Vcc |
黒 | GND |
白 | アナログ出力電圧 |
土壌湿度センサの使い方
土壌湿度センサの電源電圧Vdd、アナログ出力電圧範囲、消費電流は次のとおり。ArduinoやESP32でご使用になれます。
項目 | 内容 |
---|---|
電源電圧 | 3.3〜5.5V |
アナログ出力電圧範囲 | 0〜3V |
消費電流 | 5mA |
水分量が多い状態ではアナログ出力電圧値が低くなります。乾燥している状態ではアナログ電圧値は高くなります。
電源電圧は必ず定電圧を使用してください。乾電池で動かすと電圧降下によってアナログ出力電圧まで変化してしまいます。乾電池を使いたい場合は、DC-DCコンバータなど使って定電圧を作って下さい。
土にどのくらい埋めれば良いのかですが、推奨されるのはセンサの半分程度です。防水加工を施されたものなら気にせずに土中に埋めても大丈夫です。
センサによってはアナログ出力電圧にバラツキがあります。また土中への差し込み具合や、土の成分によっても出力が異なります。ここら辺は実際に使用しながらご研究なさってください。
マイコンボードとの配線
マイコンボードと土壌湿度センサの配線例を図に示しました。
電源電圧Vddは3.3Vまたは5Vのどちらでも構いません。アナログ電圧出力(AOUT)はマイコン側のアナログ入力へ。ADコンバータが使えるピンでしたらどこでも構いません。水分量に応じてアナログ出力電圧が0V〜3Vの範囲で変化します。
▼ ArduinoやESP32、M5 Stackなどお好きなボードをお使いください。Raspberry Piでも使用できますがADコンバータが必要となりますのでご注意ください。
▼ どのArduinoを選べば良いか分からない方はこちら。
▼ これらのブレッドボードやジャンプワイヤも揃えておくと便利です。
土壌湿度センサのアナログ値を読み取るサンプルソース
マイコンボードと土壌湿度センサを配線できたら、動作確認します。アナログ値をシリアルモニターへ出力するだけの簡単なプログラム例を紹介してます。センサの先端を手で触れたり離したりして値が変わるかどうか確認してください。
#include <Arduino.h>
#define SENSOR_PIN A0 // Arduino Uno
// #define SENSOR_PIN 27 // ESP32
void setup(void) {
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
delay(3000);
}
void loop(void) {
int val = analogRead(SENSOR_PIN);
Serial.println(val);
delay(1000);
}
土中水分量の計算方法
アナログ値だけでは水分量が分かりずらいので、0〜100の範囲で水分量を算出する方法を紹介します。それにはまず次の準備が必要です。
- センサが何も触れずに空気中の状態でのアナログ出力値をメモする
- センサが水中に浸っている時状態でのアナログ出力値をメモする
②では、コップなどに水を張り、土壌センサの半分以上を水の中に浸した状態でアナログ出力値を読み取っておきます。それらのアナログ値を元に、空気中を水分量0%とし水中を水分量100%としてプログラミングで計算させます。計算式は図の通りです。
ただし、静電容量型の土壌湿度センサは土中のEC成分や金属成分にも反応します。あくまでも水分量は目安程度と考えてください。より正確に水分量を算出したい場合は、①を空気中ではなく完全に乾燥させた土で計り、②を水ではなく十分に湿らせた状態の土で値を取っておくと良さそうです。
土中水分量を0〜100で表すサンプルソース
土壌湿度センサを使って水分量を測定するサンプルソースを紹介します。実行するとセンサが空気中の時に0、センサが水中の時を100を示します。ADC_MAX と ADC_MIN は先の説明の通り、あらかじめ測定した値に書き換えてください。
#include <Arduino.h>
#define SENSOR_PIN A0 // Arduino Uno
// #define SENSOR_PIN 27// ESP32
const int ADC_MAX = 352; // 空気中の実測値
const int ADC_MIN = 159; // 水に浸した実測値
void setup(void) {
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
delay(3000);
}
void loop(void) {
int val = analogRead(SENSOR_PIN);
if (ADC_MAX < val) {
val = ADC_MAX;
}
else if (ADC_MIN > val) {
val = ADC_MIN;
}
int moisture = 100 - map(val, ADC_MIN, ADC_MAX, 0, 100);
Serial.println(moisture);
delay(1000);
}
静電容量型土壌湿度センサの回路・動作原理
ここからは電子回路のマニアックな話になります。静電容量型土壌湿度センサの内部回路を図に示します。
この動作原理を詳しく解説します。回路は大まかに次の3つの構成となってます。
- 発振回路
- ローパスフィルタ
- AC-DCコンバータ
①ではタイマICの555を使って矩形波を発生させてます。矩形波は②の1次RCローパスフィルタを通ります。すると、矩形波の高周波成分が削られていき三角波へ近づきます。さらに③で交流信号(電圧)を直流信号(電圧)に変えるため、AC-DCコンバータを通ってアナログ電圧を出力します。
ここで②のローパスフィルタのコンデンサCを水分量で変化するプローブに置き換えると、静電容量の変化に応じてカットオフ周波数が変化します。結果、プローブが接触している水分量に応じて、アナログ出力電圧が変化します。
③のAC-DCコンバータは、Peak Detectorとも呼ばれる半波整流回路です。アナログ電圧出力は、1uのコンデンサに溜まった電荷を測っているにすぎません。また回路図中の1MΩの抵抗は、1uのコンデンサに溜まった電荷を逃がす役割をします。ただし1MΩの抵抗値は大きすぎてセンサの反応が悪いと感じましたので、私が販売しているセンサではこの抵抗を100kΩあたりに変更してます。
土壌湿度センサの種類として 抵抗型の土壌湿度センサ もあります。抵抗型のセンサは、2つの金属棒で土の抵抗を測って土壌の水分量を測定します。金属棒が土にさらされますから腐食しやすいのが難点です。静電容量型ですと電極板をコーティングできるので耐久性は高くなります。
自動水やり機への発展
土壌湿度センサの応用編として、自動水やり機への発展が挙げられます。一定の水分量を下回ったら自動で水やりを行うといった装置です。以前に企業様から依頼されて、8ch分の自動水やり機のプロトタイプを製作したこともあります。ディスプレイとボタンを設けセンサのキャリブレーションを行ったり、水やりする水分量を変えたり、水分量データをサーバーへ飛ばしたりしました。
▼ 写真のポンプはUSB電源で動かせますが水を引き上げる力が強くありません。本格的に水やりを行いたい場合は、電磁弁を使って水道を制御する方法がおすすめです。
ESP32とAmbientでデータロガーへの発展
ESP32などのWiFi機能を使ってAmbientへデータを送信してグラフ化することもできます。AmbientはIoTデータをグラフ化してくれる無料のクラウドサービスです。
#include "DHT.h"
#include "Ambient.h"
#define DHT11_PIN 32
#define ADC1_PIN 34
#define ADC2_PIN 35
const int ADC1_MAX = 2230; // 空気中の実測値
const int ADC1_MIN = 990; // 水に浸した実測値
const int ADC2_MAX = 2160; // 空気中の実測値
const int ADC2_MIN = 960; // 水に浸した実測値
const char* ssid = "WiFiのSSID";
const char* password = "WiFiのパスワード";
const int channelId = AmbientのチャンネルID;
const char* writeKey = "Ambientのライトキー";
const boolean displayMode = true;
WiFiClient client;
Ambient ambient;
DHT dht(DHT11_PIN, DHT11);
void setup() {
Serial.begin(115200);
pinMode(ADC1_PIN, INPUT);
pinMode(ADC2_PIN, INPUT);
dht.begin();
Serial.println("WiFi connecting....");
WiFi.begin(ssid, password); // Wi-Fiの初期化
while (WiFi.status() != WL_CONNECTED) { // Wi-Fiアクセスポイントへの接続待ち
delay(500);
}
Serial.println("WiFi connected!!!");
ambient.begin(channelId, writeKey, &client);
}
int counter = 0;
void loop() {
/** DHT11 処理 **/
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("DHT11 failed...");
}
/** 土壌湿度センサ 処理 **/
int adc1 = analogRead(ADC1_PIN);
delay(500); // 少し待たないとanalogReadに失敗する
int adc2 = analogRead(ADC2_PIN);
Serial.printf("adc1: %d, adc2: %d\n", adc1, adc2);
if (ADC1_MAX < adc1) {
adc1 = ADC1_MAX;
}
else if (ADC1_MIN > adc1) {
adc1 = ADC1_MIN;
}
if (ADC2_MAX < adc2) {
adc2 = ADC2_MAX;
}
else if (ADC2_MIN > adc2) {
adc2 = ADC2_MIN;
}
int m1 = 100 - map(adc1, ADC1_MIN, ADC1_MAX, 0, 100);
int m2 = 100 - map(adc2, ADC2_MIN, ADC2_MAX, 0, 100);
Serial.printf("Humid: %.0f, Temp: %.1f%, Moist1: %d, Moist2: %d\n", h, t, m1, m2);
if (counter % 6 == 0) {
sendAmbient(h, t, m1, m2);
}
counter++;
delay(4500);
}
void sendAmbient(float humid, float temp, int moist1, int moist2) {
ambient.set(1, temp);
ambient.set(2, humid);
ambient.set(3, moist1);
ambient.set(4, moist2);
ambient.send();
}