水耕栽培用自動給気給水機QLi-kun2/表面型静電容量式液面計

水槽の水量を制御するのに、液面検知は必須です。液面計にはいくつか種類がありますが、水耕栽培の水槽の液面検知なので駆動部分がないことを優先して探しました。これは、伸びてきた根が絡まって指示不良になる可能性があるためです。

Water Level Sensor for Arduino/Seeed Studio

最初に試したのはこれですね。駆動部分がないこと、液面の高さを連続的に測定できることが魅力的でした。価格も安いというところもいいですね。静電容量式と記載がありましたが、タッチパネルみたいに表面の静電容量を測定するものですね。対極があって液全体の静電容量を測定する形ではないです。

SeeedDocument/ Grove-Water-Level-Sensor
Grove-Water-Level-Sensor/water-level-sensor-demo.ino at master · SeeedDocument/Grove-Water-Level-Sensor · GitHub

スケッチは、上記のSeeed Studioのサンプルコードほぼそのままですが、以下4点を追加、変更しています。
・設定液面高さを検知した時にLED点灯/消灯を追加
・エアレーションポンプとの並行処理の為、check関数をloop内に取り込み
・RTCとエアレーションポンプの時間設定を追加
・センサーの液面高さ(%)を実際の水槽の液面高さに合うように、表示をオフセット

#include <DFRobot_SD3031.h>
#include <Wire.h>

DFRobot_SD3031 rtc; 

const int WP = D10;//water pump
const int Air = D3;//Aeration

#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
#define SERIAL SerialUSB
#else
#define SERIAL Serial
#endif

unsigned char low_data[8] = {0};
unsigned char high_data[12] = {0};

#define NO_TOUCH       0xFE
#define THRESHOLD      100
#define ATTINY1_HIGH_ADDR   0x78
#define ATTINY2_LOW_ADDR   0x77


void getHigh12SectionValue(void){
  memset(high_data, 0, sizeof(high_data));
  Wire.requestFrom(ATTINY1_HIGH_ADDR, 12);
  while (12 != Wire.available());

  for (int i = 0; i < 12; i++) {
    high_data[i] = Wire.read();
  }
  delay(10);
}


void getLow8SectionValue(void){
  memset(low_data, 0, sizeof(low_data));
  Wire.requestFrom(ATTINY2_LOW_ADDR, 8);
  while (8 != Wire.available());

  for (int i = 0; i < 8 ; i++) {
    low_data[i] = Wire.read(); // receive a byte as character
  }
  delay(10);
}


void setup(){
    Serial.begin(115200);
    Wire.begin();
    while(rtc.begin() != 0){
        Serial.println("Failed to init chip, please check if the chip connection is fine. ");
        delay(1000);
    }

    pinMode(WP, OUTPUT);
    pinMode(Air, OUTPUT);

}

void loop(){
    sTimeData_t sTime;
    sTime = rtc.getRTCTime(); 

    if(sTime.hour == 20){  //aeration start time
      if(sTime.minute == 53){
        if(sTime.second ==2){
        digitalWrite(Air, HIGH);
        }
      }
    }

    if(sTime.hour == 21){  //aeration stop time
      if(sTime.minute == 0){
        if(sTime.second ==6){
        digitalWrite(Air, LOW);
        }
      }
    }


  int sensorvalue_min = 250;
  int sensorvalue_max = 255;
  int low_count = 0;
  int high_count = 0;
  uint32_t touch_val = 0;
  uint8_t trig_section = 0;

    low_count = 0;
    high_count = 0;
    getLow8SectionValue();
    getHigh12SectionValue();

    Serial.println("low 8 sections value = ");
    for (int i = 0; i < 8; i++)
  {
      Serial.print(low_data[i]);
      Serial.print(".");
      if (low_data[i] >= sensorvalue_min && low_data[i] <= sensorvalue_max)
      {
        low_count++;
      }
      if (low_count == 8)
      {
        Serial.print("      ");
        Serial.print("PASS");
      }
    }
    Serial.println("  ");
    Serial.println("  ");
    Serial.println("high 12 sections value = ");
    for (int i = 0; i < 12; i++)
    {
      Serial.print(high_data[i]);
      Serial.print(".");

      if (high_data[i] >= sensorvalue_min && high_data[i] <= sensorvalue_max)
      {
        high_count++;
      }
      if (high_count == 12)
      {
        Serial.print("      ");
        Serial.print("PASS");
      }
    }

    Serial.println("  ");
    Serial.println("  ");

    for (int i = 0 ; i < 8; i++) {
      if (low_data[i] > THRESHOLD) {
        touch_val |= 1 << i;

      }
    }
    for (int i = 0 ; i < 12; i++) {
      if (high_data[i] > THRESHOLD) {
        touch_val |= (uint32_t)1 << (8 + i);
      }
    }

    while (touch_val & 0x01)
    {
      trig_section++;
      touch_val >>= 1;
    }

    int waterlevel;
    float waterlevel_conv;
    waterlevel = trig_section * 5;
    waterlevel_conv = waterlevel * 0.6667 + 40;

    SERIAL.print("water level = ");
    SERIAL.print(waterlevel_conv);
    SERIAL.println("% ");
    SERIAL.println(" ");

    Serial.print(sTime.year, DEC);//year
    Serial.print('/');
    Serial.print(sTime.month, DEC);//month
    Serial.print('/');
    Serial.print(sTime.day, DEC);//day
    Serial.print(" (");
    Serial.print(sTime.week);//week
    Serial.print(") ");
    Serial.print(sTime.hour, DEC);//hour
    Serial.print(':');
    Serial.print(sTime.minute, DEC);//minute
    Serial.print(':');
    Serial.print(sTime.second, DEC);//second
    Serial.println(' ');

SERIAL.println("*********************************************************");

  if (trig_section*5 <= 30){     /*water pump start*/ 
    digitalWrite(WP, HIGH);
  }

  if (trig_section*5 >= 40){     /*water pump stop*/
    digitalWrite(WP, LOW);
  }

    delay(1000);
}

