Гидропонная система периодического затопления «Гидрогоршок»

Гидропонные системы – это прогрессивный способ выращивания растений. Основная идея в том, что корни находятся не в почве или субстрате, а в питательном растворе, откуда растение берет все необходимые для роста вещества. Существует много разновидностей таких систем, одна из которых основана на принципе периодического затопления корневой системы. Растение попеременно будет получать и необходимые для роста вещества из питательного раствора, и кислород для того, чтобы корни не начали загнивать.

Видеообзор

Как устроен «Гидрогоршок»

Что понадобится

Как собрать

  1. Установи Slot Shield на Arduino Uno.

    Не забудь поставить джампер на Slot Shield в положение V2-VIN

  2. Добавь на Slot Shield светодиод, потенциометр и силовой ключ.

    Не забудь поставить джампер на силовом ключе в положение V=P+

  3. Собери корпус гидрогоршка из двух ёмкостей, как показано на рисунке.
  4. В верхнем резервуаре просверли отверстие для силиконовой трубки и дренажное отверстие для стекания питательного раствора обратно в нижнюю ёмкость.

    Дренажное отверстие не должно быть очень большим, иначе помпа не будет успевать накачать питательный раствор из одной ёмкости в другую.

  5. Просверли отверстия под датчики уровня и установи их. Помпу помести в нижний резервуар, выведи провод питания наружу и зачисти изоляцию на проводах.
  6. Установи сетчатый горшочек для гидропоники и засыпь его керамзитом.
  7. Подключи помпу к силовому ключу, а датчики уровня к цифровым пинам на Slot Shield.

Исходный код

Прошей Arduino Uno скетчем

gidroponic.ino
// Пин, к которому подключен датчик минимального уровня питательного раствора
#define LOWLEVEL  7
 
// Пин, к которому подключен датчик максимального уровня питательного раствора
#define MAXLEVEL 6
 
// Пин, к которому подключен датчик уровня заполнения верхнего резервуара
#define FLOODLEVEL 4
 
// Пин, к которому подключен светодиодный индикатор
#define LEDPIN 13
 
// Пин, к которому подключена помпа
#define PUMPPIN 12
 
// Пин, к которому подключен потенциометр регулировки
#define POTPIN A0
 
// Переменные для хранения времени
long previousMillis = 0;
long blinkPreviousMillis = 0;
unsigned long interval; // два часа  = 7200000, 15 минут = 900000
 
// Переменные-флаги для хранения состояния работы гидрогоршка
bool blinkState = 0;
bool waterStart = 0;
bool pumpState = 0;
 
// Переменные для хранения состояния датчиков уровня
bool lowLevelState;
bool maxLevelState;
bool floodLevelState;
 
// Переменная для хранения состояния мигающего светодиода 
bool ledState;
 
void setup() {
  // Притянем пины датчиков уровня к плюсу
  pinMode(LOWLEVEL, INPUT_PULLUP);
  pinMode(MAXLEVEL, INPUT_PULLUP);
  pinMode(FLOODLEVEL, INPUT_PULLUP);
 
  // Установим пины светодиода и помпы как выходы
  pinMode(LEDPIN, OUTPUT);
  pinMode(PUMPPIN, OUTPUT);
 
  // Проверим уровень раствора. Если уровень меньше минимального - зажигаем светодиодный индикатор.
  // Ждем срабатывания датчика максимального уровня, после этого гасим светодиод.
  if (digitalRead(LOWLEVEL)) {
    while (!digitalRead(MAXLEVEL)) {
     digitalWrite(LEDPIN, 1);
    }
  }
  digitalWrite(LEDPIN, 0);
 
  // Сохраняем текущее время для подсчета интервалов затопления.
  previousMillis = millis();
 
  // Устанавливаем флаг для начала работы помпы. 
  pumpState = 1;
}
 
