КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Гидропоника. Домашняя установка для выращивания растений.

Общие сведения

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

Плюсов у этого метода множество:

  • растения растут быстрее за счёт наличия в воде в достаточном количестве необходимых питательных веществ;
  • нет грязи, что особенно актуально при выращивании растений дома;
  • очень сложно пересушить растения, или забыть их полить — уровень влажности всегда оптимальный;
  • корневая система растений не такая большая как при традиционном созревании, что позволяет направить ресурсы развития на плодоношение, а не на развитие корней. 

Видео

Нам понадобится

Конструкционные материалы

  • 1х Тройник ПВХ 110мм
  • 1х Муфта двухраструбная 110мм
  • 2х Заглушка 110мм
  • 2х Хомуты 110мм крепления тройника
  • 1х Горшок
  • Керамзит (мы использовали мелкий в перемешку с мелкими камнями)
  • Питательный раствор
  • 1х Силиконовый шланг 4*6 мм, 1 метр 
  • 1х Силиконовый шланг 6*8 мм, 1 метр
  • 1х Воздушный компрессор или насос (например, используемые для аэрации в аквариумистике)
  • 1х Аэратор (камень) для равномерной аэрации воды. Можно обойтись без него, проделав отверстия в силиконовой трубочке
  • 1x Обратный клапан (для исключения попадания воды в воздушный компрессор)
  • 1х Соединитель для трубки, 4мм
  • 1х Set Box XL (конструктор ПВХ)
  • 1х Крепления LCD2004 (конструктор ПВХ)
  • Основа для крепления всех конструкционных элементов
  • Материал для стенок + светоотражатель (у нас - ПВХ, обклеенный фольгой)
  • Расходники в виде клея, герметика, стяжек и пр.

Электроника

Библиотеки

Принцип работы

Для выращивания растений методом гидропоники необходим питательный раствор. Концентрат для его приготовления можно приобрести в специализированных магазинах. Готовится он по инструкции на упаковке и уже в таком виде подаётся в ёмкость, в которой находится горшок с растением. Для долива также используется уже приготовленный раствор. Подробнее о конструкции нашей установки и принципах выращивания растений методами гидропоники вы можете посмотреть в этом видео.

Для подачи питательного раствора в ёмкость используем мембранный насос, а для контроля уровня - два уровнемера. В качестве основного служит ёмкостный датчик влажности почвы, а поплавковый уровнемер выполняет защитную функцию. При его срабатывании насос отключается. 

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

Модуль RTC отвечает за отсчёт времени. Управление производится с помощью энкодера

Подсветка экрана работает в дневное время постоянно, а в ночное можно настроить (в коде прошивки): будет она включена или нет. В любом случае, она включается при активности пользователя и, в случае выключения, отключается через определенное время.

Реализованные защиты

  • Защита от переполнения ёмкости - с помощью поплавкового уровнемера.
  • Защита от переполнения ёмкости посредством ограничения времени работы насоса - если уровень поды не увеличивается в течение заданного времени, насос выключается, а на экране появляется соответствующее уведомление. Работа системы возобновляется после любых действий пользователя в меню.
  • Защита от отключения питания. Модуль RTC самостоятельно ведёт учёт времени, поэтому при отключении питания оно не сбивается. Все настройки и статистика сохраняются в энергонезависимую память в 00:00. То есть в случае отключения питания, при последующем запуске системы применятся настройки, установленные на момент предыдущего дня. Статистика также отобразится на момент прошлого дня. 

Схема подключения

Особенности подключения

  • Установка расходомера не обязательна — он лишь выполняет функцию сбора статистики о количестве израсходованной воды. Для того, чтобы подключить его к Trema Shield'у, необходимо припаять 3-проводной шлейф "мама" вместо его стандартного штекера.
  • Освещение подключается через нормально замкнутые контакты реле. То есть, когда ток на катушку реле не подаётся, лампочка горит. Такое решение обусловлено минимизацией времени подачи питания на реле, ведь большую часть времени суток освещение будет включено. 
  • Мы рекомендуем поставить керамический конденсатор параллельно насосу (можно припаять прямо на его выводы вместе с проводами). Это позволит уменьшить помехи и увеличить стабильность работы схемы.

Управление системой

Главный экран:

При включении попадаем в меню Посадки

Выбираем общее количество дней роста (это тот период, который обычно указан на упаковке с семенами). Также выбираем текущий день. По умолчанию — первый, но если вы используете систему для уже выросших растений, то указываете другой (сколько дней этим растениям).

Далее идёт Время досвета — это то время, в которое автоматически будет включаться и выключаться освещение. Рекомендуется около 16 часов в сутки — мы указали с 7-ми до 23-х.

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

С этого момента система работает автоматически. Но рассмотрим остальные пункты меню.

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

В Посадке мы уже были, давайте зайдём в пункт Слив. Пока мы находимся здесь, насос не будет включаться и мы можем выполнять работы по смене воды. Мы меняли воду примерно раз в 1,5 — 2 недели, при этом зацветания не происходило.

После нажатия кнопки Готово мы снова попадаем в экран залива жидкости и заполняем резервуар аналогично тому, как показывали ранее. Нажимаем Готово и работа системы возобновляется.

