Время

Nursik_9797
Offline
Зарегистрирован: 09.11.2016

Прошу помощи. Я начинающий программист в свере ардуино(нуб короче). Вожусь над проектом 2 недели.

Надо было вывести на экран показания двух датчиков температуры(dht21) и времени без даты на lcd1302(16/2). есть пять кнопок set.left.down.up.right. Надо было создать менюшку на включение серво приводов(2) на включение по таймеру( таймер что бы можно было водить время вкл и вкл с кнопок) и остальные серво включаться при нужной температуре(что бы тоже можно было ставить температуру с кнопок) Пользовался скетчами с инета) Но только плачевно мой навык не вырос походу до этого уровня.

Скетч который я делал но не смог доделать

#include "DHT.h"
#include <Arduino.h>
#include <TimeLib.h>
#include "LCD_1602_RUS.h"
#include <EEPROM.h>
#include <Wire.h>
#include <OneWire.h>
#include <iarduino_RTC.h>
/*********************************************************************************************/
/* Объявление различных констант (номера пинов, смещения и т.п.)                             */
/*********************************************************************************************/
LCD_1602_RUS lcd(8, 9, 4, 5, 6, 7);
#define DHTPIN 33   // what pin we're connected to
#define DHTPIN_2 35    // what pin we're connected to
iarduino_RTC time(RTC_DS1302, 50, 46, 48);
 int button;
const int BUTTON_NONE   = 0;
const int BUTTON_RIGHT  = 1;
const int BUTTON_UP     = 2;
const int BUTTON_DOWN   = 3;
const int BUTTON_LEFT   = 4;
const int BUTTON_SELECT = 5;
DHT dht(DHTPIN, DHTTYPE);
DHT dht_2(DHTPIN_2, DHTTYPE);
/// Смещения в EEPROM
/// По этим смещениям хранятся соответствующие параметры, задаваемые пользователем
const int8_t ADDR_START_HOUR = 0;                                               // Час начала полива
const int8_t ADDR_START_MINUTE = 1;                                             // Минута начала полива
const int8_t ADDR_DURATION_HOUR = 2;                                            // Длительность полива по расписанию, часов
/*********************************************************************************************/
/* Реализация вспомогательных классов                                                        */
/*********************************************************************************************/
/**
   @brief Примитивный класс для работы с кнопками. Поддерживает обработку коротких и длинных нажатий
*/
int getPressedButton()
{
  int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0)
  if (buttonValue < 50) {
    return BUTTON_RIGHT;
  }
  else if (buttonValue < 200) {
    return BUTTON_UP;
  }
  else if (buttonValue < 400) {
    return BUTTON_DOWN;
  }
  else if (buttonValue < 600) {
    return BUTTON_LEFT;
  }
  else if (buttonValue < 800) {
    return BUTTON_SELECT;
  }
  return BUTTON_NONE;
}
/**
   @brief TimePair структура, хранящая время в виде часы-минуты
*/
struct TimePair
{
  int8_t hour;
  int8_t minute;
};
/**
   @brief PlannedActions структура, описывающая действие по времени
*/
struct PlannedActions
{
  TimePair startTime;             // время начала
  TimePair stopTime;              // время остановки
  bool isActive;                  // признак обработки линии планировщиком
  bool activated;                 // признак работы текущей линии
  bool currentState;              // состояние реле
};

