kuroの覚え書き

96の個人的覚え書き

Arduinoで土壌水分センサー

鉢植えや庭木を枯らさないように水やりのタイミングを知りたい。
そんなときは水分センサーがあればいい気がする。
ということでアマゾンでやっすいセンサーを買ってArduinoでセンシング&ロギングしてみる。

#include <Arduino.h>
#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal.h>
#include <SPI.h>
#include <SD.h>
#include <stdio.h>
#include <EEPROM.h>

// LiquidCrystal display with:
// rs on pin 2
// rw on pin 3
// enable on pin 4
// d4, d5, d6, d7 on pins 5, 6, 7, 8
LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8);
const int sensorPin = A0;    // select the input pin for the potentiometer
int sensorValue = 0;  // variable to store the value coming from the sensor
int ledPin = 13;      // select the pin for the LED
RTC_DS1307 RTC;
char daysOfTheWeek[7][12] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
int serialStat = 0;
int buttonStat = 1;
int wetValue = 0;
int dryValue = 1020;
float humValue = 0;
#define buttonPin 9
#define DEFAULT_DRY 1023
#define DEFAULT_WET 0
#define DATA_VERSION "v1.0"
/**************************************************************************/
struct DATA_SET
{
    int dValue;
    int wValue;
    char check[10];
};
DATA_SET data;

void load_data() {
  EEPROM.get<DATA_SET>(0, data);
  if (strcmp(data.check, DATA_VERSION)) // Check for version
  { 
    // If there is no data
    data.dValue = DEFAULT_DRY;
    data.wValue = DEFAULT_WET;
  }
}

void save_data() {
  strcpy(data.check, DATA_VERSION);
  EEPROM.put<DATA_SET>(0, data);
}
/**************************************************************************/
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  buttonStat = digitalRead(buttonPin);
  Serial.begin(115200);
//  while (!Serial)
//    delay(10);     // will pause Zero, Leonardo, etc until serial console opens
  if(Serial) {
    serialStat = 1;
  } else {
    serialStat = 0;
  }
// SD card  
  if(serialStat == 1) {
    Serial.print("Initializing SD card...");
  // see if the card is present and can be initialized:
    if (!SD.begin()) {
      Serial.println("Card failed, or not present");
    }
    Serial.println("card initialized.");
  }
  else {
    SD.begin();
  }
// RTC
  Wire.begin();
  RTC.begin();
  delay(1000);
 if(serialStat == 1) {
    if(!RTC.begin()) {
      Serial.println("RTC failed");
    }
  }
//  RTC.adjust(DateTime(__DATE__, __TIME__));
//  RTC.adjust(DateTime(2022, 10, 29, 0, 13, 30));
    /* Set RTC with PC initially */
  DateTime now = RTC.now();
  String timeStamp = String(now.year()) + '/' + String(now.month()) + '/' + String(now.day()) + " (" + daysOfTheWeek[now.dayOfTheWeek()] + ") " + String(now.hour()) + ':' + String(now.minute()) + ':' + String(now.second());
  if(serialStat == 1) {
    Serial.println(timeStamp);
  }
// LCD
  lcd.begin(2,16);
  lcd.clear();
  lcd.print("Soil Humidity");
  lcd.setCursor(0,1);
  lcd.print(timeStamp);
  delay(2000);
// switch to calibration of sensor
  if (buttonStat == LOW){
    calibSens();
    data.dValue = dryValue;
    data.wValue = wetValue;
    save_data();
  }
  else {
    load_data();
    dryValue = data.dValue;
    wetValue = data.wValue;
  }