水道水を使って試運転をすると、こんな感じ。
黄色LED:エアレーションポンプの代替(ONで点灯、OFFで消灯)
赤色LED:設定してある液面高さを検知で点灯(水が減って液面が下がった状態)
RTCを使ってエアレーションのポンプの制御と並行処理をしようとしていたので、この試運転では、液面計を起動させた状態で、エアレーションポンプの代替の黄色のLEDが時間制御で点灯、液面計を引き上げることで水が減った状態を再現し、設定の液面高さで赤色LED点灯、です。

作動状況は良好。シリアルモニタには下のように液面高さが%表示されます。液面計に並んでいるセンサー下8個と上12個の測定値がそれぞれ出てきます。測定値が250で液体検知です。

表面に水膜が残って検知が遅れる傾向はやっぱりあって、勢いよく引き上げると検知が遅れます。ただ、そんなすごい勢いで水が減ることは想定していないので、水耕栽培の液面計としては問題にならないですね。すごい勢いで水が減るのは、容器に穴が開いたとかの異常事態です。

液面高さは、長さ10cmのセンサーを0~100%表示しているので、水槽の液面が直接表示されているわけではありません。ただ、設置している高さからセンサーの最低点を実際の水槽液面(%)となるようにオフセットすればよいので、問題にはならないです。

これを試作中だった自動給気給水機QLi-kun2に追加、半日経ったところで外出先からAmbientを覗いてみると、、、

下がその時の液面高さのトレンドです。43~103%の間で激しくハンチングしています。

d1は実際の水槽液面高さに補正した液面高さ(%)です。補給水送液ポンプの稼働条件は、液面高さ85%以下でオン、90%以上でオフでした。

ええ、そうです、この時、すごい勢いで給水されていました。ほとんど止まることなく給水した結果、キュウリ水耕栽培の水槽は当然オーバーフローし、貯水槽は空になっていました。

帰ってから液面計を引き上げて様子を確認すると、接液していた所が全面的に白く変色していました。写真で見ると分かりにくいですが、一応載せておきます。

左が肥料と接触して変色したもの、右は通常品

どうやら肥料成分とセンサー表面の加工が反応してしまったみたいです。この記事を書いているのは、あれから2か月経過した後ですが、今でもこのセンサーは様子がおかしいです。うーん、駆動部分が無いこと、試運転の動作状況良好、安価であること、加えて液面高さを%表示できたことから、すごく期待していましたが(なんなら3個購入した)、ちょっとこちらの環境では使えそうにないですね。やっぱり材質試験は大事です。

長くなってしまったので、とりあえずここまで。次はフロート式液面計かな。

コメントを残す

しろすけ技術研究所
主任研究員

こんにちは。市民農園で野菜作りをしているしろすけです。極小規模の栽培管理システム開発をいつかやってみたいと思っていました。2024年から育苗ケース給排気温調や、水耕栽培管理システムの試作を電子工作で始めました。想像以上に検討することがあったので、記録を残すことにします。バイク(SR400)と耕運機(FV200)の整備記録もついでに記録に残しておきます。


しろすけ技術研究所をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む