/// Инициализируем LCD-дисплей. Возможно использование до 8 кириллических символов для "эмуляции" поддержки русского
/// языка на неруссифицированных экранах, все подробности - в документации к библиотеке LCD_1602_RUS
LCD_1602_RUS lcd(0x27, 16, 2);
void setup() {
  Serial.begin(9600);

  lcd.begin(16, 2);

  lcd.setCursor(0, 0);
  lcd.print("Test            ");
  lcd.setCursor(0, 1);
  lcd.print("Hello KZ");

  time.begin();
  dht.begin();
 
}
void loop() {
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  float t = dht.readTemperature();
 
  float h_2 = dht_2.readHumidity();
  float t_2 = dht_2.readTemperature();
   if (millis() % 1000 == 0) { // если прошла 1 секунда
    Serial.println()
     } else {
    lcd.setCursor(0, 0);
    lcd.print(" %\t"); 
    lcd.print(t);
    lcd.println(" *C");
    lcd.setCursor(7, 0);
    lcd.print(" %\t2"); 
    lcd.print(t2);
    lcd.println(" *C");
    lcd.clear();
    lcd.print();
    lcd.setCursor(0, 1);
    lcd.print(time.gettime("H:i:s, D"));
    
    Serial.println(time.gettime("d-m-Y, H:i:s, D")); // выводим время
    delay(1); // приостанавливаем на 1 мс, чтоб не выводить время несколько раз за 1мс
  
  }
void set_temp(){
{
  //    RTC.read(tm);
  currentTime.Hour = dVal[0];
  currentTime.Minute = dVal[1];
  RTC.write(currentTime);
}
/**
   @brief timeSumm добавить к начальному времени диапазон заданной длительности
   @param startTime начало (часы-минуты)
   @param duration длительность (часы-минуты)
   @return конец временного диапазона (часы-минуты)
*/
TimePair timeSumm(TimePair &startTime, TimePair &duration)
{
  TimePair result;
  result.minute = startTime.minute + duration.minute;
  result.hour = startTime.hour + duration.hour + (result.minute / 60);
  result.minute = result.minute % 60;
  return result;
}

/*********************************************************************************************/
/**
   @brief calculateShedule пересчитать время начала и конца полива для заданной линии
   @param idx номер линии
   @param tStart время начала полива
   @param tDuration длительность полива
*/
void calculateShedule(int8_t idx, TimePair &tStart, TimePair &tDuration)
{
  shedule[idx].startTime = tStart;
  shedule[idx].stopTime = timeSumm(tStart, tDuration);
  shedule[idx].activated = false;
  if (EEPROM.read(ADDR_LINE_1 + idx) != 0)
  {
    shedule[idx].isActive = true;
    if (++idx < 4)
      calculateShedule(idx, shedule[idx - 1].stopTime, tDuration);
  } else
  {
    shedule[idx].isActive = false;
    if (++idx < 4)
      calculateShedule(idx, tStart, tDuration);
  }
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
Второй скетч который я пытался переделать но не вышло нифига

#include "DHT.h"
#include <Arduino.h>
#include <TimeLib.h>
#include "LCD_1602_RUS.h"
#include <LiquidCrystal.h>
#include <EEPROM.h>
#include <Wire.h>
#include <OneWire.h>
#include <iarduino_RTC.h>


/// Если не нужна отладочная информация в консоли, то строка с дефайном должна быть закомментирована.
/// При включении отладки возможны проблемы в работе скетча, т.к. компилятор ругается что ему мало памяти
//#define SERIAL_DEBUG

/*********************************************************************************************/
/* Объявление различных констант (номера пинов, смещения и т.п.)                             */
/*********************************************************************************************/

/// Константы, необходимые для работы скетча
const int8_t MAX_VALUES_COUNT = 4;                                              // Максимальное число значений в строке, формируемой для вывода на экран ()
const int8_t LIGHT_TIMEOUT = 10;                                                // Время до отключения подсветки экрана при бездействии
const int8_t MENU_ITEMS_COUNT = 16;                                             // Количество строк в меню
//const int16_t HUMIDITY_MIN_PROBE = 200;                                         // Минимальное значение влажности почвы (очень сухо)
//const int16_t HUMIDITY_MAX_PROBE = 840;                                         // Максимальное значение влажности почвы (болото)

/// Смещения в EEPROM
/// По этим смещениям хранятся соответствующие параметры, задаваемые пользователем
const int8_t ADDR_START_HOUR = 0;                                               // Час начала полива
const int8_t ADDR_START_MINUTE = 1;                                             // Минута начала полива
const int8_t ADDR_DURATION_HOUR = 2;                                            // Длительность полива по расписанию, часов
const int8_t ADDR_DURATION_MINUTE = 3;                                          // Длительность полива по расписанию, минут
const int8_t ADDR_LINE_1 = 4;                                                   // Необходимость полива первой линии по расписанию
const int8_t ADDR_LINE_2 = 5;                                                   // Необходимость полива второй линии по расписанию
const int8_t ADDR_LINE_3 = 6;                                                   // Необходимость полива третьей линии по расписанию
const int8_t ADDR_LINE_4 = 7;                                                   // Необходимость полива четвертой линии по расписанию
const int8_t ADDR_BUTTON_HOUR = 8;                                              // Длительность полива по кнопке, часов
const int8_t ADDR_BUTTON_MINUTE = 9;                                            // Длительность полива по кнопке, минут

/* SDA - a4, SCL - a5*/
// аналоговые пины
//const int8_t PIN_RESERVE_A0 = 0;                                                // Пин резерв (14 цифровой, не задействован как аналог)
//const int8_t PIN_RESERVE_A1 = 1;                                                // Пин резерв (15 цифровой, не задействован как аналог)
//const int8_t PIN_SOIL_HUMIDITY = 2;                                             // Пин датчик влажности почвы
//const int8_t PIN_SOIL_TEMPERATURE = 3;                                          // Пин датчик температуры почвы
//const int8_t PIN_SDA = 4;                                                       // Пин SDA
//const int8_t PIN_SCL = 5;                                                       // Пин SCL
const int8_t PIN_CHANGE_VALUE = 7;                                                // Пин для смены пункта меню или значения в меню
const int8_t PIN_CHANGE_MODE = 6;                                                 // Пин для изменения значения в выбранном пункте меню

// цифровые пины
//const int8_t PIN_SOIL_LEFT =- ;                                                 // Пин датчика влажности почвы 1 (полярность меняется со 2)
//const int8_t PIN_SOIL_RIGHT = -;                                                // Пин датчика влажности почвы 2 (полярность меняется с 1)
//const int8_t PIN_BUTTON[] = {-};                                                // Массив пинов кнопок принудительного включения полива
//const int8_t PIN_DHT[] = {-};                                                   // Массив пинов датчиков температуры и влажности
//const int8_t PIN_MOTOR = -;                                                     // Пин для включения реле мотора
//const int8_t PIN_RELAY[] = {-};                                                 // Массив пинов реле электромагнитных клапанов
const int8_t PIN_BUTTON = {0, 1, 2, 3, 4, 5};                                     // Пин  для выбора режима или подтверждения изменений (аналоговый A0)
//const int8_t PIN_FLOWMETER = -;                                                 // Пин расходомера (аналоговый A1)

/*********************************************************************************************/
/* Реализация вспомогательных классов                                                        */
/*********************************************************************************************/

/**
   @brief Примитивный класс для работы с кнопками. Поддерживает обработку коротких и длинных нажатий
*/
class ActionButton
{
  private:
    int64_t clickTime;          // Время, когда была нажата кнопка
    bool statusClicked[2];      // Предыдущее и текущее состояние кнопки (нажата или нет)
    int8_t PIN_BUTTON;          // На каком цифровом входе находится кнопка
  public:
    /**
       @brief Конструктор ActionButton
       @param PIN_BTN номер цифрового входа ардуины, на котором висит кнопка
    */
    ActionButton(int8_t PIN_BTN)
    {
      PIN_BUTTON = PIN_BTN;
      clickTime = 0;
      statusClicked[0] = false;
      statusClicked[1] = false;
    }

    /**
       @brief buttonPressed Текущее состояние кнопки
       @return true, если кнопка нажата, иначе false
    */
    bool buttonPressed()
    {
      statusClicked[1] = statusClicked[0];
      statusClicked[0] = !digitalRead(PIN_BUTTON);
      if (statusClicked[0] && (!statusClicked[1]) && (clickTime == 0))        // если нажали кнопку только что,
        clickTime = millis() / 1000;                                        // то запомнить это время
      return statusClicked[0];
    }

    /**
       @brief buttonClicked Результат последнего нажатия кнопки
       @return 0 - кнопка еще не была нажата и отпущена; 1 - было сделано короткое нажатие; 2 - было сделано долгое нажатие (более 2 секунд)
    */
    int8_t buttonClicked()
    {
      if (!statusClicked[0])                          // если кнопка сейчас не нажата
      {
        if (statusClicked[1])                       // а раньше она была нажата
        {
          if ((millis() / 1000 - clickTime) > 2)  // и нажатие на кнопку было дольше 2 секунд
          {
            clickTime = 0;
            return 2;
          } else                                  // нажатие на кнопку было коротким
          {
            clickTime = 0;
            return 1;
          }
        }
      }
      return 0;
    }
};

// Максимальное значение аналогового датчика
#define MAX_POS 1023
// VRx и VRy выдают значения от 0 до 1023
// Максимальный угол наклона джойстика
#define MAX_ANGLE 60
// Минимальное значение угла наклона джойстика, которое засчитывается для навигации по меню
#define CLICK_ANGLE 25

/**
   @brief Класс для навигации по меню поливатора
*/
// Нажатые кнопки
int button;
const int BUTTON_NONE   = 0;
const int BUTTON_RIGHT  = 1;
const int BUTTON_UP     = 2;
const int BUTTON_DOWN   = 3;
const int BUTTON_LEFT   = 4;
const int BUTTON_SELECT = 5;

// функция обработки нажатия кнопок
int getPressedButton()
{
  int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0)
  if (buttonValue < 50) {
    return BUTTON_RIGHT;
  }
  else if (buttonValue < 200) {
    return BUTTON_UP;
  }
  else if (buttonValue < 400) {
    return BUTTON_DOWN;
  }
  else if (buttonValue < 600) {
    return BUTTON_LEFT;
  }
  else if (buttonValue < 800) {
    return BUTTON_SELECT;
  }
  return BUTTON_NONE;
}
/**
   @brief buttonClicked Результат последнего нажатия кнопки джойстика
   @return 0 - кнопка еще не была нажата и отпущена; 1 - было сделано короткое нажатие; 2 - было сделано долгое нажатие (более 2 секунд)
*/
int8_t buttonClicked()
{
  return button->buttonClicked();
}

};

/*********************************************************************************************/
/* Объявление структур и глобальных переменных                                               */
/*********************************************************************************************/

void(* softReset) (void) = 0;       // объявляем функцию с адресом 0, для софт-ресета

int8_t lastAction;                  // Время последнего действия (используется для активации/деактивации подсветки экрана)
int8_t forcedUpdate;                // Время принудительной перерисовки меню
int tmpValues[MAX_VALUES_COUNT];    // массив для временного хранения значений
int8_t currentMenu;                 // Номер текущего пункта меню
bool menuIsSelected;                // Признак того, что текущее меню было выбрано пользователем
bool motorIsActive;                 // Признак работы насоса
bool displayIsActive;               // Признак включенной подсветки дисплея
tmElements_t currentTime, previousTime;

/**
   @brief TimePair структура, хранящая время в виде часы-минуты
*/
struct TimePair
{
  int8_t hour;
  int8_t minute;
};

/**
   @brief ManualActions структура, описывающая действие по нажатию кнопки
*/
/*struct ManualActions
  {
    TimePair stopTime;              // время остановки
    ActionButton* starter;          // привязанная кнопка
    bool isUnlimited;               // бесконечный полив - включается при долгом нажатии на кнопку
    bool isActive;                  // признак активности текущего действия
  };
*/
/**
   @brief PlannedActions структура, описывающая действие по времени
*/
struct PlannedActions
{
  TimePair startTime;             // время начала
  TimePair stopTime;              // время остановки
  bool isActive;                  // признак обработки линии планировщиком
  bool activated;                 // признак работы текущей линии
  bool currentState;              // состояние реле
};

/// Инициализируем LCD-дисплей. Возможно использование до 8 кириллических символов для "эмуляции" поддержки русского
/// языка на неруссифицированных экранах, все подробности - в документации к библиотеке LCD_1602_RUS
LCD_1602_RUS lcd(0x27, 16, 2);

/// Инициализируем кнопки
int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0)