void loop() {
  // Считываем значение потенциометра для установки следующего затопления.
  interval = map(analogRead(POTPIN), 0, 1023, 900000, 7200000);
 
  // Присваиваем переменным состояния датчиков уровня.
  // Некоторые датчики установлены "вверх ногами", поэтому значения с них нужно инвертировать.
  lowLevelState = !digitalRead(LOWLEVEL);
  maxLevelState = digitalRead(MAXLEVEL);
  floodLevelState = digitalRead(FLOODLEVEL);
 
  // Проверяем, настало ли время затапливать корневую систему растения.
  // Если настало - устанавливаем флаг затопления.
  if (millis() - previousMillis > interval) {
    waterStart = 1;
  }
 
  // Если необходимо затапливать корни, и уровень в резервуаре выше минимального -
  // включаем помпу и сбрасываем флаг затопления.
  if (waterStart && lowLevelState) {
    pumpState = 1;
    waterStart = 0;
  }
 
  // Проверяем, в каком состоянии находится флаг работы помпы.
  // В зависимости от этого включаем её или выключаем.
  if (pumpState) {
    digitalWrite(PUMPPIN, HIGH);
  } else {
    digitalWrite(PUMPPIN, LOW);
  }
 
  // Если сработал датчик уровня затопления - выключаем помпу и обновляем переменную хранения времени.
  // Если вдруг раствора стало слишком мало и сработал датчик минимального уровня - выключаем помпу.
  if (!lowLevelState || floodLevelState) {
    pumpState = 0;
    previousMillis = millis();
  }
 
  // Если сработал датчик минимального уровня раствора - устанавливаем флаг мигания светодиода. 
  if (!lowLevelState) {
    blinkState = 1;
  }
 
  // Если установлен флаг мигания светодиода - мигаем им )))
  if (blinkState) {
    Alarm ();
  }
 
  // Если светодиод мигает, сигнализируя о нехватке уровня раствора, и в этот момент сработал
  // датчик максимального уровня - значит раствор долили и светодиод гаснет.
  if (blinkState && maxLevelState) {
    blinkState = 0;
    digitalWrite(LEDPIN, 0);
  }
}
 
// Функция мигания светодиодом
void Alarm () {
  if (millis() - blinkPreviousMillis > 300) {
    blinkPreviousMillis = millis(); 
    if (!ledState) {
      ledState = 1;
    } else {
      ledState = 0;
    }
    digitalWrite(LEDPIN, ledState);
  }
}

Первый запуск

  1. Выставь на блоке питания 12 Вольт и подключи его к Arduino Uno.
  2. Включи блок питания в розетку.

Светодиодный индикатор горит, значит датчик минимального уровня сообщает о недостатке питательного раствора для начала работы. Доливай питательный раствор до тех пор, пока не сработает датчик максимального уровня. После этого светодиод погаснет и заработает помпа.

Режим работы

После первого затопления корневой системы питательный раствор стекает обратно в нижнюю ёмкость. Таким образом корневая система насыщается и питательными веществами, и кислородом.

По истечении времени, заданного потенциометром, цикл повторится вновь.

Если во время затопления датчик минимального уровня питательного раствора сработает раньше датчика уровня затопления, замигает светодиодный индикатор, сигнализирующий о нехватке питательного раствора. При этом работа системы не прекратится, а последующие затопления будут происходить по графику. Но питательный раствор необходимо долить.

Если во время мигания светодиодного индикатора сработает датчик максимального уровня, индикатор погаснет, и гидрогоршок продолжит работу в штатном режиме.

Гидрогоршок +

Для лучшего роста растению нужен хороший свет. В природе с этим справляется солнце. В помещении света не достаточно, поэтому в продолжении истории о гидропонном горшке, мы решили добавить управление освещением.

Кроме света, растение прихотливо к влажности воздуха в котором оно растет. С поддержанием его на необходимом уровне справится бытовой увлажнитель.

Лампы и увлажнители работают от сети 220 Вольт. Такое напряжение опасно для жизни, поэтому мы разместили все компоненты, управляющие этими приборами, в герметичный корпус. Вслед за ними отправились и другие железки. Но обо всём по порядку.

Что понадобится

Видеообзор

Сборка блока управления

Для начала установи тройка шилд на Arduino Uno.

Подключать тройка модули удобно через тройка пады. Подключи их к тройка шилду согласно схеме приведенной ниже.

Затем вставь в них Тройка модули, как показано на рисунке.

Подключи блок питания к Arduino Uno и силовому ключу для помпы, и соедини розетки для увлажнителя и лампы по схеме

Добавь в схему сам гидрогоршок и метеосенсор. Помпу подключи к силовому ключу.

  • Верхний датчик подключи к пину А2;
  • Средний датчик подключи к пину А1;
  • Нижний датчик подключи к пину А0;

Исходный код

В коде программы используются библиотеки для работы с модулями. Скачай и установи их перед прошивкой платы.

gydroponicPlus.ino
// библиотека для работы I²C
#include <Wire.h>
// библиотека для работы с часами реального времени
#include "TroykaRTC.h"
// Подключаем библиотеку для работы с дисплеем
#include <QuadDisplay2.h>
// библиотека для работы с метеосенсором
#include <TroykaMeteoSensor.h>
// библиотека для работы со светодиодной матрицей
#include "TroykaLedMatrix.h"
// библиотека для работы с кнопками
#include <TroykaButton.h>
 
// размер массива для времени
#define LEN_TIME 12
// размер массива для даты
#define LEN_DATE 12
// размер массива для дня недели
#define LEN_DOW 12
 
// создаём объект для работы с часами реального времени
RTC clock;
 
// массив для хранения текущего времени
char time[LEN_TIME];
// массив для хранения текущей даты
char date[LEN_DATE];
// массив для хранения текущего дня недели
char weekDay[LEN_DOW];
 