Последний пункт меню — Настройки. Здесь можно настроить время и дату, а также мощность освещения. 

Эта информация понадобится для того, чтобы рассчитать расход электроэнергии на весь период выращивания растений.

Ну и вот как выглядит главный экран. В первой строке — дата и время, далее — суммарный расход воды и электричества. Строчкой ниже — количество оставшихся дней, и шкала, заполняющаяся по мере роста.

Как только число дней подойдёт к концу, надпись сменится на Готово.

Сбросить статистику по расходу воды и электричества можно, если установить число дней в меню Посадка, равным 0.

В начале скетча вы найдёте дополнительные настройки, которые можете изменить при необходимости. 

Навигация по экранам выглядит следующим образом:

Скетч

Особенности загрузки

Перед загрузкой программы в контроллер, необходимо раскомментировать строку с установкой времени в RTC-модуль watch.settime(i[0],i[1],i[2],i[3],i[4],i[5]); (находится в начале блока setup()). После загрузки необходимо закомментировать эту строку и снова загрузить прошивку. Это необходимо для корректной установки времени в RTC-модуль. В противном случае, отсчёт времени начнётся заново при возобновлении питания после его отключения. 

Эту операцию можно выполнить только один раз, даже если вы будете менять скетч и несколько раз загружать код программы.

#include <Wire.h>                           //   Подключаем библиотеку для работы с аппаратной шиной I2C.
#include <iarduino_I2C_Encoder.h>           //   Подключаем библиотеку для работы с энкодером I2C-flash.
#include <LiquidCrystal_I2C.h>              //  Подключаем библиотеку для работы с LCD дисплеем по шине I2C
#include <iarduino_RTC.h>                   //  Подключаем библиотеку для работы с модулем реального времени
#include <EEPROM.h>                         //  Подключаем библиотеку для работы с энергонезависимой памятью

#define INIT_ADDR 1023                      // номер резервной ячейки (для EEPROM)
#define INIT_KEY 26                         // ключ первого запуска. 0-254, на выбор (для EEPROM)

iarduino_I2C_Encoder enc(0x09);             //  Объявляем объект enc для работы с функциями и методами библиотеки iarduino_I2C_Encoder, указывая адрес модуля на шине I2C. 
LiquidCrystal_I2C lcd(0x27,20,4);           //  Объявляем объект библиотеки, указывая параметры дисплея (адрес I2C = 0x27, количество столбцов = 20, количество строк = 4)
iarduino_RTC watch(RTC_DS3231);             //  Объявляем объект watch для модуля на базе чипа DS3231

//  Определяем системное время:                           //  Время загрузки скетча.
const char* strM="JanFebMarAprMayJunJulAugSepOctNovDec";  //  Определяем массив всех вариантов текстового представления текущего месяца.
const char* sysT=__TIME__;                                //  Получаем время компиляции скетча в формате "SS:MM:HH".
const char* sysD=__DATE__;                                //  Получаем дату  компиляции скетча в формате "MMM:DD:YYYY", где МММ - текстовое представление текущего месяца, например: Jul.
//  Парсим полученные значения sysT и sysD в массив i:    //  Определяем массив «i» из 6 элементов типа int, содержащий следующие значения: секунды, минуты, часы, день, месяц и год компиляции скетча.
const int i[6] {(sysT[6]-48)*10+(sysT[7]-48), (sysT[3]-48)*10+(sysT[4]-48), (sysT[0]-48)*10+(sysT[1]-48), (sysD[4]-48)*10+(sysD[5]-48), ((int)memmem(strM,36,sysD,3)+3-(int)&strM[0])/3, (sysD[9]-48)*10+(sysD[10]-48)};

#define lampPin 7                   //  Реле (освещение) 7 пин
#define pompPin 3                   //  Насос долива воды 3 пин
#define levelPin 4                  //  Поплавковый датчик уровня 4 пин
const uint8_t  pinConsupt = 2;      //  Определяем № вывода Arduino, к которому подключён датчик расхода воды

///////////////////НАСТРОЙКИ///////////////////////////
uint8_t timeOn = 7;                 //  Время ВКЛючения освещения и подсветки дисплея   ---РЕКОМЕНДУЕМОЕ ВРЕМЯ ОСВЕЩЕНИЯ РАСТЕНИЙ 16 ЧАСОВ В СУТКИ---
uint8_t timeOFF = 23;               //  Время ВЫКЛючения освещения и подсветки дисплея (если установлен флаг выключать LCD на ночь)    
#define lcdOffFlag true             //  Флаг выключения подсветки LCD на ночь: true если выключать ночью, false - если не выключать ночью
#define floatSensor true            //  Наличие поплавкового уровнемера
uint8_t grownPlantDays = 30;        //  Необходимо дней для выращивания растения (согласно инструкции на упаковке)
uint8_t plantAge = 0;               //  Текущий возраст растения на момент запуска системы (также настраивается в меню настроек)
uint16_t powerLamp = 60;            //  Мощность используемых ламп
#define waterLowLevel 620           //  Уровень воды "сухого" датчика
#define waterHighLevel 365          //  Уровень воды полностью погруженного в воду датчика
uint16_t waterSetLevel = 460;       //  Установленный уровень воды  
#define timeLcdOn 60                //  Время работы подсветки дисплея после активности, с
#define deltaWaterLevel 5           //  Разница установленного и реального уровней, при которой включится насос         
#define pompMaxTime 20              //  Максимальное время работы насоса, после которого произойдет его выключение и появится сообщение об ошибке, сек. (не влияет на режим залива воды при посадке)
///////////////////////////////////////////////////////
 
