Время
- Войдите на сайт для отправки комментариев
Ср, 09/11/2016 - 10:57
Прошу помощи. Я начинающий программист в свере ардуино(нуб короче). Вожусь над проектом 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, ¶mId[0]}, {L"TEK. BPEMЯ", 3, 2, 0, ¶mId[1]}, {L"HAЧAЛO ПOЛИBA", 1, 2, 0, ¶mId[1]}, {L"ДЛИTEЛbHOCTb", 2, 2, 0, ¶mId[1]}, {L"ПOЛИB C KHOПKИ", 1, 2, 0, ¶mId[1]}, {L"AKTИBHЫE ЛИHИИ", 1, 4, 0, ¶mId[2]}, {L"ПOЧBA", 5, 0, 0, ¶mId[3]}, {L"HACOC. CTAHЦИЯ", 1, 0, 0, ¶mId[3]}, {L"TEПЛИЦA", 4, 0, 0, ¶mId[3]}, {L"KPЫЛbЦO", 4, 0, 0, ¶mId[3]}, {L"PACXOД BOДЫ", 2, 0, 0, ¶mId[4]}, {L"PECTAPT", 4, 2, 0, ¶mId[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; }
Фига се "нуб". Я с контроллерами уже лет 20-ть знаком, а таких больших программ так и не написал (ну разве что на ассемблере, когда то, давно). И это за две недели то.
Вы ни разу не слыхали, что слона кушают маленькими кусочками. И чем заниматься селекцией непородистых кодов, проще научиться выводить свои. Вот и начните с кнопочек и экранчика. Если договоритесь с 234, он покажет как найти ссылку на "титановый лисапед" для кнопок.
Можно использовать виртуальную машину отладки, собрать код там, загрузить в Proteus (создав схему) и отладить самостоятельно.
Вот пример как это могло бы выглядеть:
Можно использовать виртуальную машину отладки.
теперь к неработающим кускам кода у него будет неработающая виртуальная машина
дай ему ссылку на виртуального юзера.
Мужики как убрать бегане символов по экрану подсажите плиз . Вот таким скейчем удалось запустить DS 1302 и вывести на lcd16*2