// создаём объект класса QuadDisplay и передаём номер пина CS
QuadDisplay qd(3);
 
// создаём объект для работы с датчиком
TroykaMeteoSensor meteoSensor;
 
// создаём объект matrix для работы с матрицей
TroykaLedMatrix matrix;
 
// Пин к которому подключен датчик минимального уровня питательного раствора
#define LowLevel  A0
// Пин к которому подключен датчик максимального уровня питательного раствора
#define MaxLevel A1
// Пин к которому подключен датчик уровня заполнения верхнего резервуара
#define FloodLevel A2
// Пин к которому подключена кнопка
#define ButtonPin 2
// Пин к которому подключена помпа
#define PumpPin 4
// Пин к которому подключено реле освещения
#define LightPin 6
// Пин к которому подключено реле увлажнителя воздуха
#define HumPin 5
 
// создаем объект для работы с кнопками (номер пина, длительность удержания кнопки, притяжка к минусу)
TroykaButton button(ButtonPin, 2000, 1);
 
// Переменные для хранения вренени
int hour;
int minute;
// Переменная для хранения времени в минутах
int curent;
// Переменные для хранения времени включения освещения
int hon=5;
int mon=59;
// Переменная для хранения времени включения освещения в минутах
int on;
// Переменные для хранения времени выключения освещения
int hoff=23;
int moff=59;
// Переменная для хранения времени выключения освещения в минутах
int off;
 
// Переменные для хранения миллисекунд
long previousMillis = 0;
long BlinkPreviousMillis = 0;
long DisplayPreviousMillis = 0;
// переменная хранения значения интервала затопления корневой системы
unsigned long interval = 1200000; // два часа  = 7200000, 20 минут = 1200000
 
// Переменные-флаги для хранения состояния работы гидрогоршка
bool blinkState = 0;
bool waterStart = 0;
bool pumpState = 0;
 
// Переменные для хранения состояния датчиков уровня
bool LowLevelState;
bool MaxLevelState;
bool FloodLevelState;
 
// Переменная для хранения состояния индикации об ошибке
bool ErrState;
 
// Переменная для хранения значений температуры
int temperature;
// Переменная для хранения значений относительной влажности
int humidity;
// Переменная для хранения минимального значения относительной влажности
int lowHumidity = 40;
// Переменная режимов отображения
int buttonState = 0;
 
void setup(){
 
  // Инициализируем работу сериал порта
  Serial.begin(9600);
 
  // начало работы с матрицей
  matrix.begin();
  // очищаем матрицу
  matrix.clear();
  // Установим уровень яркости
  matrix.setCurrentLimit(ROW_CURRENT_20MA);
  // устанавливаем шрифт
  matrix.selectFont(FONT_8X8_BASIC);
 
  // Инициализируем работу дисплея  
  qd.begin();
 
  // Инициализируем работу метеосенсора
  meteoSensor.begin();
 
  // Инициализируем работу часов реального времени
  clock.begin();
  // Установим время 
  clock.set(__TIMESTAMP__);
 
  // начало работы с кнопкой
  button.begin();
 
  // Притянем пины датчиков уровня к плюсу
  pinMode(LowLevel, INPUT_PULLUP);
  pinMode(MaxLevel, INPUT_PULLUP);
  pinMode(FloodLevel, INPUT_PULLUP);
 
  // Установим пины контроллера как выходы и входы
  pinMode(LightPin, OUTPUT);
  pinMode(ButtonPin, INPUT);
  pinMode(PumpPin, OUTPUT);
  pinMode(HumPin, OUTPUT);
 
  // Проверим уровень раствора.
  // Если уровень меньше минимального - отобразим на матрице восклицательный знак.
  // Ждем пока не сработает датчик максимального уровня, после этого очистим матрицу.
  if (digitalRead(LowLevel)){
    while (!digitalRead(MaxLevel)){
     matrix.drawSymbol('!');
     qd.displayDigits(QD_NONE, QD_L, QD_0, QD_L);
    }
  }
  matrix.clear();;
 
  // Сохраняем текущее время для подсчета интервалов затопления.
  previousMillis = millis();
 
  // Устанавливаем флаг для начала работы помпы. 
  pumpState = 1;
}
 