/* Инициализируем датчик температуры почвы
  OneWire soilT(PIN_SOIL_TEMPERATURE);

  int8_t soilHumidity;                // значение влажности почвы, необходимо читать его раз в 10 минут или при обращении к нему
  int8_t soilTemperature;             // значение температуры почвы, необходимо читать его раз в 10 минут или при обращении к нему
*/
/// Объявляем и заполняем массив действий по 4 кнопкам
ManualActions control[] = {
  {{0, 0}, 0, false},
  {{0, 0}, 0, false},
  {{0, 0}, 0, false},
  {{0, 0}, 0, false}
};

/// Объявляем массив запланированных действий по 4 линиям
PlannedActions shedule[4];

/// Объявляем и инициализируем датчики температуры и влажности
DHT dht1(PIN_DHT[0], DHT11);
DHT dht2(PIN_DHT[1], DHT11);
DHT dht3(PIN_DHT[2], DHT11);

/**
   @brief ValueView структура, хранящая начальную позицию и количество символов в отображаемом значении
*/
struct ValueView
{
  int8_t pos;
  int8_t length;
};

/**
   @brief RowParameters структура, описывающая параметры выводимой строки для пункта меню одного типа
*/
struct RowParameters
{
  int minimum[4];
  int maximum[4];
  ValueView value[4];
};

/// Заполняем параметры выводимых строк для разных типов отображаемой информации
RowParameters paramId[] = {
  {{1, 1, 2016, 0}, {31, 12, 2050, 0}, {{2, 2}, {6, 2}, {10, 4}, {0, 0}}},    // строка с датой
  {{0, 0, 0, 0}, {23, 59, 0, 0}, {{5, 2}, {9, 2}, {0, 0}, {0, 0}}},           // строки со временами
  {{0, 0, 0, 0}, {1, 1, 1, 1}, {{1, 2}, {5, 2}, {9, 2}, {13, 2}}},            // строка с клапанами
  {{0, 0, 0, 0}, {0, 0, 0, 0}, {{1, 0}, {10, 0}, {0, 0}, {0, 0}}},            // строки с влажностью и температурой
  {{0, 0, 0, 0}, {0, 0, 0, 0}, {{2, 2}, {10, 5}, {0, 0}, {0, 0}}},            // строка с данными о расходе воды
  {{0, 0, 0, 0}, {0, 0, 0, 0}, {{3, 3}, {10, 2}, {0, 0}, {0, 0}}}             // строка с запросом перезагрузки
};

/**
   @brief MenuInfo структура, описывающая строку меню
*/
struct MenuInfo
{
  const wchar_t* menuName;
  int8_t firstPosition;
  int8_t menuValues;
  int8_t activeSubmenu;
  RowParameters* rowParameter;
};

/// Заполняем меню. Префикс "L" перед каждой строкой необходим для корректного вывода
/// кириллических символов с помощью библиотеки LCD_1602_RUS, подробности - в документации к ней
MenuInfo allMenu[] = {
  {L"TEK.  ДATA", 3, 3, 0, &paramId[0]},
  {L"TEK. BPEMЯ", 3, 2, 0, &paramId[1]},
  {L"HAЧAЛO  ПOЛИBA", 1, 2, 0, &paramId[1]},
  {L"ДЛИTEЛbHOCTb", 2, 2, 0, &paramId[1]},
  {L"ПOЛИB C KHOПKИ", 1, 2, 0, &paramId[1]},
  {L"AKTИBHЫE ЛИHИИ", 1, 4, 0, &paramId[2]},
  {L"ПOЧBA", 5, 0, 0, &paramId[3]},
  {L"HACOC. CTAHЦИЯ", 1, 0, 0, &paramId[3]},
  {L"TEПЛИЦA", 4, 0, 0, &paramId[3]},
  {L"KPЫЛbЦO", 4, 0, 0, &paramId[3]},
  {L"PACXOД  BOДЫ", 2, 0, 0, &paramId[4]},
  {L"PECTAPT", 4, 2, 0, &paramId[5]}
};


/**
   @brief MessageType различные типы сообщений, которые необходимо обрабатывать
*/
enum MessageType
{
  MTSheduleOn     =   0,          // начало по расписанию
  MTSheduleOff    =   1,          // завершение по расписанию
  MTControlOn     =   2,          // кнопка включена
  MTControlOff    =   3,          // кнопка выключена
  MTUnlimited     =   4,          // начат бесконечный полив
  MTCompleted     =   5           // успешно завершено
};

volatile int waterCounter;      // переменная для хранения объема прошедшей через насос воды
int waterCountPrevious;         // предыдущее значение счетчика воды
int currentFlowRate;            // текущий расход воды (л/с) - считается ежесекундно
float currentConsumption;       // текущее потребление (л) - накапливается

/*********************************************************************************************/
/* Функции                                                                                   */
/*********************************************************************************************/
/**
   @brief waterFlow подсчет воды (функция должна висеть на прерывании)
*/
void waterFlow ()
{
  waterCounter++;
}

/*********************************************************************************************/
/**
   @brief setSensorPolarity установка полярности для датчика влажности почвы
   @param flip true или false для переключения полярности
*/
void setSensorPolarity(boolean flip)
{
  if (flip) {
    digitalWrite(PIN_SOIL_LEFT, HIGH);
    digitalWrite(PIN_SOIL_RIGHT, LOW);
  } else {
    digitalWrite(PIN_SOIL_LEFT, LOW);
    digitalWrite(PIN_SOIL_RIGHT, HIGH);
  }
}