// loging start
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  dataFile.print(timeStamp);
  dataFile.print(" Dry Value: ");
  dataFile.print(dryValue);
  dataFile.print(" Wet Value: ");
  dataFile.print(wetValue);
  dataFile.println("");
  dataFile.close();
}
/**************************************************************************/
void calibSens(void) {
  dryValue =  analogRead(sensorPin);
  while(dryValue < 1000){
    if(serialStat == 1) {
        Serial.println("Calibrate Dry");
    }
    lcd.clear();
    lcd.print("Calibrate Dry");
    delay(1000);
    dryValue =  analogRead(sensorPin);    
  }  
  sensorValue =  analogRead(sensorPin);
  while((dryValue-sensorValue) < 100) {
    if(serialStat == 1) {
        Serial.print("DryValue = "); Serial.println(dryValue);
        Serial.println("Calibrate Wet");
        delay(1000);
    }      
    String dryText = "DryValue: " + String(dryValue);
    lcd.clear();
    lcd.print(dryText);
    lcd.setCursor(0,1);
    lcd.print("Calibrate Wet");    
    delay(1000);
    sensorValue =  analogRead(sensorPin);
  }  
  sensorValue =  analogRead(sensorPin);
  delay(5000);
  wetValue =  analogRead(sensorPin);
  while (!(wetValue==sensorValue)) {
    if(serialStat == 1) {
        Serial.print("Value is not stable; "); Serial.println(wetValue);
    }
    String wetText = String(wetValue) + "(Not stable)";
    lcd.clear();
    lcd.print("Calibrate Wet");
    lcd.setCursor(0,1);
    lcd.print(wetText);
    sensorValue =  analogRead(sensorPin);
    delay(5000);
    wetValue =  analogRead(sensorPin);   
  }
  if(serialStat == 1) {
      Serial.print("WetValue: "); Serial.println(wetValue);
  }
  String wetText = "WetValue: " + String(wetValue);
  lcd.clear();
  lcd.print(wetText);
  lcd.setCursor(0,1);
  lcd.print("Measure the Soil");
  delay(5000);
}
/**************************************************************************/
void sensorReadSw(void) {
  sensorValue = analogRead(sensorPin);
  humValue = ((float)(dryValue - sensorValue))/((float)(dryValue - wetValue)) * 100;
  DateTime now = RTC.now();
  String timeStamp = String(now.hour()) + ':' + String(now.minute()) + ':' + String(now.second());
   if(serialStat == 1) {
    Serial.println(timeStamp);
    Serial.print("Dry value: "); Serial.println(dryValue); 
    Serial.print("Wet value: "); Serial.println(wetValue); 
    Serial.print("Sensor value: "); Serial.println(sensorValue); 
    Serial.print("Soil Humidity: "); Serial.print(humValue); Serial.println(" %");
  }
  String humText = "Hum: " + String(humValue) + "%";
  lcd.clear();
  lcd.print(timeStamp);
  lcd.setCursor(0,1);
  lcd.print(humText);
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  dataFile.print(timeStamp);
  dataFile.print(",");
  dataFile.print(humValue);
  dataFile.println("");
  dataFile.close();
}
/**************************************************************************/
void loop() {
  sensorReadSw();
  digitalWrite(ledPin, HIGH);
  delay(2000);
  digitalWrite(ledPin, LOW);
  delay(897700);
}

いきなりプログラム。
水分センサーの抵抗値をA0ピンでセンスしてAD変換、シリアルでPCにデータ送信、LCDに表示、SDカードに記録、と3種類のデータ転送、表示をしている。
この手のセンサーはキャリブレーションがキモなので最初に行っている。
基本的にはセンサーのロットが変わったり対象の土の性質が変わると全く比較にならないので、キャリブレーションモードを作った。
9ピンをGNDに落として起動するとキャリブレーションモードに入り完全乾燥状態と完全水没状態の抵抗値を順番に読んで、EEPROMに保存する。そのまま測定モードに移行する。
同じセンサーで同じ土を測定するなら同じキャリブレーションデータを使えるはずなので、9ピンをオープンにして測定モードで起動したときはEEPROMから保存した値を読み込む。
プログラムはいい感じに作り込めたが、やはりセンサーの精度がいまいちなのね。

追記
今回用いたセンサーはダイナミックレンジが非常に狭い上、電極が直接水分にさらされるため腐食が激しく、ずっと土に刺しっぱなしにしてモニターしていると1週間程度で電極パターンが錆びて剥がれ落ちてしまった。
静電容量タイプのこちらの方がちょっと値段が高いがおそらくは耐久性がありそうに思う。