uint8_t D, M, Y, h, m, s;           //  Объявляем переменные для получения даты и времени: D-день, M-месяц, Y-год, h-часы, m-минуты, s-секунды
uint8_t old_s, old_h, old_D;        //  Предыдущее значение секунд, часов, дней
uint32_t timeBlink;                 //  Временная переменная для периода мигания курсора            
bool visible = true;                //  Переменная видимости курсора
bool sChangeFlag = true, hChangeFlag = true, DChangeFlag = true;    //  флаг изменения переменных - секунды, часы, дни. 
bool homeChangeFlag = true; bool mainMenuChangeFlag = false; bool screen_5_Update =  false; bool screen_3_Update = false; bool screen_6_Update = false; bool screen_7_Update = false; bool screen_4_Update = false; bool screen_8_Update = false;  //  Флаги отрисовки экранов              
bool lampState = false; bool pompState = false;   //  Состояния реле - лампы и силового ключа - насоса
uint8_t screen = 5;                 //  Текущий видимый экран
int8_t cursorPosition = 0;          //  Текущая позиция курсора (отвечает за выбор активного пункта меню)
int8_t encSteps;                    //  Текущее значение шагов энкодера
bool cursorActive = false;          //  Флаг активности курсора (при горизонтальном перемещении стрелки в главном меню)
float waterConsupt = 0.0;           //  Текущий расход воды
float lumConsupt = 0.0;             //  Текущий расход энергии на освещение
uint16_t waterLevel;                //  Значение с датчика уровня
uint16_t waterLevelPercent;         //  Уровень воды, в %
volatile uint16_t varCount  = 0;    //  Определяем переменную для подсчёта количества импульсов поступивших от датчика расхода
uint8_t countTacts = 0;             //  Счётчик тактов для задержки срабатывания насоса при изменении уровня
uint32_t timeOnBackLigth;           //  Время включения подсветки дисплея
uint32_t pompStartTime;             //  Время включения насоса
bool pompError = false;             //  Флаг ошибки насоса (работал слишком долго без изменения уровня)
bool startFlag = true;              //  Флаг при включении контроллера
bool noSetFlag = true;

uint8_t symbol[7][8] = {            //  Задаём пользовательские символы (русские недостающие буквы)
  {B01111,B00101,B00101,B01001,B10001,B11111,B10001,B00000,}, // Д
  {B11111,B10001,B10001,B10001,B10001,B10001,B10001,B00000,}, // П
  {B10001,B10011,B10011,B10101,B11001,B11001,B10001,B00000,}, // И
  {B10000,B10000,B10000,B11110,B10001,B10001,B11110,B00000,}, // Ь
  {B10101,B10101,B10101,B10101,B10101,B10101,B11111,B00001,}, // Щ
  {B11111,B10001,B10000,B10000,B10000,B10000,B10000,B00000,}, // Г
  {B00011,B00111,B00101,B00101,B01101,B01001,B11001,B00000,}, // Л
};
  long x = 0;
  
void setup() {
  pinMode(lampPin, OUTPUT);             //  Реле (освещение) - выход
  pinMode(pompPin, OUTPUT);             //  Силовой ключ (насос) - выход
  pinMode(levelPin, INPUT_PULLUP);      //  Поплавковый уровнемер - вход, подтяжка включена
  watch.begin();                        //  Инициируем RTC модуль
  
  // --- !!!  СТРОКУ НИЖЕ НУЖНО РАСКОММЕНТИРОВАТЬ, ЗАГРУЗИТЬ СКЕТЧ, ЗАКОММЕНТИРОВАТЬ, И СНОВА ЗАГРУЗИТЬ СКЕТЧ. ТОЛЬКО ТАК ВРЕМЯ БУДЕТ УСТАНОВЛЕНО КОРРЕКТНО
  //watch.settime(i[0],i[1],i[2],i[3],i[4],i[5]);         // Устанавливаем время в модуль: i[0] сек, i[1] мин, i[2] час, i[3] день, i[4] месяц, i[5] год, без указания дня недели.
  
  Serial.begin(9600);                   //  Инициализация последовательного порта
  enc.begin();                          //  Инициируем работу с энкодером
  lcd.init();                           //  Инициируем работу с LCD дисплеем
  lcd.clear();                          //  Очищаем дисплей
  lcd.backlight();                      //  Включаем подсветку LCD дисплея
  lcd.createChar(1, symbol[0]);         //  Загружаем пользовательские символы в ОЗУ дисплея
  lcd.createChar(2, symbol[1]);                 
  lcd.createChar(3, symbol[2]);                
  lcd.createChar(4, symbol[3]);                
  lcd.createChar(5, symbol[4]);                
  lcd.createChar(6, symbol[5]);                
  lcd.createChar(7, symbol[6]);

  if (EEPROM.read(INIT_ADDR) != INIT_KEY) {     // первый запуск
    EEPROM.write(INIT_ADDR, INIT_KEY);          // записываем ключ
    eepromUpdate();
    Serial.println ("Данные обновлены");
  }
  else{
    EEPROM.get(0, waterConsupt);                     
    EEPROM.get(4, lumConsupt);
    EEPROM.get(8, timeOn);
    EEPROM.get(9, timeOFF);
    EEPROM.get(10, powerLamp);
    EEPROM.get(12, plantAge);
    EEPROM.get(13, grownPlantDays);
    EEPROM.get(14, waterSetLevel);
    Serial.println ("Данные считаны");
  }
  
  pinMode(pinConsupt, INPUT);                                 // Конфигурируем вывод к которому подключён датчик, как вход
  uint8_t intConsupt = digitalPinToInterrupt(pinConsupt);     // Определяем № прерывания который использует вывод pinSensor
  attachInterrupt(intConsupt, funCountInt, RISING);           // Назначаем функцию funCountInt как обработчик прерываний intConsupt при каждом выполнении условия RISING - переход от 0 к 1

  screen_5_Update = true;                                     //  Для отрисовки экрана посадки при запуске
  watch.gettime();                                            // Считываем текущее время из модуля.
  D = watch.day;                                              // Получаем текущий день месяца 1-31.
  old_D = D;   
}
void funCountInt(){varCount++;}                               // Определяем функцию, которая будет приращать значение счётчика импульсов с расходомера