/*********************************************************************************************/
/**
   @brief getSoilHumidity получение информации о влажности почвы с датчика
*/
void getSoilHumidity()
{
  setSensorPolarity(true);
  delay(500);
  int val1 = analogRead(PIN_SOIL_HUMIDITY);
  delay(500);
  setSensorPolarity(false);
  delay(500);
  int val2 = 1023 - analogRead(PIN_SOIL_HUMIDITY);
  digitalWrite(PIN_SOIL_LEFT, LOW);
  digitalWrite(PIN_SOIL_RIGHT, LOW);
  soilHumidity = ((val1 + val2) / 2 - HUMIDITY_MIN_PROBE) / (HUMIDITY_MAX_PROBE - HUMIDITY_MIN_PROBE) * 100;
  if (soilHumidity < 0)
    soilHumidity = 0;
  if (soilHumidity > 100)
    soilHumidity = 100;
}

/*********************************************************************************************/
/**
   @brief getSoilTemperature получение информации о температуре почвы с датчика
*/
void getSoilTemperature()
{
  uint8_t address[8];
  if (!soilT.search(address))
  {
#ifdef SERIAL_DEBUG
    Serial.println("Soil temperature sensor is not exist");
#endif
    return;
  }
  byte data[2];
  soilT.reset();
  soilT.write(0xCC);
  soilT.write(0x44);
  delay(750);
  soilT.reset();
  soilT.write(0xCC);
  soilT.write(0xBE);
  data[0] = soilT.read();
  data[1] = soilT.read();
  int Temp = (data[1] << 8) + data[0];
  soilTemperature = Temp >> 4;
}

/*********************************************************************************************/
/**
   @brief showInfo формирование сообщения по событиям и номерам линий
   @param mt тип события, которое необходимо
   @param mask маска линий, к которым относится событие
*/
void showInfo(MessageType mt, int mask)
{
  const wchar_t* textOn = L" BKЛ. ";
  const wchar_t* textOff = L" BЫKЛ. ";
  const wchar_t* textShedule = L"ПЛAHOBOE";
  const wchar_t* textControl = L"(KHOПKA)";
  const wchar_t* textUnlimited = L"ПOCTOЯHHO";
  const wchar_t* textCompleted = L"** BЫПOЛHEHO! **";
  const wchar_t* lineTxt;
  const wchar_t* lineMulti = L"ЛИHИИ: ";
  const wchar_t* lineOne = L"ЛИHИЯ # ";

  bool used[4];
  int8_t count = 0;
  for (int8_t i = 0; i < 4; i++)
  {
    used[i] = bool (1 & (mask >> i));
    if (used[i])
      count++;
  }
  activateLight(true);
  lcd.clear();
  lcd.setCursor(0, 0);
  switch (mt)
  {
    case MTCompleted:
      lcd.print(textCompleted);
      break;
    case MTControlOff:
      lcd.print(textOff);
      lcd.print(textControl);
      break;
    case MTControlOn:
      lcd.print(textOn);
      lcd.print(textControl);
      break;
    case MTSheduleOff:
      lcd.print(textOff);
      lcd.print(textShedule);
      break;
    case MTSheduleOn:
      lcd.print(textOn);
      lcd.print(textShedule);
      break;
    case MTUnlimited:
      lcd.print(textUnlimited);
      break;
  }

  if (count > 1)
    lineTxt = lineMulti;
  else
    lineTxt = lineOne;

  int lineBegin = ((16 - sizeof(lineTxt)) / 2) - count;
  lcd.setCursor(lineBegin, 1);
  lcd.print(lineTxt);
  for (int8_t i = 0; i < 4; i++)
    if (used[i])
    {
      lcd.print(i + 1);
      lcd.print(" ");
    }
  forcedUpdate = 5;
}

/*********************************************************************************************/
/**
   @brief currentToArray текущие значения (с датчиков или из EEPROM) поместить в массив для отображения
   @param ta указатель на массив
*/
void currentToArray(int ta[])
{
  float fT, fH;
  //    int tmpFR;
  switch (currentMenu) {
    case 0: // текущая дата
      getData(ta);
      break;
    case 1: // текущее время
      getTime(ta);
      break;
    case 2: // начало полива
      ta[0] = EEPROM.read(ADDR_START_HOUR);
      ta[1] = EEPROM.read(ADDR_START_MINUTE);
      break;
    case 3: // длительность полива
      ta[0] = EEPROM.read(ADDR_DURATION_HOUR);
      ta[1] = EEPROM.read(ADDR_DURATION_MINUTE);
      break;
    case 4: // длительность полива, запускаемого с кнопки
      ta[0] = EEPROM.read(ADDR_BUTTON_HOUR);
      ta[1] = EEPROM.read(ADDR_BUTTON_MINUTE);
      break;
    case 5: // активные клапаны
      for (int8_t i = 0; i < 4; i++)
      {
        ta[i] = EEPROM.read(ADDR_LINE_1 + i);
        if (ta[i] != 0)
          ta[i] = 1;
      }
      break;
    case 6: // температура и влажность почвы
      getSoilHumidity();
      getSoilTemperature();
      ta[0] = soilTemperature;
      ta[1] = soilHumidity;
      break;
    case 7: // температура и влажность
      ta[0] = dht1.readTemperature();
      ta[1] = dht1.readHumidity();
      break;
    case 8: // температура и влажность
      ta[0] = dht2.readTemperature();
      ta[1] = dht2.readHumidity();
      break;
    case 9: // температура и влажность
      ta[0] = dht3.readTemperature();
      ta[1] = dht3.readHumidity();
      break;
    case 10: // расход воды
      ta[0] = currentFlowRate;
      ta[1] = int(currentConsumption);
      break;
    default: // ошибка
      break;
  }
}

/*********************************************************************************************/
/**
   @brief arrayToCurrent сохранить введенные пользователем данные
   @param ta указатель на массив, в котором находятся пользовательские данные
*/
void arrayToCurrent(int ta[])
{
  TimePair t1, t2;
  switch (currentMenu) {
    case 0: // текущая дата
      setNewData(ta);
      break;
    case 1: // текущее время
      setNewTime(ta);
      break;
    case 2: // начало полива
      EEPROM.write(ADDR_START_HOUR, ta[0]);
      EEPROM.write(ADDR_START_MINUTE, ta[1]);
      t1.hour = ta[0];
      t1.minute = ta[1];
      t2.hour = EEPROM.read(ADDR_DURATION_HOUR);
      t2.minute = EEPROM.read(ADDR_DURATION_MINUTE);
      calculateShedule(0, t1, t2);
      break;
    case 3: // длительность полива
      EEPROM.write(ADDR_DURATION_HOUR, ta[0]);
      EEPROM.write(ADDR_DURATION_MINUTE, ta[1]);
      t1.hour = EEPROM.read(ADDR_START_HOUR);
      t1.minute = EEPROM.read(ADDR_START_HOUR);
      t2.hour = ta[0];
      t2.minute = ta[1];
      calculateShedule(0, t1, t2);
      break;
    case 4: // длительность полива с кнопки
      EEPROM.write(ADDR_BUTTON_HOUR, ta[0]);
      EEPROM.write(ADDR_BUTTON_MINUTE, ta[1]);
      break;
    case 5: // активные клапаны
      for (int8_t i = 0; i < 4; i++)
      {
        if (ta[i] == 0)
          EEPROM.write(ADDR_LINE_1 + i, 0);
        else
          EEPROM.write(ADDR_LINE_1 + i, 1);
      }
      t1.hour = EEPROM.read(ADDR_START_HOUR);
      t1.minute = EEPROM.read(ADDR_START_HOUR);
      t2.hour = EEPROM.read(ADDR_DURATION_HOUR);
      t2.minute = EEPROM.read(ADDR_DURATION_MINUTE);
      calculateShedule(0, t1, t2);
      break;
    case 11:
      if (allMenu[currentMenu].activeSubmenu == 1)
        softReset();
      break;
    default: // ошибка
      break;
  }
}

