水耕栽培用自動給気給水機QLi-kun2/基板作成、スケッチ全体

これまで検討してきた測定装置、ポンプ、ブロアを組み込み、ユニバーサル基板で基板を作ります。 設計図は途中まで書きましたが、組み立てのほうが先行していってしまい、結局付け間違えて一回やり直しています。 ちゃんと設計図を書かないとだめですね。もっと言えば、回路図も起こしていないので、そっちも勉強しないといけないですね。

全体像はごちゃごちゃなので、それぞれのパーツごとに整理していきます。

DS3031/RTC

DS3031のFritzingパーツを見つけられなかったので、ピンの数と位置だけ同じ別のRTCのパーツを流用しています。なので、色やRTC上の部品の配置は全く違います。

DS3031とXIAO ESP32C3との接続

接続はI2Cなので、RTCのSCLをD5ピン(SCL)に、SDAをD4ピン(SDA)に繋ぎます。アラーム発報のINTをD7ピン、電源はVCCとGNDをそれぞれESP32C3の3.3VとGNDに繋ぎます。

外部スイッチによる割込み

RTCによる割込みはFALLINGになっているので、同様な挙動をタクトスイッチを使って作ります。具体的にはD7を3.3Vでプルアップしておいて、タクトスイッチを押すとGNDに繋がって電圧が落ちる、というものです。抵抗は330Ωで問題なく動作していますが、抵抗の大きさはエイヤです。どう計算するのが正解なのでしょうか。

タクトスイッチは、ボタンを押していないとき(OFF)、左のように縦側の赤色のラインが接続された状態です。ボタンを押すと(ON)、横の水色のラインも接続されます。ボタンを押している間だけ四方全てが繋がる形ですね。今回はプルアップしているラインをGNDに落とすだけなので、右上の端子は使っていません。

液面計LI1(フロート式)/HL-S1A

接続は半固定抵抗TSR-3386Tの2番ピン(中央)から信号線をA0へ繋ぎ、1番ピン(図で上側)を3.3V、3番ピン(図で下側)を液面計に繋いでいます。液面計のマイナス側をGNDに繋ぎます。

半固定抵抗TSR-3386Tは、本体に番号とかないので分かり難いですね。ピンを手前側に置いたときに、左から1、2、3です。つまみを時計回りに回すと、抵抗が大きくなっていって、全開で10kΩです。

TSR-3386T

半固定抵抗のつまみを回して様子を見ましたが、一定以上の開度できちんと数字がでる感じでした。一定以上10kΩ以下で問題ないなら、10kΩの固定抵抗でもいいような気がします。次改造するときは固定抵抗にしてみようかな。イメージ図は下のような感じ。

元々基板上に余力があるので、これなら基板をもう少し小さく作れそうです。

水温計/DS18B20

接続はプルアップ抵抗を入れるくらいですね。DS18B20コネクタ一番上の信号線をD3へ繋いで、同時に5Vを1kΩ抵抗を挟んで信号線に繋いでいます。

液面計LI2(光学式)/FS-IR02

FS-IR02は接続のためのインターフェースを別途持っており、電源を取って信号線を繋げば終わりです。ラクチンですね。その代わり、インターフェースの小型基板がセンサーとの間に挟まります。

将来的に基板を小さく作ろうとすると、接続の簡便さと天秤になるので悩むところですね。

TDS計

TDS計も接続のインターフェースが付いていて、接続自身はラクチンですね。電源を確保して信号線をA1ピンに繋ぐだけです。光学式液面計と同様に小型のインターフェースがセンサーと基板の間に挟まるところが、将来的に小さく作る時に悩むことになりますね。

送水ポンプP1

フォトリレーを介して制御するので、LED側に信号線とLED点灯の為の510Ω抵抗がいれてあります。負荷側は5VとGNDをESP32C3から取ります。他のパーツの端子経由で取るのでゴチャッとしてしまっていますが、あまり悩むところはないですね。

負荷側のコネクタの先は、5V水中ポンプを接続します。使っているのはDONG HONG製DH-390。

DH-390

防水性能はIPX8、水槽底部に設置します。ポンプ下部に付いている吸盤が意外と有能で、つるつるした水槽の底部には結構しっかり固定できます。サクション側は可動のオリフィスが付いています。付いていますが、全閉/全開であまり流量に差を感じないです。いつかしっかり検証したいです。

DH-390下部(網の中の小さなつまみがオリフィス板調整つまみ)

DH-390は電源をUSBで取る仕様になっています。切断してコネクタ直結でもいいですが、今後の作り替えを考えて、USBtypeA→typeC変換コネクタ、USBtypeC→DCケーブル変換ケーブルの2つを挟んでいます。