void eepromUpdate(){
  EEPROM.put(0, waterConsupt);                     
  EEPROM.put(4, lumConsupt);
  EEPROM.put(8, timeOn);
  EEPROM.put(9, timeOFF);
  EEPROM.put(10, powerLamp);
  EEPROM.put(12, plantAge);
  EEPROM.put(13, grownPlantDays);
  EEPROM.put(14, waterSetLevel);
}

void loop(){
  waterLevel = constrain(analogRead(A0), waterHighLevel, waterLowLevel);  //  Ограничиваем значение с ёмкостного датчика уровня минимальным и максимальным значением
  watch.gettime();                                    // Считываем текущее время из модуля.
    D = watch.day;                                    // Получаем текущий день месяца 1-31.
    M = watch.month;                                  // Получаем текущий месяц       1-12.
    Y = watch.year;                                   // Получаем текущий год         0-99.
    h = watch.Hours;                                  // Получаем текущие часы        0-23.
    m = watch.minutes;                                // Получаем текущие минуты      0-59.
    s = watch.seconds;                                // Получаем текущие секунды     0-59.
  if(old_s != s){                                     // Если прошла секунда
    sChangeFlag = true; 
    old_s = s;          

    if (screen == 1 && lcdOffFlag && !((h >= timeOn && h < timeOFF) || (timeOn >= timeOFF && (h > timeOn || h < timeOFF))) && (timeOnBackLigth + timeLcdOn*1000 < millis())) lcd.noBacklight();  //  условие выключения подсветки дисплея
    else lcd.backlight();                                              //  Включаем подсветку LCD дисплея
    
    if (old_h != h){hChangeFlag = true; old_h = h;}                    //  Прошел час
    if (old_D != D){                                                   //  Прошел день
      DChangeFlag = true; 
      old_D = D; 
      plantAge += 1;
      eepromUpdate();
    }
    waterConsupt += (float)varCount/4.575;                             //  4575 - кол-во импульсов при прохождении 1л. 
    x += varCount;
    Serial.println (x);
    varCount = 0;
    if (lampState == true) lumConsupt += powerLamp/3600.0;             // мощность в сукунду
    
    if (screen != 7 && screen != 8){                                   //  Определение состояния насоса
      if (!pompState && !pompError && (waterSetLevel+deltaWaterLevel < waterLevel)){
        countTacts += 1;
        if (countTacts >= 5) {
          countTacts = 0;
          pompState = true;
          pompStartTime = millis();
        }
      }
      if (waterLevel <= waterSetLevel){                                // При достижении заданного уровня выключим насос
        pompState = false;
      }
      if (screen == 1 && pompState && (pompStartTime + pompMaxTime*1000 < millis())){ // Если насос работает автоматически (главный экран) и прошло контрольное время его работы
        pompState = false;                                                            // Выключаем насос и устанавливаем флаг ошибки
        pompError = true;      
      }
    } 
    if ((floatSensor && !digitalRead(levelPin)) ||  noSetFlag) digitalWrite(pompPin, LOW); // выключаем насос если сработал попловковый уровнемер, или идёт настройка уровня жидкости в Экране 7
    else if (screen == 1 || screen == 7) digitalWrite(pompPin, pompState);  
    else digitalWrite(pompPin, LOW); 
    
    if (((h >= timeOn && h < timeOFF) || (timeOn >= timeOFF && (h > timeOn || h < timeOFF))) && screen == 1) lampState = true;  //  Определение состояния освещения
    else lampState = false;    
    digitalWrite(lampPin, !lampState);                     //  ВЫКЛючить лампу, если мы не на главном экране
  }

if (pompError) blinkSymbol(0, 2, 19, "\2PE\1E\7 BPEM. HACOCA "); // При наличии ошибки, выводим уведомление

switch (screen){                 //  Вызов функций в зависимости от видимого экрана
      case 1: homeScreen();      //  Домашний экран
        break;
      case 2: mainMenu();        //  Главное меню
        break;
      case 3: screen_3();        //  Настройка RTC
        break;
      case 4: screen_4();        //  Мощность ламп
        break;
      case 5: screen_5();        //  Рост, дней
        break;
      case 6: screen_6();        //  Время досвета
        break;    
      case 7: screen_7();        //  Уровень раствора
        break;  
      case 8: screen_8();        //  Слив
        break;  
    }

if ((enc.getButton(KEY_PUSHED)) || startFlag){       //  Если кнопка нажата
  pompError = false;
  startFlag = false;
  lcd.backlight();                    //  Включаем подсветку экрана
  timeOnBackLigth = millis();
    if (screen == 1){                 //  Если мы  на домашнем экране
      screen = 2;                     //  Переходим на главный экран настроек
      lcd.clear();                    //  Очищаем экран
      cursorPosition = 0;             //  Устанавливаем курсор в 0 (курсор - активный элемент на экране)
      cursorActive = true;            //  Курсор активен (можем прокручивать вверх и вниз - только для главного меню)
      mainMenuChangeFlag = true;      //  Главное меню изменилось (требуется отрисовка на экране)
      return;                         
    }
    if (screen == 2){
      cursorActive = false;
      if (cursorPosition == 0) {
        screen = 5;
        lcd.clear();
        cursorPosition = 0;
        screen_5_Update = true;
      }
      if (cursorPosition == 1) {
        screen = 8;
        lcd.clear();
        cursorPosition = 0;
        screen_8_Update = true;
      }
      if (cursorPosition == 2) {
        screen = 3;
        lcd.clear();
        cursorPosition = 0;
        screen_3_Update = true;
      }
      if (cursorPosition == 3) {
        screen = 1;
        lcd.clear();
        cursorPosition = 0;
        homeChangeFlag = true;
      }
    }
    if (screen == 3) {
      cursorPosition += 1;
      if (cursorPosition == 7) {
        screen = 4;
        lcd.clear();
        cursorPosition = 0;
        screen_4_Update = true;
      }
    }
    if (screen == 4) {
      cursorPosition += 1;
    if (cursorPosition == 3) {
        screen = 1;
        lcd.clear();
        cursorPosition = 0;
        homeChangeFlag = true;
      }
    }
    if (screen == 5) {
      cursorPosition += 1;
      if (cursorPosition >= 4) {
        screen = 6;
        lcd.clear();
        cursorPosition = 0;
        screen_6_Update = true;
      }
      if (plantAge == 0){                     //  Если возраст растения 0, сбрасываем статистику
        waterConsupt = 0.0; varCount = 0;
        lumConsupt = 0.0;
        eepromUpdate();
      }
    }
    if (screen == 6) {
      cursorPosition += 1;
      if (cursorPosition >= 4) {
        noSetFlag = false;
        screen = 7;
        lcd.clear();
        cursorPosition = 0;
        screen_7_Update = true;
      }
    }
    if (screen == 8) {
       cursorPosition += 1;
       if (cursorPosition >= 2) {
        screen = 7;
        lcd.clear();
        cursorPosition = 0;
        screen_7_Update = true;
      }
    }
    if (screen == 7) {
      cursorPosition += 1;
      if (cursorPosition >= 3) {
        screen = 1;
        lcd.clear();
        cursorPosition = 0;
        homeChangeFlag = true;
        eepromUpdate();
        //timeOnBackLigth = millis();
      }
      if (cursorPosition != 1) {
        pompState = false;
        pompStateUpdate();
      } 
    }
}   // ^^^^^^ IF PUSHED ^^^^^^^^^

  if (screen == 1) {        
    if (enc.getEncoder()) {                         // Сброс шагов вращения энкодера если мы не в режиме настроек
      lcd.backlight();
      timeOnBackLigth = millis();
    }
  }
  encSteps = enc.getEncoder();                      // Получаем число шагов энкодера

  if (encSteps && cursorActive){
    if (encSteps > 0) {
      if (cursorPosition >= 3) cursorPosition = 3; 
      else {
        cursorPosition += 1; 
        lcd.setCursor(0,cursorPosition-1); 
        lcd.write(254);                             //  Выводим пустой символ
      }
    }
    else {
      if (cursorPosition <= 0) cursorPosition = 0; 
      else {
        cursorPosition -= 1; 
        lcd.setCursor(0,cursorPosition+1); 
        lcd.write(254);
      }
    }
  }

  if (screen == 3){
    if (cursorPosition == 1){
      h += encSteps;
      if (h < 0) h = 0;
      if (h > 23) h = 0;
      if (h != watch.gettime("H")) watch.settime(-1, -1, h);
      hUpdate();
    }
    if (cursorPosition == 2){
      m += encSteps;
      if (m < 0) m = 0;
      if (m > 59) m = 0;
      if (m != watch.gettime("i")) watch.settime(-1, m);
      mUpdate();
    }
    if (cursorPosition == 3){
      D += encSteps;
      if (D < 1) D = 1;
      if (D > 31) D = 1;
      if (D != watch.gettime("d")) watch.settime(-1, -1, -1, D);
      DUpdate();
    }
    if (cursorPosition == 4){
      M += encSteps;
      if (M < 1) M = 1;
      if (M > 12) M = 1;
      if (M != watch.gettime("d")) watch.settime(-1, -1, -1, -1, M);
      MUpdate();
    }
    if (cursorPosition == 5){
      Y += encSteps;
      if (Y < 0) Y = 0;
      if (Y > 99) Y = 0;
      if (Y != watch.gettime("d")) watch.settime(-1, -1, -1, -1, -1, Y);
      YUpdate();
    }
    if (cursorPosition == 6){
      blinkSymbol(14, 3, 5, "\1A\7EE");
    }
  }

  if (screen == 4){
    if (cursorPosition == 1){
      powerLamp += encSteps*10;
      if (powerLamp >= 990) powerLamp = 0;  // Сбрасываем можность лампы при значении выше 990Вт
      if (powerLamp < 0 ) powerLamp = 0;    // Не даём установить можность меньше 0
      powerLampUpdate();
    }
    if (cursorPosition == 2){
       blinkSymbol(13, 3, 6, "\6OTOBO");
       powerLampUpdate();
    }
  }
  
  if (screen == 5){
    if (cursorPosition == 1){
      grownPlantDays += encSteps;
      if (grownPlantDays <= 1 || grownPlantDays > 200) grownPlantDays = 1;
      grownPlantUpdate();
    }
    if (cursorPosition == 2){
      plantAge += encSteps;
      if (plantAge <= 0 || plantAge > 200) plantAge = 0;
      if (plantAge > grownPlantDays) plantAge = grownPlantDays;
      plantAgeUpdate();
    }
    if (cursorPosition == 3){
      blinkSymbol(14, 3, 5, "\1A\7EE");
    }
  }
  
  if (screen == 6){
    if (cursorPosition == 1){
      timeOn += encSteps;
      if (timeOn > 23) timeOn = 0;
      if (timeOn <= 0) timeOn = 0;
      timeLightOnUpdate();
    }
    if (cursorPosition == 2){
      timeOFF += encSteps;
      if (timeOFF > 23) timeOFF = 0;
      if (timeOFF <= 0) timeOFF = 0;
      timeLightOffUpdate();
    }
    if (cursorPosition == 3){
      blinkSymbol(14, 3, 5, "\1A\7EE");
    }
  }
  if (screen == 7){
    waterLevelPercent = map(waterLevel, waterLowLevel, waterHighLevel, 0, 100);  
    if (cursorPosition == 1){
      if (encSteps){
        pompState = !pompState;
        pompStateUpdate();
      }
    }
    if (pompState) {
      waterSetLevel = waterLevel;
    }
    if (cursorPosition == 2){
        blinkSymbol(13, 3, 6, "\6OTOBO");
      }
  }

  if (screen == 8){
    if (cursorPosition == 1){
      blinkSymbol(13, 3, 6, "\6OTOBO");
    }
  }
 DChangeFlag = false; hChangeFlag = false; sChangeFlag = false; //  Сбрасываем флаги изменения парамтров
}