/*********************************************************************************************/
/**
   @brief activateLight активировать подсветку экрана
   @param act true - включить подсветку, false - выключить ее
*/
void activateLight(bool act)
{
  displayIsActive = act;
  if (act)
  {
    lastAction = LIGHT_TIMEOUT;
    lcd.display();            // Включаем подсветку дисплея и сам дисплей
    lcd.backlight();
  } else
  {
    lcd.noDisplay();          // Отключаем подсветку дисплея и сам дисплей
    lcd.noBacklight();
  }
}

/*********************************************************************************************/
/**
   @brief print2digits дополнить число от 0 до 9 дополнительным нулем слева
   @param number число, которое необходимо привести к двухсимвольному виду
*/
void print2digits(int number)
{
  if (number >= 0 && number < 10)
  {
    lcd.print("0");
  }
  lcd.print(number);
}

/*********************************************************************************************/
/**
   @brief setNewData установка новой даты
   @param dVal массив, в котором хранится день, месяц и год, установленные пользователем
*/
void setNewData(int dVal[])
{
  //    RTC.read(tm);
  currentTime.Day = dVal[0];
  currentTime.Month = dVal[1];
  currentTime.Year = CalendarYrToTm(dVal[2]);
  RTC.write(currentTime);
}

/*********************************************************************************************/
/**
   @brief setNewTime установка нового времени
   @param dVal массив, в котором хранятся часы и минуты, заданные пользователем
*/
void setNewTime(int dVal[])
{
  //    RTC.read(tm);
  currentTime.Hour = dVal[0];
  currentTime.Minute = dVal[1];
  RTC.write(currentTime);
}

/*********************************************************************************************/
/**
   @brief getData получить от часов реального времени текущую дату и поместить ее в массив
   @param dVal массив для полученной даты
*/
void getData(int dVal[])
{
  // надо получить от часов текущую дату и отобразить ее
  if (RTC.read(currentTime))
  {
    dVal[0] = currentTime.Day;
    dVal[1] = currentTime.Month;
    dVal[2] = tmYearToCalendar(currentTime.Year);
  }
  else
  {
    dVal[0] = 1;
    dVal[1] = 1;
    dVal[2] = 2016;
  }
}

/*********************************************************************************************/
/**
   @brief getTime получить от часов реального времени текущее время и поместить его в массив
   @param dVal массив для полученного времени
*/
void getTime(int dVal[])
{
  // надо получить от часов текущее время и отобразить его
  if (RTC.read(currentTime))
  {
    dVal[0] = currentTime.Hour;
    dVal[1] = currentTime.Minute;
    //        showValues(currSubmenu, tm.Day, tm.Month, tmYearToCalendar(tm.Year));
  }
  else
  {
    dVal[0] = 0;
    dVal[1] = 0;
  }
}

/*********************************************************************************************/
/**
   @brief showValues отобразить строку со значениями в соответствии с текущим пунктом меню
   @param sv массив с отображаемыми данными
   @param activeValue номер значения, выбранного пользователем для изменения
*/
void showValues(int sv[], int8_t activeValue)
{
  for (int8_t i = 0; i < allMenu[currentMenu].menuValues; i++)
  {
    // здесь нужно формировать вывод значений на экран
    if (activeValue == i)
    {
      lcd.setCursor(allMenu[currentMenu].rowParameter->value[i].pos + allMenu[currentMenu].rowParameter->value[i].length, 1);
      lcd.print(">");
      lcd.setCursor(allMenu[currentMenu].rowParameter->value[i].pos - 1, 1);
      lcd.print("<");
    }
    else
      lcd.setCursor(allMenu[currentMenu].rowParameter->value[i].pos, 1);
    switch (currentMenu) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
        print2digits(sv[i]);
        break;
      case 11:
        if (i == 0)
          lcd.print("HET");
        else
          lcd.print(L"ДA");
        break;
      default:
        lcd.print(i + 1);
        if (sv[i] > 0)
          lcd.print("+");
        else
          lcd.print("-");
        break;
    }
  }
  if (allMenu[currentMenu].menuValues == 0)
  {
    switch (currentMenu) {
      case 6:
      case 7:
      case 8:
      case 9:
        lcd.setCursor(allMenu[currentMenu].rowParameter->value[0].pos, 1);
        lcd.print("T:");
        if (sv[0] > 0)
          lcd.print("+");
        else if (sv[0] < 0)
          lcd.print("-");
        else
          lcd.print(" ");
        print2digits(sv[0]);
        lcd.print(L"°C");
        lcd.setCursor(allMenu[currentMenu].rowParameter->value[1].pos, 1);
        lcd.print("B:");
        print2digits(sv[1]);
        lcd.print("%");
        break;
      case 10:
        lcd.setCursor(0, 1);
        lcd.print("T:");
        print2digits(sv[0]);
        lcd.print(L"Л/c");
        lcd.setCursor(8, 1);
        lcd.print("C:");
        lcd.print(sv[1]);
        lcd.print(L"Л");
        break;
    }
  }
}

/*********************************************************************************************/
/**
   @brief showSubmenu отобразить на LCD строку со значениями текущего пункта меню
*/
void showSubmenu()
{
  int tmpLocal[4];
  lcd.setCursor(0, 1);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  if (menuIsSelected)                                 // если меню выбрано
  { // то надо отображать временные значения
    showValues(&tmpValues[0], allMenu[currentMenu].activeSubmenu);
  }
  else                                                // иначе - получать и отображать текущие значения
  {
    currentToArray(&tmpLocal[0]);
    showValues(&tmpLocal[0], -1);
  }
}

/*********************************************************************************************/
/**
   @brief markMenuAsSelected пометить квадратными скобками состояние текущего меню - выбрано или не выбрано
   @param marked true, если меню выбрано
*/
void markMenuAsSelected(bool marked)
{
  char *markedMenu = " [ ]";
  int8_t isMarked = (int8_t) marked;
  lcd.setCursor(0, 0);
  lcd.print(markedMenu[isMarked]);
  lcd.setCursor(15, 0);
  lcd.print(markedMenu[isMarked + 2]);
}

/*********************************************************************************************/
/**
   @brief updateMenu перерисовать меню и подменю
   @param idx номер пункта меню
*/
void updateMenu(int8_t idx)
{
  if (!menuIsSelected)
  {
    lcd.clear();
    lcd.setCursor(allMenu[idx].firstPosition, 0);
    lcd.print(allMenu[idx].menuName);
  }
  markMenuAsSelected(menuIsSelected);
  showSubmenu();
}