送水ポンプP2

送水ポンプP2は、P1と全く同じもの(DH-390)を使っています。フォトリレーへの接続も全く同じで、信号線の接続先がP2用に指定しているD2ピンになるくらいですね。

給気ブロアB1

接続方法は給水ポンプP1、P2と同じです。コネクタの先も、同様に変換コネクタ、変換ケーブルを経由してUSBtypeAに繋ぎます。使っているポンプは5Vの金魚ポンプですが、意外と耐久性はある印象です。どれくらい持つか、今後確認していきます。今のところ4か月は持っていますね。

全体

ここまでのパーツを全て組み合わせると、下の図のような感じになります。

まあまあゴチャゴチャですね。電源はXIAO ESP32C3のUSB端子へ6Vバッテリから降圧コンバータ経由でDC5Vを供給します。実際の配線はこんな感じ。

QLi-kun2制御基板裏側

もうちょっと配線がすっきりするような部品配置が出来るといいですが、その辺はセンスですよね。

スケッチ

スケッチはこれまでのパーツの検討で紹介してきたものを組み合わせたものです。内容としては

<setup>
・RTC起動
・WiFi接続/再接続 ←接続開始後10秒で0.5秒のDeep Sleepを入れて疑似再起動
・RTC時刻合わせ(NTPサーバ同期)

<loop>
・RTC現在時刻読み取り
・RTC温度読み取り(基板温度として)
・RTC時刻を参照して、設定時間に給気ブロアB1起動、停止
・上部液面計(LI1/フロート式)読み取り
・上部液面計液面検知で送水ポンプP1起動、液面上昇でP1停止
・下部液面計(LI2/光学式)読み取り
・TDS値読み取り
・RTC設定時間にアラーし0-ム発報
・アラーム発報で水交換シーケンス開始(割込み)
・水交換シーケンスは底部液面検知(LI2)、若しくは水替え開始後15分で終了
・水替えシーケンス終了後はループに戻って上部液面計が液面検知まで給水(P1起動)
・タクトスイッチによる水替えシーケンス割込み実行はRTCアラームと全く同じ

<interrupt>
・水替えシーケンス開始(アラームフラグを1に変更)

くらいですね。ちょっと長いですが、下に貼っておきます。

#include &lt;DFRobot_SD3031.h>
#include &lt;Wire.h>

DFRobot_SD3031 rtc; 

const int P1 = D8;    //water charge pump
const int P2 = D2;    //water discharge pump
const int Air = D10;  //Aeration

#include &lt;math.h>

#define TDS_PIN A1  //TDS
int TDS_sensor = 0;
float TDS_Voltage = 0;
float TDS = 0;

#include &lt;OneWire.h>  // For Water Temp.
#include &lt;DallasTemperature.h>
#define ONE_WIRE_BUS D3
#define SENSER_BIT 9  // 

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&amp;oneWire);


int LI1 = 0;  // UPPER_WaterLevel
int LI1_PIN = A0;

int LI2 = 0;  // LOWER_WaterLevel
const int LI2_PIN = D9;


volatile int8_t alarmFlag = 0;  //RTC alarm

unsigned long RTC_alarmTime;  // water discharge pump start time
unsigned long AjustStart;     // Ajust Concentration start time
unsigned long AjustTime;


#include &lt;WiFi.h>  //wifi setting
const char SSID[] = "*******";
const char PASSWORD[] = "***********";


#include &lt;Ambient.h>  //Ambient setting
unsigned int channelId = *****;
const char* writeKey = "***************";

WiFiClient client;
Ambient ambient;