void homeScreen(){
  if (homeChangeFlag){
    homeChangeFlag = false;
    lcd.setCursor(0,0);                          
    lcd.print("  -  -   |    :  :  ");
    lcd.setCursor(0,1);  
    lcd.print("       \7 |       KBT");
    
    lcd.setCursor(0,2);
    lcd.print("OCTA\7OC\4, \1H: ");
    DChangeFlag = true; hChangeFlag = true; sChangeFlag = true;
  }
  if (DChangeFlag){
    lcd.setCursor(0,0);                           //  Устанавливаем курсор
    if (D < 10) {                                 //  Корректируем отображение (добавляем 0) если параметр меньше 10
      lcd.print("0");
      lcd.setCursor(1, 0);
      lcd.print(D);                               //  выводим параметр
    }
    else lcd.print(D);
    
    lcd.setCursor(3,0);                          
    if (M < 10) {                            
      lcd.print("0");
      lcd.setCursor(4, 0);
      lcd.print(M);                        
    }
    else lcd.print(M);

    lcd.setCursor(6,0);                       
    if (Y < 10) {                             
      lcd.print("0");
      lcd.setCursor(7, 0);
      lcd.print(Y);                         
    }
    else lcd.print(Y);

    lcd.setCursor(14,2);  
    if (grownPlantDays - plantAge < 0) lcd.print(0);
    else {
      lcd.print("   ");
      lcd.setCursor(14,2);
      lcd.print(grownPlantDays - plantAge);
    }
    lcd.setCursor(15,3);
    if (plantAge < grownPlantDays) lcd.print("POCT"); 
    else {lcd.print("\6OTOB");}
  }
  if (hChangeFlag){
    lcd.setCursor(12, 0);
    if (h < 10) {
      lcd.print("0");
      lcd.setCursor(13, 0);
      lcd.print(h);
    }
    else lcd.print(h);
  }
  if (sChangeFlag){
    lcd.setCursor(15, 0);
    if (m < 10) {
      lcd.print("0");
      lcd.setCursor(16, 0);
      lcd.print(m);
    }
    else lcd.print(m);
    lcd.setCursor(18, 0);
    if (s < 10) {
      lcd.print("0");
      lcd.setCursor(19, 0);
      lcd.print(s);
    }
    else lcd.print(s);
    lcd.setCursor(0,1);   
    lcd.print(waterConsupt/1000.0, 3); //  переводим в литры
    lcd.setCursor(10,1);  
    lcd.print(lumConsupt/1000.0, 3);
    graph(3, 0, 13, map(plantAge, 0, grownPlantDays, 0, 100));
  } 
}

