Гидропонная система периодического затопления «Гидрогоршок»
Гидропонные системы – это прогрессивный способ выращивания растений. Основная идея в том, что корни находятся не в почве или субстрате, а в питательном растворе, откуда растение берет все необходимые для роста вещества. Существует много разновидностей таких систем, одна из которых основана на принципе периодического затопления корневой системы. Растение попеременно будет получать и необходимые для роста вещества из питательного раствора, и кислород для того, чтобы корни не начали загнивать.
Видеообзор
Как устроен «Гидрогоршок»
Что понадобится
- Сетчатый гидропонный горшок
- Удобрения для питательного раствора
- Керамзит
- Двойной горшок или два ведёрка разного объёма
Как собрать
- Установи Slot Shield на Arduino Uno.
Не забудь поставить джампер на Slot Shield в положение V2-VIN
- Добавь на Slot Shield светодиод, потенциометр и силовой ключ.
Не забудь поставить джампер на силовом ключе в положение V=P+
- Собери корпус гидрогоршка из двух ёмкостей, как показано на рисунке.
- В верхнем резервуаре просверли отверстие для силиконовой трубки и дренажное отверстие для стекания питательного раствора обратно в нижнюю ёмкость.
Дренажное отверстие не должно быть очень большим, иначе помпа не будет успевать накачать питательный раствор из одной ёмкости в другую.
- Просверли отверстия под датчики уровня и установи их. Помпу помести в нижний резервуар, выведи провод питания наружу и зачисти изоляцию на проводах.
- Установи сетчатый горшочек для гидропоники и засыпь его керамзитом.
- Подключи помпу к силовому ключу, а датчики уровня к цифровым пинам на 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); } }
Первый запуск
- Выставь на блоке питания 12 Вольт и подключи его к Arduino Uno.
- Включи блок питания в розетку.
Светодиодный индикатор горит, значит датчик минимального уровня сообщает о недостатке питательного раствора для начала работы. Доливай питательный раствор до тех пор, пока не сработает датчик максимального уровня. После этого светодиод погаснет и заработает помпа.
Режим работы
После первого затопления корневой системы питательный раствор стекает обратно в нижнюю ёмкость. Таким образом корневая система насыщается и питательными веществами, и кислородом.
По истечении времени, заданного потенциометром, цикл повторится вновь.
Если во время затопления датчик минимального уровня питательного раствора сработает раньше датчика уровня затопления, замигает светодиодный индикатор, сигнализирующий о нехватке питательного раствора. При этом работа системы не прекратится, а последующие затопления будут происходить по графику. Но питательный раствор необходимо долить.
Если во время мигания светодиодного индикатора сработает датчик максимального уровня, индикатор погаснет, и гидрогоршок продолжит работу в штатном режиме.
Гидрогоршок +
Для лучшего роста растению нужен хороший свет. В природе с этим справляется солнце. В помещении света не достаточно, поэтому в продолжении истории о гидропонном горшке, мы решили добавить управление освещением.
Кроме света, растение прихотливо к влажности воздуха в котором оно растет. С поддержанием его на необходимом уровне справится бытовой увлажнитель.
Лампы и увлажнители работают от сети 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; }
Готовое устройство
Размести все элементы внутри герметичного корпуса.
Толщины акриловой крышки достаточно для работы сенсорной кнопки, которой можно переключать отображаемую информации на дисплее.