#include &lt;time.h>


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

  pinMode(P1, OUTPUT);      //D8
  pinMode(P2, OUTPUT);      //D2
  pinMode(Air, OUTPUT);     //D10
  pinMode(TDS_PIN, INPUT);  //A1
  pinMode(LI1_PIN, INPUT);  //A0
  pinMode(LI2_PIN, INPUT);  //D9
                            //D4 --SDA for RTC
                            //D5 --SCL for RTC
                            //D6 --vacant
                            //D7 --INT from RTC
  pinMode(ONE_WIRE_BUS, INPUT);  //D3 --ONE WIRE BUS, DS18B20

  sensors.setResolution(SENSER_BIT);

  bool connecting = true;
  WiFi.begin(SSID, PASSWORD);
  while (connecting){
    Serial.print("WiFi connecting to the AP: ");
    Serial.println(SSID);
    unsigned long connect_start = millis();
    while (WiFi.status() != WL_CONNECTED &amp;&amp; connect_start + 20000 > millis() ){
      Serial.print(".");
      delay(500);
    }
    if (WiFi.status() == WL_CONNECTED){
      connecting = false;
      delay(500);
    } else {
      Serial.println("retry");
      delay(500);
      esp_deep_sleep(500);
    }
  }

  configTzTime("JST-9", "ntp.nict.jp","ntp.jst.mfeed.ad.jp");
  getLocalTime(&amp;timeInfo);
  rtc.setTime(timeInfo.tm_year +1900, timeInfo.tm_mon +1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);

  rtc.setAlarm(rtc.eSaturday, 10, 0, 0);  //Set weekly timed alarm
  attachInterrupt(digitalPinToInterrupt(D7), interrupt, FALLING);


  Serial.println("connected.");
  Serial.print("IP adress:");
  Serial.println(WiFi.localIP());

  ambient.begin(channelId, writeKey, &amp;client);

}


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

  if (alarmFlag == 1) {
    LI2 = digitalRead(LI2_PIN);
    unsigned long P2_time = millis();
    digitalWrite(P2, HIGH);
    digitalWrite(P1, LOW);
    Serial.println("Water Discharge has been started.");
    unsigned long P2_work_time = P2_time - RTC_alarmTime;
    Serial.println(RTC_alarmTime, DEC);
    Serial.println(P2_time, DEC);
    Serial.println(P2_work_time, DEC);
    Serial.println(LI2);
    delay(1000);

    if (LI2 == 0 || P2_work_time >= 900000) {  //Limit setting for P2, water level or work time
      digitalWrite(P2, LOW);
      Serial.println("Water Discharge was finished.");
      rtc.clearAlarm();
      alarmFlag = 0;
      Serial.println("alarmFlag was cleared.");
      delay(1000);
    }
  } else {

    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(' ');

    LI1 = analogRead(LI1_PIN);  //Check UPPER WaterLevel, Work P1 the Water Charge Pump
    if (LI1 >= 3500) {
      digitalWrite(P1, HIGH);
    } else {
      digitalWrite(P1, LOW);
    }

    Serial.print("UPPER Water Level = ");  //UPPER Water Level
    Serial.println(LI1, DEC);

    if (LI1 >= 3500) {
      ambient.set(1, 80);
    } else {
      (ambient.set(1, 100));
    }


    LI2 = digitalRead(LI2_PIN);  //Check LOWER WaterLevel

    Serial.print("LOWER Water Level = ");  //LOWER Water Level
    Serial.println(LI2, DEC);

    if (LI2 == 1) {
      ambient.set(8, 1);
    } else {
      (ambient.set(8, 0));
    }


    if ((sTime.hour >= 7) &amp;&amp; (sTime.hour &lt;= 17)) {  //aeration start ,stop time
      digitalWrite(Air, HIGH);
    } else {
      digitalWrite(Air, LOW);
    }


    float Water_Temp;  //Water Temp

    sensors.requestTemperatures();
    Water_Temp = sensors.getTempCByIndex(0);
    Serial.print("Water Temp = ");
    Serial.print(Water_Temp);
    Serial.println(" C");

    ambient.set(6, Water_Temp);


    float RTC_TEMP;  //RTC Temp
    RTC_TEMP = rtc.getTemperatureC();
    Serial.print("RTC Temp = ");
    Serial.print(RTC_TEMP);
    Serial.println(" C");

    ambient.set(4, RTC_TEMP);


    int P1_motion = digitalRead(P1);

      ambient.set(2, P1_motion);

    int Air_motion = digitalRead(Air);
    if (Air_motion == HIGH) {
      ambient.set(3, HIGH);
    } else if (Air_motion == LOW) {
      ambient.set(3, LOW);
    }

    TDS_sensor = analogRead(TDS_PIN);  //TDS
    TDS_Voltage = TDS_sensor * 5 / 4096.0;
    TDS = (133.42 * TDS_Voltage * TDS_Voltage * TDS_Voltage - 255.86 * TDS_Voltage * TDS_Voltage + 857.39 * TDS_Voltage) * 0.5;
    Serial.print("TDS = ");
    Serial.print(TDS);
    Serial.println(" ppm");
    Serial.println(" ");

    ambient.set(5, TDS);

    ambient.send();

    delay(1000);
  }
}

void interrupt(void) {
  alarmFlag = 1;
  RTC_alarmTime = millis();
}

長くなったので、とりあえずここまで。次は省電力化検討かTDS値温度依存性かな。

コメントを残す

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

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


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

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

続きを読む