void graph(byte row, byte startPos, byte endPos, byte perc){  //  Функция отрисовки графика (строка начала отрисовки, столбец начала отрисовки, конечная позиция столбца отрисовки, процент заполнения
  uint8_t fill = map(perc, 0, 100, 0, endPos-startPos);
  lcd.setCursor(startPos, row);
  for (uint8_t i = startPos; i <= fill; i++){
      lcd.write(255);                                         //  Выводим полность закрашенный сегмент
  }
  for (uint8_t i = fill+1; i <= endPos; i++){                 
      lcd.write(95);                                          //  Вывод символа - нижнее подчёркивание
  }
}

void mainMenu(){
  if (mainMenuChangeFlag){
    mainMenuChangeFlag = false;
    lcd.setCursor(1, 0);
    lcd.print("\2OCA\1KA");
    lcd.setCursor(1, 1);
    lcd.print("C\7\3B");
    lcd.setCursor(1, 2);
    lcd.print("HACTPO\3K\3");
    lcd.setCursor(14, 3);
    lcd.write(127);
    lcd.print("\1OMO\3");
  }
  lcd.setCursor(0, cursorPosition);
  lcd.write(126);
}
  ////////////////////////////////////////////////////
void screen_3(){
  if (screen_3_Update){
    screen_3_Update = false;
    lcd.setCursor(3, 0);
    lcd.write(45);
    lcd.print("HACTPO\3KA RTC");
    lcd.write(45);
    lcd.setCursor(6, 1);
    lcd.print("  :  :");
    lcd.setCursor(5, 2);
    lcd.print("  -  -20");
    lcd.setCursor(14, 3);
    lcd.print("\1A\7EE");
    lcd.write(126);
  }
  hUpdate();
  mUpdate();
  DUpdate();
  MUpdate();
  YUpdate();
  if (sChangeFlag) {
    lcd.setCursor(12, 1);
    lcd.print("  ");
    lcd.setCursor(12, 1);
    if (s < 10) {
      lcd.print("0");
      lcd.print(s);
    }
    else lcd.print(s);  
  }
}
void hUpdate(){
  if (cursorPosition == 1) blinkSymbol(6, 1, 2, h < 10 ? String("0" + String(h)) : String(h));
  else{
    lcd.setCursor(6, 1);
    lcd.print("  ");
    lcd.setCursor(6, 1);
    if (h < 10) {
      lcd.print("0");
    }
    lcd.print(h);
  }
}
void mUpdate(){
  if (cursorPosition == 2) blinkSymbol(9, 1, 2, m < 10 ? String("0" + String(m)) : String(m));
  else{
    lcd.setCursor(9, 1);
    lcd.print("  ");
    lcd.setCursor(9, 1);
    if (m < 10) {
      lcd.print("0");
    }
    lcd.print(m);
  }
}
void DUpdate(){
  if (cursorPosition == 3) blinkSymbol(5, 2, 2, D < 10 ? String("0" + String(D)) : String(D));
  else{
    lcd.setCursor(5, 2);
    lcd.print("  ");
    lcd.setCursor(5, 2);
    if (D < 10) {
      lcd.print("0");
    }
    lcd.print(D);
  }
}
void MUpdate(){
  if (cursorPosition == 4) blinkSymbol(8, 2, 2, M < 10 ? String("0" + String(M)) : String(M));
  else{
    lcd.setCursor(8, 2);
    lcd.print("  ");
    lcd.setCursor(8, 2);
    if (M < 10) {
      lcd.print("0");
    }
    lcd.print(M);
  }
}
void YUpdate(){
  if (cursorPosition == 5) blinkSymbol(13, 2, 4, Y < 10 ? String("0" + String(Y)) : String(Y));
  else{
    lcd.setCursor(13, 2);
    lcd.print("  ");
    lcd.setCursor(13, 2);
    if (Y < 10) {
      lcd.print("0");
    }
    lcd.print(Y);
  }
}
  ////////////////////////////////////////////////////