/*********************************************************************************************/
/**
   @brief moveLeft обработка нажатия кнопки "влево"
*/
void moveLeft()
{
  if (menuIsSelected)                                 // если выбран пункт основного меню
  {
    if (allMenu[currentMenu].menuValues == 0)       // и в нем нет изменяемых значений
      return;                                     // то выйти
    if (--allMenu[currentMenu].activeSubmenu < 0)   // иначе уменьшить индекс выбранного значения
    {
      allMenu[currentMenu].activeSubmenu = allMenu[currentMenu].menuValues - 1;
    }
  }
  else                                                // а если пункт еще не выбран
  {
    if (--currentMenu < 0)                          // то уменьшить индекс текущего пункта меню
      currentMenu = MENU_ITEMS_COUNT - 1;
  }
}

/*********************************************************************************************/
/**
   @brief moveRight обработка нажатия кнопки "вправо"
*/
void moveRight()
{
  if (menuIsSelected)                                 // если выбран пункт основного меню
  {
    if (allMenu[currentMenu].menuValues == 0)       // и в нем нет изменяемых значений
      return;                                     // то выйти
    if (++allMenu[currentMenu].activeSubmenu >=     // иначе уменьшить индекс выбранного значения
        allMenu[currentMenu].menuValues)
    {
      allMenu[currentMenu].activeSubmenu = 0;
    }
  }
  else                                                // а если пункт еще не выбран
  {
    if (++currentMenu >= MENU_ITEMS_COUNT)          // то уменьшить индекс текущего пункта меню
      currentMenu = 0;
  }
}

/*********************************************************************************************/
/**
   @brief moveUp обработка нажатия кнопки "вверх"
*/
void moveUp()
{
  if ((!menuIsSelected)                               // если не выбран пункт основного меню
      || (allMenu[currentMenu].menuValues == 0))      // или в нем нет изменяемых значений
    return;                                         // то выйти
  int8_t valueId = allMenu[currentMenu].activeSubmenu;
  if (++tmpValues[valueId] >                          // иначе уменьшить индекс выбранного значения
      allMenu[currentMenu].rowParameter->maximum[valueId])
  {
    tmpValues[valueId] = allMenu[currentMenu].rowParameter->minimum[valueId];
  }
}

/*********************************************************************************************/
/**
   @brief moveDown обработка нажатия кнопки "вниз"
*/
void moveDown()
{
  if ((!menuIsSelected)                               // если не выбран пункт основного меню
      || (allMenu[currentMenu].menuValues == 0))      // или в нем нет изменяемых значений
    return;                                         // то выйти
  int8_t valueId = allMenu[currentMenu].activeSubmenu;
  if (--tmpValues[valueId] <                          // иначе уменьшить индекс выбранного значения
      allMenu[currentMenu].rowParameter->minimum[valueId])
  {
    tmpValues[valueId] = allMenu[currentMenu].rowParameter->maximum[valueId];
  }
}

/*********************************************************************************************/
/**
   @brief applyButton обработка нажатия кнопки подтверждения
*/
void applyButton()
{
  if (!menuIsSelected)                                // если не выбран пункт основного меню
  { // то надо проверить, можно ли его выбрать
    if (allMenu[currentMenu].menuValues == 0)       // если выбираемых значений нет
      return;                                     // то выйти
    currentToArray(&tmpValues[0]);
    menuIsSelected = true;
  }
  else
  {
    arrayToCurrent(&tmpValues[0]);
    menuIsSelected = false;
    allMenu[currentMenu].activeSubmenu = 0;
  }
}

/*********************************************************************************************/
/**
   @brief showLogo отобразить надпись при старте поливатора
*/
void showLogo()
{
  wchar_t strLogo1[] = L"ПOЛИBATOP -";
  wchar_t strLogo2[] = L"И3MEPЯTOP";
  wchar_t currSymbol[] = L" ";
  lcd.setCursor(0, 0);
  for (int8_t i = 0; i < 11; i++)
  {
    currSymbol[0] = strLogo1[i];
    lcd.print(currSymbol);
    delay(200);
  }
  delay(1000);
  lcd.setCursor(6, 1);
  for (int8_t i = 0; i < 9; i++)
  {
    currSymbol[0] = strLogo2[i];
    lcd.print(currSymbol);
    delay(200);
  }
  delay(2000);
}

/*********************************************************************************************/
/**
   @brief initAllPins инициализация входов и выходов ардуины
*/
void initAllPins()
{
  // активируем пины электромагнитных клапанов и кнопок
  for (int8_t i = 0; i < 4; i++)
  {
    pinMode(PIN_RELAY[i], OUTPUT);
    digitalWrite(PIN_RELAY[i], HIGH);
    pinMode(PIN_BUTTON[i], INPUT);
    digitalWrite(PIN_BUTTON[i], HIGH);
    control[i].starter = new ActionButton(PIN_BUTTON[i]);
  }

  /* // активируем пин насоса
    pinMode(PIN_MOTOR, OUTPUT);
    digitalWrite(PIN_MOTOR, HIGH);
  */
  // активируем пин кнопки
  pinMode(PIN_APPLY_BUTTON, INPUT);
  digitalWrite(PIN_APPLY_BUTTON, HIGH); //включаем на нем подтягивающий резистор

  /*    // активируем пины катода и анода на датчике влажности почвы
      pinMode(PIN_SOIL_LEFT, OUTPUT);
      pinMode(PIN_SOIL_RIGHT, OUTPUT);

      // убираем ток с анода и катода
      digitalWrite(PIN_SOIL_LEFT, LOW);
      digitalWrite(PIN_SOIL_RIGHT, LOW);

      // инициализируем пин расходомера и вешаем на него прерывание
      pinMode(PIN_FLOWMETER, INPUT);
      attachInterrupt(0, waterFlow, RISING);*/

}

/*********************************************************************************************/
/**
   @brief setup старт ардуины с инициализацией датчиков
*/
void setup()
{
#ifdef SERIAL_DEBUG
  Serial.begin(9600);
  while (!Serial); // wait for serial
#endif
  delay(200);
  if (!RTC.read(currentTime))
  {
    currentTime.Hour = 12;
    currentTime.Minute = 0;
    currentTime.Second = 0;
    currentTime.Day = 1;
    currentTime.Month = 1;
    currentTime.Year = CalendarYrToTm(2016);
    RTC.write(currentTime);
  }
  waterCounter = waterCountPrevious = 0;
  currentFlowRate = 0;
  currentConsumption = 0.;
  initAllPins();
  dht1.begin();
  dht2.begin();
  dht3.begin();
  getSoilHumidity();
  lcd.init();
  currentMenu = 2;
  applyButton();
  applyButton();
  activateLight(true);
  showLogo();
  previousTime = currentTime;
  lcd.clear();
  motorIsActive = false;
  menuIsSelected = false;
  currentMenu = 0;
  forcedUpdate = 0;
  updateMenu(currentMenu);
}

/*********************************************************************************************/
/**
   @brief drawArrow отображение стрелок вправо-влево при перемещении по меню
   @param directed направление перемещения
   @return 0, если перемещения не будет, -1, если кнопка нажата влево ,
           1, если кнопка нажата вправо
*/
int drawArrow(int directed)
{
  if (directed == 0)
    return 0;
  if (!menuIsSelected)
  {
    if (directed > 0)
    {
      // вправо
      lcd.setCursor(0, 0);
      lcd.print(" ");
      lcd.setCursor(15, 0);
      lcd.print(">");
    }
    else
    {
      // влево
      lcd.setCursor(0, 0);
      lcd.print("<");
      lcd.setCursor(15, 0);
      lcd.print(" ");
    }
  }
  return 1;
}