void loop(){
  // считываем данные с датчика
  int stateSensor = meteoSensor.read();
 
  // Проверяем ответ и сохраняем значения температуры 
  // и относительной влажности в переменные
  switch (stateSensor) {
    case SHT_OK:
      temperature = meteoSensor.getTemperatureC();
      humidity = meteoSensor.getHumidity();
      break;
      // Ошибка получения данных
      case SHT_ERROR_DATA:
      Serial.println("Data error or sensor not connected");
      break; 
      // Ошибка контрольной суммы
      case SHT_ERROR_CHECKSUM:
      Serial.println("Checksum error");
      break;
  }
 
  // запрашиваем данные с часов
  clock.read();
  hour = clock.getHour();
  minute = clock.getMinute();
 
  // Переводим текущее время и время работы освещения в минуты
  curent=timeto(hour,minute);
  on=timeto(hon, mon);
  off=timeto(hoff , moff );
 
  // Сравниваем текущее время с временем работы освещения
  if (on>off){
  if ((curent>=on)||(curent<off)){
    digitalWrite(LightPin,HIGH);
  }else{
    digitalWrite(LightPin,LOW);
  }
  }
 
  if (on<off){
  if ((curent>=on)&&(curent<off)){
    digitalWrite(LightPin,HIGH);
  }else{
    digitalWrite(LightPin,LOW);
  }
  }
 
// Сравниваем текущее значение относительной влажности с установленным
  if(humidity < lowHumidity){
    digitalWrite(HumPin, HIGH);
  }
  if( humidity > (lowHumidity+10)){
    digitalWrite(HumPin, LOW);
  }
 
 
  // считывание данных с кнопки
  button.read();
  // опеределяем клик кнопки
  if (button.justPressed()) {
    if (buttonState < 2){
      buttonState = buttonState + 1;
      DisplayPreviousMillis = millis();
    } else {
      buttonState = 0;
      matrix.clear();
    }
  }
  // если после переключения режимов прошло 5 секунд-
  // сбрасываем счетчик нажжатий для отображения времени
  if( (millis())- DisplayPreviousMillis > 5000){
    buttonState = 0;
    matrix.clear();
  }
 
  // выводим значения на дисплей
  if(!blinkState){
    switch (buttonState) {
    case 0: 
      qd.displayScore(hour, minute, true); 
      break;
    case 1: 
        qd.displayTemperatureC(temperature);
        matrix.drawSymbol('T');
      break; 
    case 2: 
        qd.displayHumidity(humidity);
        matrix.drawSymbol('H');
      break;    
  }
  } else {
    qd.displayDigits(QD_NONE, QD_L, QD_0, QD_L);
  }
 
 
  // Присваиваем переменным состояния датчиков уровня.
  // Некоторые датчики установлены "вверх ногами", поэтому значения с них нужно инвертировать.
  LowLevelState = !digitalRead(LowLevel);
  MaxLevelState = digitalRead(MaxLevel);
  FloodLevelState = digitalRead(FloodLevel);
 
  // Проверяем настало ли время затапливать корневую систему растения.
  // Если настало - устанавливаем флаг затопления.
  if (millis() - previousMillis > interval){
    waterStart = 1;
  }
 
  // Если необходимо затапливать корни, и уровень в резервуаре выше минимального -
  // включаем помпу и сбрасываем флаг затопления.
  if (waterStart && LowLevelState){
    pumpState = 1;
    waterStart = 0;
  }
 
  // Проверяем в каком состоянии находится флаг работы помпы.
  // В зависимости от этого включаем её или выключаем.
  if (pumpState){
    digitalWrite(PumpPin, HIGH);
  } else{
    digitalWrite(PumpPin, LOW);
  }
 
  // Если сработал датчик уровня затопления - выключаем помпу и обновляем переменную хранения времени.
  // Если вдруг раствора стало слишком мало и сработал датчик минимального уровня - выключаем помпу.
  if (!LowLevelState || FloodLevelState){
    pumpState = 0;
    previousMillis = millis();
  }
 
  // Если сработал датчик минимального уровня раствора - устанавливаем флаг отображения ошибки на матрице. 
  if (!LowLevelState){
    blinkState = 1;
  }
 
  // Если установлен флаг отображения ошибки на матрице - отображаем её )))
  if (blinkState){
    alarm ();
  }
 
  // Если восклицательный знак ошибки мигает сигнализируя, что уровня раствора не достаточно
  // и в этот момент сработал датчик максимального уровня - значит раствор долили и ошибка гаснет.
  if (blinkState && MaxLevelState){
    blinkState = 0;
    matrix.drawSymbol(' ');
  }
}
 
// Функция мигания восклецательного знака
void alarm (){
  if (millis() - BlinkPreviousMillis > 300){
    BlinkPreviousMillis = millis(); 
    if (!ErrState){
      ErrState = 1;
      matrix.drawSymbol('!');
    } else{
      ErrState = 0;
      matrix.clear();
    }
  }
}
 
// Функция перевода времени в минуты
int timeto(int x, int y){
  int z;
  z=(60*x)+y;
  return z;
}

Готовое устройство

Размести все элементы внутри герметичного корпуса.

Толщины акриловой крышки достаточно для работы сенсорной кнопки, которой можно переключать отображаемую информации на дисплее.