void screen_4(){
  if (screen_4_Update){
    screen_4_Update = false;
    lcd.setCursor(0, 0);
    lcd.print("MO\5HOCT\4 \7AM\2:    BT");
    lcd.setCursor(13, 3);
    lcd.print("\6OTOBO");
    lcd.write(126);
    powerLampUpdate();
  }
}
void powerLampUpdate(){
  if (cursorPosition == 1) blinkSymbol(15, 0, 3, String(powerLamp));
  else{
    lcd.setCursor(15, 0);
    lcd.print("   ");
    lcd.setCursor(15, 0);
    lcd.print(powerLamp);
  }
}
  ////////////////////////////////////////////////////
void screen_5(){
  if (screen_5_Update){
    screen_5_Update = false;
    lcd.setCursor(4, 0);
    lcd.write(45);
    lcd.print("POCT, \1HE\3");
    lcd.write(45);
    lcd.setCursor(0, 1);
    lcd.print("BCE\6O: ");
    lcd.setCursor(0, 2);
    lcd.print("TEKY\5\3\3: ");
    lcd.setCursor(14, 3);
    lcd.print("\1A\7EE");
    lcd.write(126);
  }
  grownPlantUpdate();
  plantAgeUpdate(); 
} 
void grownPlantUpdate(){
  if (cursorPosition == 1) blinkSymbol(10, 1, 3, String(grownPlantDays));
  else{
    lcd.setCursor(10, 1);
    lcd.print("   ");
    lcd.setCursor(10, 1);
    lcd.print(grownPlantDays);
  }
}
void plantAgeUpdate(){ 
  if (cursorPosition == 2) blinkSymbol(10, 2, 3, String(plantAge));
  else{
    lcd.setCursor(10, 2);
    lcd.print("   ");
    lcd.setCursor(10, 2);
    lcd.print(plantAge);
  }
}
  ////////////////////////////////////////////////////