/*********************************************************************************************/
/**
   @brief timeSumm добавить к начальному времени диапазон заданной длительности
   @param startTime начало (часы-минуты)
   @param duration длительность (часы-минуты)
   @return конец временного диапазона (часы-минуты)
*/
TimePair timeSumm(TimePair &startTime, TimePair &duration)
{
  TimePair result;
  result.minute = startTime.minute + duration.minute;
  result.hour = startTime.hour + duration.hour + (result.minute / 60);
  result.minute = result.minute % 60;
  return result;
}

/*********************************************************************************************/
/**
   @brief calculateShedule пересчитать время начала и конца полива для заданной линии
   @param idx номер линии
   @param tStart время начала полива
   @param tDuration длительность полива
*/
void calculateShedule(int8_t idx, TimePair &tStart, TimePair &tDuration)
{
  shedule[idx].startTime = tStart;
  shedule[idx].stopTime = timeSumm(tStart, tDuration);
  shedule[idx].activated = false;
  if (EEPROM.read(ADDR_LINE_1 + idx) != 0)
  {
    shedule[idx].isActive = true;
    if (++idx < 4)
      calculateShedule(idx, shedule[idx - 1].stopTime, tDuration);
  } else
  {
    shedule[idx].isActive = false;
    if (++idx < 4)
      calculateShedule(idx, tStart, tDuration);
  }
}

/*********************************************************************************************/
/**
   @brief checkNextSecond проверка - наступила ли следующая секунда
   @param ct структура времени
   @return true, если наступила новая секунда
*/
bool checkNextSecond(tmElements_t &ct)
{
  if (ct.Second != previousTime.Second)
    return true;
  return false;
}

/*********************************************************************************************/
/**
   @brief checkNextMinute проверка - наступила ли следующая минута
   @param ct структура времени
   @return true, если наступила новая минута
*/
bool checkNextMinute(tmElements_t &ct)
{
  if (ct.Minute != previousTime.Minute)
    return true;
  return false;
}

/*********************************************************************************************/
/**
   @brief checkNextHour проверка - наступил ли следующий час
   @param ct структура времени
   @return true, если наступил новый час
*/
bool checkNextHour(tmElements_t &ct)
{
  if (ct.Hour != previousTime.Hour)
    return true;
  return false;
}

/*********************************************************************************************/
/**
   @brief motorOn включение или выключение насоса
   @param ns true, если мотор необходимо включить, иначе false

  void motorOn(bool ns)
  {
    if (ns)
    {
        if (motorIsActive)
            return;
        delay(1000);
        digitalWrite(PIN_MOTOR, LOW);
    } else
    {
        if (!motorIsActive)
            return;
        digitalWrite(PIN_MOTOR, HIGH);
        delay(5000);
    }
    motorIsActive = ns;
  }

  /*********************************************************************************************/
/**
   @brief relayOn включение или отключение линии (открытие/закрытие клапана)
   @param idx номер линии
   @param ns true, если клапан необходимо открыть, иначе false
*/
/*
  void relayOn(int8_t idx, bool ns)
  {
   bool anotherLineActive = false;
   shedule[idx].currentState = ns;
   for (int8_t i = 0; i < 4; i++)
   {
       if (i == idx)
           continue;
       if ((shedule[i].currentState))
           anotherLineActive = true;
   }
   if (!anotherLineActive)                         // если все линии отключены
   {
       if (ns)                                     // и надо включить полив, то
       {
  #ifdef SERIAL_DEBUG
           Serial.println("switch on relay, then power on motor");
  #endif
           digitalWrite(PIN_RELAY[idx], LOW);      // включить реле,
           motorOn(ns);                            // потом включить мотор
       } else
       {
  #ifdef SERIAL_DEBUG
           Serial.println("power off motor, then switch off relay");
  #endif
           motorOn(ns);                            // сначала выключить мотор,
           digitalWrite(PIN_RELAY[idx], HIGH);     // потом выключить реле
       }
   } else                                          // а если хоть одна линия активна
   {
       if (ns)                                     // и надо включить полив, то
       {
  #ifdef SERIAL_DEBUG
           Serial.println("just switch on relay");
  #endif
           digitalWrite(PIN_RELAY[idx], LOW);      // включить реле
       } else
       {
  #ifdef SERIAL_DEBUG
           Serial.println("just switch off relay");
  #endif
           digitalWrite(PIN_RELAY[idx], HIGH);     // выключить реле
           control[idx].isActive = false;
       }
   }
  }
*/
/*********************************************************************************************/
/**
   @brief checkButtonActions функция для проверки нажатий кнопок ручного включения полива
*/
/*
  void checkButtonActions()
  {
   int8_t mask = 0;
   MessageType mt;

   TimePair thisTime, nextTime;
   thisTime.hour = currentTime.Hour;
   thisTime.minute = currentTime.Minute;
   nextTime.hour = EEPROM.read(ADDR_BUTTON_HOUR);
   nextTime.minute = EEPROM.read(ADDR_BUTTON_MINUTE);
  //    Serial.println("==================================================");
   for (int8_t i = 0; i < 4; i++)
   {
       if (!control[i].starter->buttonPressed())
       {
           switch (control[i].starter->buttonClicked())
           {
           case 1:                     // короткое нажатие

               if (control[i].isActive)
               {
                   if (control[i].isUnlimited)
                   {
                       mt = MTControlOn;
                       control[i].isUnlimited = false;
                       control[i].stopTime = timeSumm(thisTime, nextTime);
                       mask |= (1 << i);
                       break;
                   }
                   mt = MTControlOff;
                   control[i].isActive = false;
                   relayOn(i, false);
                   mask |= (1 << i);
               } else
               {
                   mt = MTControlOn;
                   control[i].stopTime = timeSumm(thisTime, nextTime);
                   control[i].isActive = true;
                   relayOn(i, true);
                   mask |= (1 << i);
               }
  #ifdef SERIAL_DEBUG
               Serial.print("button ");
               Serial.print(i + 1);
               Serial.println(" was pressed. Now ");
               Serial.print(currentTime.Hour);
               Serial.print(" : ");
               Serial.print(currentTime.Minute);
               Serial.print(" : ");
               Serial.println(currentTime.Second);
  #endif
               break;
           case 2:                     // длинное нажатие
               if (control[i].isActive)
               {
                   mt = MTUnlimited;
                   control[i].isUnlimited = false;
                   control[i].isActive = false;
                   relayOn(i, false);
                   mask |= (1 << i);
               } else
               {
                   mt = MTCompleted;
                   control[i].stopTime = timeSumm(thisTime, nextTime);
                   control[i].isUnlimited = true;
                   control[i].isActive = true;
                   relayOn(i, true);
                   mask |= (1 << i);
               }

               break;
           default:                    // нажатия не было
               break;
           }
       }
   }
   if (mask > 0)
       showInfo(mt, mask);
  }
*/
/*********************************************************************************************/
/**


  /*********************************************************************************************/
/**
   @brief checkControlledOperations проверка необходимости выполнения ручных действий
*/
void checkControlledOperations()
{
  int8_t mask = 0;
  MessageType mt;

  int minuteCurrent = currentTime.Hour * 60 + currentTime.Minute;
  int minuteControl;
  for (int8_t i = 0; i < 4; i++)
  {
    if ((!control[i].isActive) || (control[i].isUnlimited))
      continue;
    minuteControl = control[i].stopTime.hour * 60 + control[i].stopTime.minute;
    if (minuteCurrent > minuteControl)
    {
      if (shedule[i].activated)
      {
        control[i].stopTime.hour = shedule[i].stopTime.hour;
        control[i].stopTime.minute = shedule[i].stopTime.minute;
      } else
      {
        mt = MTControlOff;
        control[i].isActive = false;
        relayOn(i, false);
        mask |= (1 << i);
      }
    }
  }
  if (mask > 0)
    showInfo(mt, mask);
}

/*********************************************************************************************/
/**
   @brief checkSheduledOperations проверка необходимости выполнения запланированных действий
*/
void checkSheduledOperations()
{
  int8_t mask = 0;
  MessageType mt;

  for (int8_t i = 0; i < 4; i++)
  {
    if (!shedule[i].isActive)
      continue;
    if ((shedule[i].activated) && (shedule[i].stopTime.hour == currentTime.Hour) && (shedule[i].stopTime.minute == currentTime.Minute))
    {
      mt = MTSheduleOff;
      shedule[i].activated = false;
      relayOn(i, false);
      // надо принудительно прекратить и полив линии, запущенный с кнопки, если он был активен
      if (control[i].isActive)
      {
        control[i].isUnlimited = false;
        control[i].isActive = false;
        control[i].stopTime.hour = shedule[i].stopTime.hour;
        control[i].stopTime.minute = shedule[i].stopTime.minute;
      }
      mask |= (1 << i);
    }
    if ((!shedule[i].activated) && (shedule[i].startTime.hour == currentTime.Hour) && (shedule[i].startTime.minute == currentTime.Minute))
    {
      mt = MTSheduleOn;
      relayOn(i, true);
      shedule[i].activated = true;
      mask |= (1 << i);
    }
  }
  if (mask > 0)
    showInfo(mt, mask);
}

/*********************************************************************************************/
/**
   @brief denyByFlowmeter проверка данных с расходомера и аварийное отключение мотора в случае необходимости
*/
void denyByFlowmeter()
{
  bool activeLines = false;
  for (int8_t i = 0; i < 4; i++)
  {
    if (shedule[i].activated)
      activeLines = true;
    if (control[i].isActive)
      activeLines = true;
  }
  if (!activeLines)
    motorOn(false);
}

/*********************************************************************************************/
/**
   @brief loop рабочий цикл поливатора
*/
void loop()
{
  RTC.read(currentTime);
  if (checkJoystickActions())
    updateMenu(currentMenu);

  checkButtonActions();

  if (checkNextSecond(currentTime))
  {
    cli();                                      // отключим обработку прерываний для корректной работы со счетчиком
    currentFlowRate = ((waterCounter - waterCountPrevious) * 60 / 5.5);
    currentConsumption += (float(currentFlowRate) / 60.);
    waterCountPrevious = waterCounter;
    sei();                                      // снова включим обработку прерываний через минимально короткое время
    if ((currentMenu == 10) && (displayIsActive))
      updateMenu(currentMenu);
    if (checkNextMinute(currentTime))
    {
      if (checkNextHour(currentTime))
      {
        if (currentTime.Hour == 0)          // если полночь, то перегрузиться
          softReset();
      }
      checkControlledOperations();
      checkSheduledOperations();
      if ((currentMenu == 1) && (!menuIsSelected) && (forcedUpdate == 0) && (displayIsActive))
        updateMenu(currentMenu);
      if (currentTime.Minute % 10 == 0)
        getSoilHumidity();
    }
    if ((currentMenu == 1) && (!menuIsSelected) && (forcedUpdate == 0) && (displayIsActive))
    {
      lcd.setCursor(7, 1);
      if ((currentTime.Second % 2) > 0)
        lcd.print(": ");
      else
        lcd.print(" :");
    }
    if (forcedUpdate > 0)
    {
      if (--forcedUpdate == 0)
        updateMenu(currentMenu);
    }
    if (displayIsActive && (lastAction > 0) && (currentMenu != 1))
    {
      if (--lastAction == 0)
        activateLight(false);
    }
  }

  previousTime = currentTime;
}

Примерно что я хотел но никак не выходит

gena
Offline
Зарегистрирован: 04.11.2012

  Фига се "нуб". Я с контроллерами уже лет 20-ть знаком, а таких больших программ так и не написал (ну разве что на ассемблере, когда то, давно). И это за две недели то.

bwn
Offline
Зарегистрирован: 25.08.2014

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

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Можно использовать виртуальную машину отладки, собрать код там, загрузить в Proteus (создав схему) и отладить самостоятельно.

Вот пример как это могло бы выглядеть:

Клапауций 234
Offline
Зарегистрирован: 24.10.2016

uni пишет:

Можно использовать виртуальную машину отладки.

теперь к неработающим кускам кода у него будет неработающая виртуальная машина

дай ему ссылку на виртуального юзера.

osele
osele аватар
Offline
Зарегистрирован: 12.11.2016

Мужики как убрать бегане символов по экрану подсажите плиз . Вот таким скейчем удалось запустить DS 1302 и вывести на lcd16*2

 

#include <stdio.h>
#include <DS1302.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F, 16, 2); 
 
namespace {
 
const int kCePin   = 5;  // Chip Enable
const int kIoPin   = 6;  // Input/Output
const int kSclkPin = 7;  // Serial Clock
 
// Create a DS1302 object.
DS1302 rtc(kCePin, kIoPin, kSclkPin);
 
String dayAsString(const Time::Day day) {
  switch (day) {
    case Time::kSunday: return "Sunday";
    case Time::kMonday: return "Monday";
    case Time::kTuesday: return "Tuesday";
    case Time::kWednesday: return "Wednesday";
    case Time::kThursday: return "Thursday";
    case Time::kFriday: return "Friday";
    case Time::kSaturday: return "Saturday";
  }
  return "(unknown day)";
}
 
void printTime() {
  // Get the current time and date from the chip.
  Time t = rtc.time();
 
  // Name the day of the week.
  const String day = dayAsString(t.day);
 
  // Format the time and date and insert into the temporary buffer.
  char buf[50];
  snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
           day.c_str(),
           t.yr, t.mon, t.date,
           t.hr, t.min, t.sec);
 
  // Print the formatted string to serial so we can see the time.
  Serial.println(buf);
   lcd.println(buf);
}
 
}  // namespace
 
void setup() {
  Serial.begin(9600);
    lcd.init(); 
  // Initialize a new chip by turning off write protection and clearing the
  // clock halt flag. These methods needn't always be called. See the DS1302
  // datasheet for details.
  rtc.writeProtect(false);
  rtc.halt(false);
  lcd.backlight();
  lcd.print("t");
  // Make a new time object to set the date and time.
  // Sunday, September 22, 2013 at 01:38:50.
  Time t(2016, 11, 12, 19, 38, 00, Time::kSunday);
 
  // Set the time and date on the chip.
  rtc.time(t);
}
 
// Loop and print the time every second.
void loop() {
  printTime();
  delay(1000);
}