void screen_6(){
  if (screen_6_Update){
    screen_6_Update = false;
    lcd.setCursor(2, 0);
    lcd.write(45);
    lcd.print("BPEM. \1OCBETA");
    lcd.write(45);
    lcd.setCursor(5, 1);
    lcd.print(":00 -   :00");
    lcd.setCursor(14, 3);
    lcd.print("\1A\7EE");
    lcd.write(126);
  }
  timeLightOnUpdate();
  timeLightOffUpdate();
}
void timeLightOnUpdate(){
  if (cursorPosition == 1) blinkSymbol(3, 1, 2, timeOn < 10 ? String("0" + String(timeOn)) : String(timeOn));
  else{
    lcd.setCursor(3, 1);
    lcd.print("  ");
    lcd.setCursor(3, 1);
    if (timeOn < 10) lcd.print("0");
    lcd.print(timeOn);
  }
}
void timeLightOffUpdate(){
  if (cursorPosition == 2) blinkSymbol(11, 1, 2, timeOFF < 10 ? String("0" + String(timeOFF)) : String(timeOFF));
  else{
    lcd.setCursor(11, 1);
    lcd.print("  ");
    lcd.setCursor(11, 1);
    if (timeOFF < 10) lcd.print("0");
    lcd.print(timeOFF);
  }
}
  ////////////////////////////////////////////////////
void screen_7(){
  if (screen_7_Update){
    screen_7_Update = false;
    lcd.setCursor(1, 0);
    lcd.write(45);
    lcd.print("YPOBEH\4 PACTBOPA");
    lcd.write(45);
    lcd.setCursor(0, 1);
    lcd.print("____________________");
    lcd.setCursor(0, 2);
    lcd.print("HACOC: ");
    lcd.setCursor(13, 3);
    lcd.print("\6OTOBO");
    lcd.write(126);
  }
  pompStateUpdate();
  if (sChangeFlag) graphUpdate();
}
void graphUpdate(){ 
  graph(1, 0, 19, waterLevelPercent);
}
void pompStateUpdate(){
  if (cursorPosition == 1){
    if (!pompState) blinkSymbol(8, 2, 5, "CTAPT");
    else blinkSymbol(8, 2, 5, "CTO\2");
  }
  else{
    lcd.setCursor(8, 2);
    lcd.print("     ");
    lcd.setCursor(8, 2);
    if (!pompState) lcd.print ("CTAPT"); else lcd.print ("CTO\2");
  } 
}
 void screen_8(){
  if (screen_8_Update){
    screen_8_Update = false;
    lcd.setCursor(6, 0);
    lcd.write(45);
    lcd.print("C\7\3B");
    lcd.write(45);
    lcd.setCursor(0, 1);
    lcd.print("B \1AHHOM OKHE ABTO-");
    lcd.setCursor(0, 2);
    lcd.print("MAT\3KA HE AKT\3BHA");
    lcd.setCursor(13, 3);
    lcd.print("\6OTOBO");
    lcd.write(126);
  }
}
void blinkSymbol(uint8_t x, uint8_t y, uint8_t maxLength, String text){  //  Функция мигания курсором
  if (timeBlink + 500 < millis()) {                       // Меняем состояние каждые 500мс
    timeBlink = millis();
    visible = !visible; 
  }
  if (visible){
     for (uint8_t i=0; i < text.length(); i++){
      lcd.setCursor(x+i, y);
      lcd.print(text[i]);
    }
  }
  else {
    for (uint8_t i=0; i < maxLength; i++){
      lcd.setCursor(x+i, y);
      lcd.write (254);
    }
  }   
}

Ссылки




    Обсуждение

    Гарантии и возврат Используя сайт Вы соглашаетесь с условями