Описание:
В данном проекте мы сделаем автополив с записью показаний датчика влажности почвы, датчика освещенности, с сенсорным дисплеем (для управления устройством и построением графиков датчиков) и управлением насосом через силовой ключ. Наше устройство будет собирать данные и включать насос по необходимости в поливе в зависимости от показаний датчика влажности и устоновок пользователя.
Видео:
Нам понадобится:
- 1x Piranha ULTRA
- 1x Trema-модуль Емкостной датчик влажности почвы
- 1x Trema-модуль Датчик освещенности
- 1x Trema-модуль Адаптер карт microSD
- 1x Trema-модуль Цветной графический дисплей 2.8 TFT 320x240, Сенсорный
- 1х Trema-модуль Силовой ключ
- 1x Мембранный насос
Для реализации проекта необходимо установить библиотеки:
- Библиотека iardiono_RTC
- Библиотека UTFT
- Библиотека TouchScreen
Подключение:
Для удобства подключения мы воспользуемся Trema Shield для Arduino. Так же нам понадобится: Trema-модуль Емкостной датчик влажности, Trema-модуль датчик освещённости, trema-модуль Адаптер карт microSD, Цветной графический дисплей 2.8 TFT 320x240, Сенсорный и Trema-модуль Силовой ключ.
Для начала подкючим Trema Shield к Piranha ULTRA:
Далее подключим адаптер microSD карт и силовой ключ по следующим таблицам:
Адаптер карт microSD | Piranha ULTRA |
---|---|
SS(CS) | D10 |
MOSI | D11 |
MISO | D12 |
SCK | D13 |
VCC | VCC |
GND | GND |
Силовой ключ | Piranha ULTRA |
---|---|
G | GND |
V | VCC |
D | D8 |
Следующим шагом подключим датчик влажности, датчик освещённости и часы реального времени:
Датчик влажности | Piranha ULTRA |
---|---|
A | A5 |
V | VCC |
G | GND |
Датчик освещённости | Piranha ULTRA |
---|---|
S | A4 |
V | VCC |
G | GND |
Часы реального времени | Piranha ULTRA |
---|---|
SCL | SCL |
SDA | SDA |
Vcc | Vcc |
GND | Gnd |
Теперь подключим дисплей с touchscreen:
Дисплей | Piranha ULTRA |
---|---|
VCC | VCC |
GND | GND |
D/C | D2 |
RST | D3 |
CS | D4 |
SCK | D5 |
MOSI | D6 |
LED | D7 |
YU | A0 |
XR | A1 |
YD | A2 |
XL | A3 |
И финальным шагом подключим насос к внешнему питанию через уже подключенный к Piranha силовой ключ:
В результате у нас полючится вот такая система, в которую остаётся только загрузить скетч и вставить SD карту, отформатированную в файловой системе FAT16 или FAT32:
Внимание: Вствляем microSD карту только после успешной загрузки скетча
Калибровка датчика:
Для калибровки датчика можно воспользоваться следующим скетчем. Необходимо экспериментальным путём определить минимальные и максимальные показания датчика и затем внести их в определения MY_SENSOR_MAX и MY_SENSOR_MIN в основном проекте. Так же калибровка необходима при изменении длинны провода датчика.
MY_SENSOR_MAX
- датчик сухой, в воздухе
MY_SENSOR_MIN
- датчик по линию погружения в воде.
#define MOIST_SENSOR A5 void setup() { Serial.begin(9600); pinMode(MOIST_SENSOR, INPUT); } void loop() { int Sensor = analogRead(MOIST_SENSOR); Serial.println(Sensor); delay(500); }
Скетч проекта:
// Подключаем библиотеки: #include <EEPROM.h> // Подключаем библиотеку для работы с ПЗУ ардуино #include "UTFT.h" // Подключаем библиотеку для работы с дисплеем #include "TouchScreen.h" // Подключаем библиотеку для работы с TouchScreen #include <SPI.h> // Подключаем библиотеку для работы с SPI #include <SD.h> // Подключаем библиотеку для работы с SD картой #include <iarduino_RTC.h> // Подключаем библиотеку для работы с часами реального времени #define myPumpHysteresis 1 // часы, определяем гистерисис насоса (если насос сработал, он не будет работать независимо от показаний датчиков в течении этого времени) // Определяем выводы датчиков и ключа насоса #define MOIST_SENSOR A5 // Ескостной датчик влажности #define LDR A4 // Датчик освещенности #define PUMP 8 // Вывод управления насосом // определяем колибровочные значения датчика #define MY_SENSOR_MAX 629 // максимальное "сырое" значение (датчик сухой) #define MY_SENSOR_MIN 326 // минимальное "сырое" значение (датчик влажный) // определяем подмену значений для меню #define my_backlight_timer 30 // секунды, определяем время отключения подсветки #define mainMenu 0 // Определяем номер главного меню #define durationMenu 1 // Опредеояем номер меню установки насоса #define moistureMenu 2 // Опредеояем номер меню установки влажности #define timeMenu 3 // Опредеояем номер меню установки времени #define dateMenu 4 // Опредеояем номер меню установки даты #define plotMenu 5 // Опредеояем номер меню графика #define standBy 6 // Опредеояем номер меню #define SS 10 // Опредеояем номер меню #define draw true // Определяем значение draw #define erase false // Определяем значение erase #define plot_moisture_color VGA_AQUA // Определяем цвет кривой влажности #define plot_ldr_color VGA_YELLOW // Определяем цвет кривой освещенности #define background_color VGA_SILVER // Определяем цвет фона #define font_color VGA_BLACK // Определяем цвет шрифта #define button_color VGA_NAVY // Определяем цвет кнопки #define pump_color VGA_RED // Определяем цвет графика мотора #define stopButtonColor VGA_RED // Определяем цвет цвет кнопки "стоп" //определяем кнопки #define up1 1 // #define down1 2 // #define up2 3 // #define down2 4 // #define up3 5 // #define down3 6 // #define up4 7 // #define down4 8 // #define back 9 // #define date 10 // #define moist_up 11 // #define moist_down 12 // #define mainDuration 13 // #define mainMoisture 14 // #define mainTime 15 // #define mainPlot 16 // #define plotNext 17 // #define plotPrev 18 // // Определяем значения #define logInterval 276 // Определяем интервал пробы датчиков (13 раз в час) #define height 240 // Опрелеояем высоту дисплея #define width 320 // Определяем ширину дисплея #define latest 0 // Самый новый созданный файл для функции парсирования директории и меню графика // Определяем ключевые слова для функции парсирования директории #define prev -1 // Предыдущий #define next 1 // Следующий // Определяем ключевые слова для функции меню графика #define oldest 2 // самый старый файл #define between 1 // между самым новым и самым старым // bool mutex = false; // определяем взаимоисключение для SD.open() uint16_t currentFile = 0; // итератор "указатель" на текущий файл extern uint8_t SmallFont[]; // подключаем шрифт имитирующий семисегментный индикатор extern uint8_t BigFont[]; // подключаем шрифт имитирующий семисегментный индикатор extern uint8_t SevenSegNumFont[]; // подключаем шрифт имитирующий семисегментный индикатор // Определяем выводы используемые для управления дисплеем 2.8" TFT 320x240 UNO: const uint8_t RS = 6; // const uint8_t WR = 5; // const uint8_t CS = 4; // const uint8_t RST = 3; // const uint8_t SER = 2; // const uint8_t LED = 7; // // Определяем выводы используемые для чтения данных с TouchScreen: const uint8_t YP = A3; // Вывод Y+ должен быть подключен к аналоговому входу const uint8_t XM = A2; // Вывод X- должен быть подключен к аналоговому входу const uint8_t YM = A1; // Вывод Y- const uint8_t XP = A0; // Вывод X+ // Определяем экстремумы для значений считываемых с аналоговых входов при определении точек нажатия на TouchScreen: const int tsMinX = 100; // соответствующий точке начала координат по оси X const int tsMinY = 120; // соответствующий точке начала координат по оси Y const int tsMaxX = 950; // соответствующий максимальной точке координат по оси X const int tsMaxY = 915; // соответствующий максимальной точке координат по оси Y const int minPress = 10; // соответствующий минимальной степени нажатия на TouchScreen const int maxPress = 1000; // соответствующий максимальной степени нажатия на TouchScreen // переменные для хранения времени, датчика, длительности работы насоса const int duration_max = 25; // максимальное время работы насоса const int duration_min = 3; // минимальное время работы насоса const int sensor_moist_max= MY_SENSOR_MAX; // максимальное значение для датчика const int sensor_moist_min= MY_SENSOR_MIN; // минимальное значение для датчика const int set_moist_max = 99; // максимальное значение установки влажности const int set_moist_min = 1; // минимальное значение установки влажности // массивы для вывода в график uint8_t plot_moist[314]; // массив для вывода лога датчика влажности uint16_t plot_LDR[314]; // массив для вывода лога датчика освещенности bool pumpPlotFlag[312]; // массив для вывода флага срабатывания насоса // переменные насоса bool pump_stop = false; // флаг нажатия на кнопку стоп bool halt_pump = false; // флаг экстренной остановки bool pump_stopped = false; // флаг остановленного насоса bool pump_flag = false; // флаг сработавшего насоса bool pumpLogFlag = false; // флаг записи в лог-файл uint32_t pumpStartMillis; // миллисекунды старта насоса uint32_t pumpHysteresis = myPumpHysteresis*60*60; // гистерисис насоса // структура для хранения имён файлов логов struct files { // String Name = ""; // переменная для хранения названия файла uint32_t ints = 0; // переменная для хранения даты файла } *sFiles; // // таймеры и настройки String realtime = ""; // переменная для хранения текущего времени int moist = 99; // переменная для хранения текущей влажности почвы uint16_t set_moist = 78; // переменная для хранения влажности срабатывания насоса uint8_t duration = 3; // переменная для хранения времени работы насоса short menuState = 0; // состояние меню bool wakeFlag = true; // флаг подсветки дисплея bool waitFlag = false; // флаг выхода из режима ожидания uint8_t plotMenuDay = latest; // переменная состояния меню графика // таймеры: unsigned long backlightMillis; // таймер подсветки unsigned long minuteMillis; // таймер обновления минут в главном меню unsigned long moistureMillis; // таймер обновления влажности в главном меню unsigned long logMillis; // таймер записи лога unsigned long wakeFlagMillis; // таймер выхода из режима ожидания unsigned long backlight_timer = my_backlight_timer * 1000; // таймер подсветки unsigned long sec; // таймер // UI const int plotStartX = 3; // точка X началa графика const int plotStartY = 114; // точка Y началa графика const uint16_t moist_x = 170; // положение данных датчика влажности по оси Х // Создаём объекты библиотек: iarduino_RTC time(RTC_DS1307); // Создаём объект часов реального времени UTFT myGLCD(TFT01_24SP, RS, WR, CS, RST, SER); // Создаём объект для работы с дисплеем TouchScreen ts= TouchScreen(XP, YP, XM, YM); // Создаём объект для работы с TouchScreen TSPoint p; // Создаём переменную для хранения координат и силы нажатия void setup() { // pinMode(LED, OUTPUT); // Определяем вывод управления подсветкой в режим выход analogWrite(LED, 196); // Определяем уровень ШИМ для подсаетки Serial.begin(9600); // Инициируем передачу данных в монитор последовательного порта на скорости 9600 бит/сек while (!Serial); // Ждём серийный протокол, если нужно if(!SD.begin(SS)){ // Если SD карта не инициировалась Serial.println("SD failed"); // Выводим текст в последовательный порт myGLCD.setFont(BigFont); // Устанавливаем шрифт myGLCD.setColor(VGA_WHITE); // Устанавливаем цвет шрифта myGLCD.setBackColor(VGA_BLACK); // Устанавливаем цвет фона myGLCD.print(("Isert SD card and restart") , 50, 120); // Выводим текст нв дисплей while(true); // Скетч дальше не выполняется } // time.begin(); // Инициируем работу с часами реального времени moist = readMoisture(); // Обновляем переменную влажности почвы realtime = time.gettime("H:i"); // Обновляем часы реального времени myGLCD.InitLCD(); // Инициируем работу с TFT дисплеем myGLCD.clrScr(); // Отчищаем экран дисплея myGLCD.setBackColor(background_color); // Устанавливаем цвет фона if (EEPROM.read(0) == 0xFF) saveSettings; // если первый байт ипрома не инициализирован, сохраняем настройки else readSettings(); // если нет - читаем настройки delay(100); // myGLCD.setColor(background_color); // Устанавливаем цвет фона myGLCD.fillRect(0, 0, width, height); // заливаем экран readLog(plot_moist, plot_LDR, pumpPlotFlag, parseRoot(latest));// читаем файл лога delay(100); drawMainMenu(draw); // рисуем главное меню } void loop() { //Выводим время на главный экран ------------------------------- // если прошло 60 секунд if(millis() - minuteMillis > 60000 && menuState == mainMenu){ realtime = time.gettime("H:i"); // записываем текущее время в переменную myGLCD.print(realtime , 10, 35); // выводим переменную в главное меню if (time.Hours == 0 && time.minutes == 0){ // если полночь drawMainMenu(erase); // стираем главное меню (обновляем график и дату) zero_out(); // обнуляем массивы графика drawMainMenu(draw); // рисуем главное меню и пустой график } minuteMillis = millis(); // сбрасываем таймер минут } if (millis() - backlightMillis > backlight_timer && wakeFlag){ // если пришло время выключить подсветку и она включена lightOff(); // выключаем подсветку backlightMillis = millis(); // обновляем таймер } //Обновляем влажность в главном меню каждую секунду; if(millis() - moistureMillis > 1000 && menuState == mainMenu){ // drawMoisture(moist = readMoisture()); // выводим текущую влажность moistureMillis = millis(); // обновляем таймер } //Насос здесь--------------------------------------------------- //если влажность ниже установленной и насос ещё не сработал и насос не остановлен вручную if (moist < set_moist && !pump_flag && !halt_pump){ pump_stopped = false; // флаг остановки насоса drawPumpButton(erase); // стираем кнопку установок насоса drawStopButton(draw); // рисуем кнопку остановки насоса pump_flag = true; // флаг работающего насоса pumpLogFlag = true; // флаг для записи в файл лога pumpStartMillis = millis(); // обновляем таймер старта насоса pinMode(PUMP, OUTPUT); // переводим вывод управления насосом в режим выход digitalWrite(PUMP, HIGH); // устанавливаем вывод насоса в режим логической единицы } // если время работы насоса равно уставленному и насос работает и не остановлен или нажата кнопка стоп if(((millis() - pumpStartMillis > duration*1000) && pump_flag && !pump_stopped) || pump_stop){ pinMode(PUMP, INPUT); // переводим вывод управления насосом в режим вход drawStopButton(erase); // стираем кнопку стоп drawPumpButton(draw); // рисуем кнопку установок насоса pump_stopped = true; // насос остановлен if(pump_stop){ // если нажата кнопка стоп pump_flag = false; // сбрасываем флаг насоса pump_stop = false; // сбрасываем флаг ручной остановки halt_pump = true; // насос в режиме ручной остановки } } // ручная остановка сбрасывается, если влажность почвы стала выше установленной if (moist > set_moist) halt_pump = false; // сбрасываем флаг насоса, если прошло время гистерисиса if(pump_flag && !pumpHysteresis){ // pump_flag = false; // Сбрасывем флаг насоса pumpHysteresis = myPumpHysteresis*3600; // Устанавливаем значение гистерисиса } //* Конец насоса ----------------------------------------------- // Логгинг здесь ----------------------------------------------- if((millis() - logMillis) > 1000){ // Если прошла секунда time.gettime(); // Вызываем часы реального времени sec = time.Hours*3600 + time.minutes*60 + time.seconds; // Рассчитываем секунды от полуночи logMillis = millis(); // обновляем таймер if (pump_flag) pumpHysteresis--; // Если нассос сработал, декрементируем таймер гистерисиса } if (!(sec % logInterval) && !mutex) { // если подошёл заданный интервал времени String file = time.gettime("d-m-y"); // создаём строку "день-месяц-год" file += ".csv"; // добавляем к ней расширение .csv saveLog(file); // сохраняем в файл с названием из переменной if (menuState == mainMenu) updatePlot(); // если в главном меню, обновляем график } //* Конец логгинга---------------------------------------------- // Пользлвательский интерфейс здесь----------------------------- p = ts.getPoint(); // Считываем координаты и интенсивность нажатия на TouchScreen в структуру p // Возвращаем режим работы выводов: pinMode(XM, OUTPUT); // Возвращаем режим работы вывода X- в значение «выход» для работы с дисплеем pinMode(YP, OUTPUT); // Возвращаем режим работы вывода Y+ в значение «выход» для работы с дисплеем if(millis() - wakeFlagMillis > 100 && wakeFlag && waitFlag){ // Ожидаем 100 миллисекунд после включения подсветки waitFlag = false; // Сбрасывем флаг ожидания } if (p.z > minPress && p.z < maxPress && !wakeFlag){ // Если степень нажатия достаточна для фиксации координат TouchScreen и подсветка выключена waitFlag = true; // Устанавливаем флаг ожидания wakeFlagMillis = millis(); // Обновляем таймер ожидания backlightMillis = millis(); // Обновляем таймер подсветки lightOn(); // Включаем подсветку } if(p.z > minPress && p.z < maxPress && !waitFlag && wakeFlag){ // Если степень нажатия достаточна для фиксации координат TouchScreen, подсветка включена и мы не ждем после её включения backlightMillis = millis(); // Обновляем таймер подсветки p.x = map(p.x, tsMinX, tsMaxX, 0, 320); // Преобразуем значение p.x от диапазона tsMinX...tsMaxX, к диапазону 0...320 p.y = map(p.y, tsMinY, tsMaxY, 0, 240); // Преобразуем значение p.y от диапазона tsMinY...tsMaxY, к диапазону 0...240 switch(menuState){ // проверяем состояние меню // ------------ГЛАВНОЕ МЕНЮ------------ // case mainMenu: mainMenuFunc(); break; // -------МЕНЮ УСТАНОВОК ВРЕМЕНИ------- // case timeMenu: timeMenuFunc(); break; // -------МЕНЮ УСТАНОВОК ДАТЫ---------- // case dateMenu: dateMenuFunc(); break; // ------МЕНЮ УСТАНОВОК ВЛАЖНОСТИ------ // case moistureMenu: moistureMenuFunc(); break; // ------МЕНЮ УСТАНОВОК ДЛИТЕЛЬНОСТИ----// case durationMenu: durationMenuFunc(); break; // -------------МЕНЮ ГРАФИКА-----------// case plotMenu: plotMenuFunc(); break; default: break; } } // Конец пользовательского интерфейса } //Функция прорисовки главного меню void drawMainMenu(bool draw_erase){ // принимаемые значения: true - рисуем, false - стираем myGLCD.setFont(SmallFont); // устанавливаем шрифт // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); myGLCD.print("Time, date:" , 10, 10); // выводим текст на экран myGLCD.print("Moisture:" , 125, 10); // выводим текст на экран myGLCD.print("Duration:" , 225, 10); // выводим текст на экран myGLCD.setFont(BigFont); // устанавливаем шрифт myGLCD.print(realtime , 10, 35); // выводим время на экран String m = String(time.month); // записываем текущий месяц в переменную if (m.toInt() < 10) m = "0" + m; // добавляем ноль, если меньше 10 String d = String(time.day); // записываем текущий день в переменную if (d.toInt() < 10) d = "0" + d; // добавляем ноль, если меньше 10 myGLCD.print(d + "/" + m , 10, 60); // выводим дату на экран myGLCD.setFont(SmallFont); // устанавливаем шрифт myGLCD.print("Cur:", moist_x-32, 39); // выводим текст на экран myGLCD.print("Set:", moist_x-32, 64); // выводим текст на экран myGLCD.setFont(BigFont); // устанавливаем шрифт drawMoisture(readMoisture()); // выводим показания датчика влажности на экран myGLCD.print(String(set_moist) , moist_x, 60); // выводим установку влажности на экран drawMenuButton(2,4, draw_erase); // рисуем/стираем кнопку установок времени drawMenuButton(107,4, draw_erase); // рисуем/стираем кнопку установок drawPumpButton(draw_erase); // рисуем/стираем кнопку установок plot(draw_erase); // рисуем/стираем график } //Функция прорисовки графика void plot(bool draw_erase){ // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); myGLCD.setFont(SmallFont); // устанавливаем шрифт for (int i = 0; i < 24; i+=2){ // myGLCD.print(String(i) , plotStartX+5+i*13, 100); // выводим часы графика от 0 до 22 } myGLCD.print("Plot:" , plotStartX+2, 100-13); // выводим текст на экран myGLCD.print("Moisture" , plotStartX+7*8, 100-13); // выводим текст на экран myGLCD.print("Sunlight" , plotStartX+7*8+10*8, 100-13); // выводим текст на экран myGLCD.print("Pump" , plotStartX+7*8+10*8+10*8, 100-13); // выводим текст на экран // рисуем? да: цвет графика = цвет графика влажности, нет: цвет графика = цвет фона draw_erase ? myGLCD.setColor(plot_moisture_color) : myGLCD.setColor(background_color); myGLCD.print("-" , plotStartX+6*8, 100-13); // выводим текст на экран // рисуем? да: цвет = цвет графика освещенности, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(plot_ldr_color) : myGLCD.setColor(background_color); myGLCD.print("-" , plotStartX+6*8+10*8, 100-13); // выводим текст на экран // рисуем? да: цвет = цвет графика насоса, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(pump_color) : myGLCD.setColor(background_color); myGLCD.print("*" , plotStartX+6*8+10*8+10*8, 100-13); // выводим текст на экран // рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(button_color) : myGLCD.setColor(background_color); for (int i = 12; i < 120; i += 12){ // myGLCD.drawLine(plotStartX, 235 - i, 314, 235 - i); // рисуем сетку Y } for (int i = plotStartX; i < 312; i += 13){ // myGLCD.drawLine(i, plotStartY, i, 235); // рисуем сетку X } for (int x = plotStartX; x < 314; x++) { int newY = map(plot_LDR[x-plotStartX], 0, 1024, 0, 115); // определяем точку Y датчика освещённости int newY2 = map(plot_LDR[x-(plotStartX-1)],0,1024,0,115); // определяем точку Y2 датчика освещённости // рисуем? да: цвет = цвет графика освещенности, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(plot_ldr_color) : myGLCD.setColor(background_color); if(/*abs(newY - newY2) > 3 && */newY > 0 && newY2 > 0) // если показания датчика не ноль myGLCD.drawLine(x, 235 - newY, x+1, 235 - newY2); // рисуем линию от Y до Y2 //else myGLCD.drawPixel(x, 235 - newY); newY = map(plot_moist[x-plotStartX], 0, 100, 0, 115); // определяем точку Y датчика влажности newY2 = map(plot_moist[x-(plotStartX-1)], 0, 100, 0, 115); // определяем точку Y2 датчика влажности // рисуем? да: цвет = цвет графика влажности, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(plot_moisture_color) : myGLCD.setColor(background_color); if(/*abs(newY - newY2) > 3 && */newY > 0 && newY2 > 0) // если показания датчика не ноль myGLCD.drawLine(x, 235 - newY, x+1, 235 - newY2); // рисуем линию от Y до Y2 //myGLCD.drawPixel(x, 235 - newY); // выводим точку на экран // рисуем? да: цвет графика = цвет графика насоса, нет: цвет графика = цвет фона draw_erase ? myGLCD.setColor(pump_color) : myGLCD.setColor(background_color); if(pumpPlotFlag[x-plotStartX])myGLCD.fillCircle(x,235-newY,2); //рисуем круг, если сработал насос } // рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(button_color) : myGLCD.setColor(background_color); myGLCD.drawRoundRect(plotStartX,plotStartY,314,235); // рисуем рамку графика myGLCD.drawRoundRect(plotStartX-1,plotStartY-1,315,236); // рисуем рамку графика myGLCD.setColor(font_color); // устанавливаем цвет обратно для шрифта myGLCD.setFont(BigFont); // устнанавливаем шрифт } //функция прорисовки меню установок длительности работы насоса void drawDurationMenu(bool draw_erase){ // рисуем меню установок времени работы насоса uint8_t offset = 20; // общий сдвег элементов меню // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); myGLCD.setFont(SmallFont); // устанавливаем шрифт myGLCD.print("Pump duration menu", 110, 20); // выводим текст на экран myGLCD.setFont(BigFont); // устанавливаем шрифт myGLCD.print("Sec", 200, 144); // выводим текст на экран drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки кнопки drawUpButton(offset + 110, 50); // рисуем кнопку вверх drawDownButton(offset + 110, 150); // рисуем кнопку вниз drawDigits(String(duration), offset + 110, 97); } //функция прорисовки меню установок влажности срабатывания насоса void drawMoistureMenu(bool draw_erase){ uint8_t offset = 20; // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки кнопки "назад" myGLCD.setFont(SmallFont); // устанавливаем шрифт myGLCD.print("Set moisture pump", 110, 5); // выводим текст на экран myGLCD.print("activation menu",110, 17); // выводим текст на экран drawUpButton(offset + 110, 50); // рисуем кнопку вверх drawDownButton(offset + 110, 150); // рисуем кнопку вниз drawDigits(String(set_moist), offset + 110, 97); // выводим текст на дисплей } //функция прорисовки меню установок времени ------------------------------------------------------------------- void drawTimeMenu(bool draw_erase){ int offsetY = 80; // общий сдвег элементов меню // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки кнопки drawSmallButton(312-95,5,"date->"); // вызываем функцию прорисовки кнопки drawUpButton(80, offsetY); // рисуем кнопку вверх drawDownButton(80, offsetY+100); // рисуем кнопку вниз drawUpButton(160, offsetY); // рисуем кнопку вверх drawDownButton(160, offsetY+100); // рисуем кнопку вниз myGLCD.fillCircle(73+75, offsetY+62, 4); // двоеточие myGLCD.fillCircle(73+75, offsetY+82, 4); // двоеточие drawDigits(String(time.Hours), 80, offsetY+47); // минуты drawDigits(String(time.minutes), 80 + 80, offsetY+47); // число } //рисуем или стираем меню установок даты -------------------------------------------------------------------- void drawDateMenu(bool draw_erase){ int offsetY = 80; // сдвиг элементов меню по оси Y // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки малой кнопки drawUpButton(5, offsetY); // рисуем кнопку вверх drawDownButton(5, offsetY+100); // рисуем кнопку вниз drawUpButton(90, offsetY); // рисуем кнопку вверх drawDownButton(90, offsetY+100); // рисуем кнопку вниз drawUpButton(240, offsetY); // рисуем кнопку вверх drawDownButton(240, offsetY+100); // рисуем кнопку вниз drawDigits(String(time.day), 5, offsetY+47); // выводим день drawDigits(String(time.month), 90, offsetY+47); // выводим месяц drawDigits(String(20), 180, offsetY+47); // выводим текст на дисплей myGLCD.drawLine(71, offsetY+47+45, 84, offsetY+50); // рисуем косую черту myGLCD.drawLine(72, offsetY+47+45, 85, offsetY+50); // рисуем косую черту myGLCD.drawLine(73, offsetY+47+45, 86, offsetY+50); // рисуем косую черту drawDigits(String(time.year), 240, offsetY+47); // выводим год } // функция прорисовки меню графика void drawPlotMenu(bool draw_erase){ // рисуем? да: цвет шрифта = цвет шрифта, нет: цвет шрифта = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); myGLCD.setFont(SmallFont); // устнанавливаем шрифт myGLCD.print(sFiles[currentFile].Name, 120, 50); // Выводим название файла на дисплей drawSmallButton(108, 5, "home"); // рисуем кнопку "домой" // если самый старый файл - стираем кнопку if (plotMenuDay == oldest) myGLCD.setColor(background_color); drawSmallButton(5, 5, "<-prev"); // рисуем кнопку // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); // если самый новый файл - стираем кнопку if (plotMenuDay == latest) myGLCD.setColor(background_color); drawSmallButton(width-100, 5, "next->"); // рисуем кнопку plot(draw_erase); // рисуем график датчиков } // функция включения подсветки void lightOn(){ analogWrite(LED, 196); // устанавливаем значение ШИМ на выводе подсветки wakeFlag = true; // устанавливаем флаг подсветки } // функция выключения подсветки void lightOff(){ digitalWrite(LED, LOW); // устанавливаем логический ноль на выводе подсветки wakeFlag = false; // сбрасываем флаг подсветки } // функция вывода текущей влажности void drawMoisture(int moist){ if (moist < 10){ // если число до 10 myGLCD.print(" " + String(moist) , moist_x, 35); // выводим пробел } else myGLCD.print(String(moist) , moist_x, 35); // выводим показания датчика } // функция прорисовки цифр void drawDigits(String digits, int x, int y){ myGLCD.setFont(SevenSegNumFont); // устанавливаем шрифт if (digits.toInt() < 10){ // если одна цифра myGLCD.print("0" + digits, x, y); // добавляем ноль и выводим на дисплей } else myGLCD.print(digits, x, y); // выводим цифры на дисплей } // функция прорисовки кнопки главного меню void drawMenuButton(uint16_t x, uint16_t y, bool draw_erase){ // рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(button_color) : myGLCD.setColor(background_color); myGLCD.drawRoundRect(x,y,x+101,y+82); // выводим прямоугольник myGLCD.drawRoundRect(x+1,y+1,x+100,y+81); // выводим прямоугольник // рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(VGA_WHITE) : myGLCD.setColor(background_color); myGLCD.drawLine(x+1,y+3,x+1,y+80); // выводим линию myGLCD.drawLine(x+3,y+1,x+98,y+1); // выводим линию // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); } // функция прорисовки малой кнопки void drawSmallButton(int x, int y, String bName){ myGLCD.drawRoundRect(x, y, x+95, y+40); // выводим прямоугольник myGLCD.drawRoundRect(x+1, y+1, x+94, y+39); // выводим прямоугольник myGLCD.setFont(SmallFont); // устанавливаем шрифт myGLCD.print(bName, x+20, y+15); // выводим название кнопки } // функция прорисовки кнопки вверх void drawUpButton(int x, int y) { myGLCD.drawRoundRect(x, y, x+65, y+45); // выводим прямоугольник myGLCD.drawRoundRect(x-1, y-1, x+66, y+46); // выводим прямоугольник myGLCD.setFont(BigFont); // устанавливаем шрифт myGLCD.print("UP", x+20, y+15); // выводим название кнопки } // функция прорисовки кнопки вниз void drawDownButton(int x, int y){ myGLCD.drawRoundRect(x, y, x+65, y+45); // выводим прямоугольник myGLCD.drawRoundRect(x-1, y-1, x+66, y+46); // выводим прямоугольник myGLCD.print("DOWN", x+1, y+15); // выводим название кнопки } // функция чтения файла лога void readLog(uint8_t* mos_val, uint16_t* ldr_val, bool* pump_log_flag, String logFile) { zero_out(); // обнуляем массивы графика mutex = true; // взаимоисключение для SD.open File myFile = SD.open(logFile); // открываем файл с названием logFile String value = ""; // создаем пустую переменную строки byte Byte; // создаем переменную для чтения из байта из файла uint8_t time_flag = 0; // создаем переменную флагов(часы - флаг=0, минуты - флаг=1 секунды - флаг=2) uint8_t log_hour, log_minute, log_second; // создаем переменные для хранения часов, минут, секунд uint32_t log_time; // создаем переменную для вычисления времени взятия пробы в секундах while(myFile.available()){ // Пока файл не закончился Byte = myFile.read(); // Читаем байты в переменную if(Byte == '\n') { // Если символ новой строки myFile.seek(myFile.position() - 23); // Отматываем на 23 байта назад while((Byte = myFile.read())!=','){ // Пока не символ запятой if (Byte != ':'){ // Если не двоеточие value +=char(Byte); // Накапливаем в строку байты } else { // Если двоеточие switch(time_flag){ // проверяем переменную флагов времени //если флаг равен нулю - записываем час, устанавливаем флаг на минуты case 0: log_hour = value.toInt(); time_flag = 1; value = ""; break; //если флаг равен единице - записываем минуты, устанавливаем флаг на секунды case 1: log_minute = value.toInt(); time_flag = 2; value = ""; break; default: value = ""; break; } } } log_second = value.toInt(); // заисывем секунды //вичисляем время взятия пробы в секундах от 00:00 log_time = (uint32_t)log_hour*3600 + log_minute*60 + log_second; int elem = map(log_time, 0, 86399, 0, 311); // преобразуем значения секунд к элементам массива value = ""; // обнуляем строку time_flag = 0; // обнуляем флаг while((Byte = myFile.read())!=','){ // пока не символ запятой value += char(Byte); // накапливаем в строку } // mos_val[elem] = value.toInt(); // записываем показания влажности в элемент массива value = ""; // обнуляем строку while((Byte = myFile.read())!=','){ // пока не запятая value += char(Byte); // накапливаем в строку } ldr_val[elem] = value.toInt(); // записываем показания освещенности в элемент массива value = ""; // обнуляем строку myFile.read(); // считываем байт Byte = myFile.read(); // записываем байт char c = char(Byte); // записываем байт как символ int i = c - '0'; // приводим символ к значению целого числа pump_log_flag[elem] = bool(i); // записываем значение в массив while((Byte = myFile.read())!='\n'); // ждём конца строки } } myFile.close(); // закрываем файл mutex = false; // сбрасывем взаимоисключение для SD.open } // функция чтения датчика влажности int readMoisture() { pinMode(MOIST_SENSOR, INPUT); // Устанавливаем вывод датчика в режим входа uint16_t fluct = 0; // Создаём переменную для суммы 10 считываений с датчика for (int i = 0; i < 10; i++){ // Считываем с датчика 10 раз fluct += analogRead(MOIST_SENSOR); // при жтом суммируя показания в переменную } // fluct /= 10; // Делим переменную на 10 для вычисления среднего арифметического значения // приводим показания датчки к диапазону от 0 до 100 uint8_t m = map(fluct, sensor_moist_max, sensor_moist_min, 0, 100); if (m >= 100) m = 99; // ограничиваем верхнее показание датчика return m; // возвращаем значение } // функция чтения настроек void readSettings(){ // duration = EEPROM.read(0); // читаем значение настроек из ПЗУ set_moist = EEPROM.read(sizeof(duration)); // читаем значение настроек из ПЗУ } // функция сохранения настроек void saveSettings(){ // EEPROM.update(0, duration); // записываем значение настроек в ПЗУ EEPROM.update(sizeof(duration), set_moist); // записываем значение настроек в ПЗУ } // функция сохранения файла лога void saveLog(String fileName){ mutex = true; // устанавливаем взаимоисключение для SD.open() String REAL_TIME = time.gettime("d-m-Y, H:i:s"); // записываем значение, считанное с часов реального времени, в переменную REAL_TIME int ldr = analogRead(LDR); // записываем показания датчика освещённости String ldr_str = String(ldr); // конвертируем в строку if (ldr < 10) ldr_str = "0" + ldr_str; // добавляем ноль if (ldr < 100) ldr_str = "0" + ldr_str; // добавляем ноль if (ldr < 1000) ldr_str = "0" + ldr_str; // добавляем ноль File myFile = SD.open(fileName, FILE_WRITE); // создаём или открываем файл "d-m-y.csv" для записи значений myFile.print(REAL_TIME); // записываем переменную в файл myFile.print(", "); // записываем разделительный символ myFile.print(moist); // записываем переменную в файл myFile.print(", "); // записываем разделительный символ myFile.print(ldr_str); // записываем переменную в файл myFile.print(", "); // записываем разделительный символ myFile.println(pumpLogFlag); // записываем переменную в файл myFile.flush(); // записываем буфер в файл myFile.close(); // закрываем файл pumpLogFlag = false; // сбрасывем флаг насоса mutex = false; // сбрасываем взщаимоисключение для SD.open() } // функция нажатия кнопок bool buttonPress(int button) { uint8_t offset = 20; switch (button) { case back: if (p.x < 100 && p.y < 45) return true; else return false; case date: if (p.x > 217 && p.y < 45) return true; else return false; case moist_up: if (p.x > 110 + offset && p.x < 205 + offset && p.y > 50 && p.y < 95) return true; else return false; case moist_down: if (p.x > 110 + offset && p.x < 205 + offset && p.y > 150 && p.y < 200) return true; else return false; case up1: if (p.x > 5 && p.x < 50 && p.y > 80 && p.y < 125) return true; else return false; case up2: if (p.x > 80 && p.x < 80+65 && p.y > 80 && p.y < 125) return true; else return false; case up3: if (p.x > 160 && p.x < 160+65 && p.y > 80 && p.y < 125) return true; else return false; case up4: if (p.x > 240 && p.x < 240+65 && p.y > 80 && p.y < 125) return true; else return false; case down1: if (p.x > 5 && p.x < 50 && p.y > 180 && p.y < 225) return true; else return false; case down2: if (p.x > 80 && p.x < 80+65 && p.y > 180 && p.y < 225) return true; else return false; case down3: if (p.x > 160 && p.x < 160+65 && p.y > 180 && p.y < 225) return true; else return false; case down4: if (p.x > 240 && p.x < 240+65 && p.y > 180 && p.y < 225) return true; else return false; case mainDuration: if (p.x > 210 && p.y < 90) return true; else return false; case mainMoisture: if (p.x > 120 && p.x < 210 && p.y < 90) return true; else return false; case mainTime: if (p.x > 5 && p.x < 120 && p.y < 90) return true; else return false; case mainPlot: if (p.x > 5 && p.y > 120) return true; else return false; case plotPrev: if (p.x < 100 && p.y < 45) return true; else return false; case plotNext: if (p.x > width-45-95 && p.y < 45) return true; else return false; default: return false; } } // функция меню даты void dateMenuFunc(){ int8_t d = time.day; // записываем текущий день в переменную int8_t m = time.month; // записываем текущий месяц в переменную int8_t y = time.year; // записываем текущий год в переменную if (buttonPress(back)){ // если нажата кнопка назад drawDateMenu(erase); // стираем меню настроек drawTimeMenu(draw); // рисуем главное меню menuState = timeMenu; // меняем переменную состояния меню } else if (buttonPress(up1)) { // если нажата кнопка день вверх d++; // приращиваем день switch (m){ // проверяем кол-во дней в месяце case 1: // case 3: // case 5: // case 7: // case 8: // case 10: // case 12: if(d == 32) d = 1; break; // если январь, март, май, июль, август... ограничиваем до 31 case 4: // case 6: // case 9: // case 11: if(d == 31) d = 1; break; // если февраль, апрель, июнь... ограничиваем до 30 case 2: if(d == 28) d = 1; break; // TODO: здесь можно добавить високосный год (year%4 ==0 && (year % 100 != 0 || year % 400 == 0)) default: break; } time.settime(-1, -1, -1, d); // drawDigits(String(time.day), 5, 80+47); // } else if (buttonPress(down1)) { // если нажата кнопка день вниз d--; // switch (m){ // проверяем кол-во дней в месяце case 1: // case 3: // case 5: // case 7: // case 8: // case 10: // case 12: if(d == 0) d = 31; break; // если январь, март, май, июль, август... ограничиваем до 31 case 4: // case 6: // case 9: // case 11: if(d == 0) d = 30; break; // если февраль, апрель, июнь... ограничиваем до 30 case 2: if(d == 0) d = 28; break; // TODO: здесь можно добавить високосный год (year%4 ==0 && (year % 100 != 0 || year % 400 == 0)) default: break; } time.settime(-1, -1, -1, d); drawDigits(String(time.day), 5, 80+47); } else if (buttonPress(up2)) { // если кнопка месяц вверх m++; // приращиваем месяц if (m == 13) m = 1; // ограничиваем месяцы до 12 time.settime(-1, -1, -1, -1, m); // устанавливаем месяц в часы реального времени drawDigits(String(time.month), 90, 80+47); // выводим текст на экран } else if (buttonPress(down2)){ // кнопка месяц вниз m--; // уменьшаем месяц if (m == 0) m = 12; // ограничиваем месяцы до 1 time.settime(-1, -1, -1, -1, m); // устанавливаем месяц в часы реального времени drawDigits(String(time.month), 90, 80+47); // выводим текст на экран } else if (buttonPress(up4)){ // если кнопка год вверх y++; // приращиваем год if (y == 100) y = 0; // ограничиваем год до 2099 time.settime(-1, -1, -1, -1, -1, y); // устанавливаем год в часы реального времени drawDigits(String(time.year), 240, 80+47); // выводим текст на экран } else if (buttonPress(down4)){ // кнопка год вниз y--; // уменьшаем год if (m == -1) m = 99; // оганичиваем до 2000 time.settime(-1, -1, -1, -1, -1, y); // устанавливаем год в часы реального времени drawDigits(String(time.year), 240, 80+47); // выводим текст на экран } } // функция меню длительности работы насоса void durationMenuFunc() { if (buttonPress(back)) { // если нажата кнопка назад drawDurationMenu(erase); // стираем текущее меню drawMainMenu(draw); // рисуем главное меню menuState = mainMenu; // обновляем переменную состояний меню saveSettings(); // сохраняем настройки } else if (buttonPress(moist_up)) { // если кнопка вверх if (duration < duration_max) duration++; // приращиваем переменную drawDigits(String(duration), 110+20, 97); // обновляем её на жкране } else if (buttonPress(moist_down)) { // если кнопка вних if (duration > duration_min) duration--; // уменьшаем перменную drawDigits(String(duration), 110+20, 97); // обновляем её на экране } } // функция меню установок влажности void moistureMenuFunc() { if (buttonPress(back)) { // если нажата кнопка назад drawMoistureMenu(erase); // стираем текущее меню drawMainMenu(draw); // рисуем главное меню menuState = mainMenu; // обновляем переменную состояний меню saveSettings(); // сохраняем настройки } else if (buttonPress(moist_up)) { // если кнопка вверх if (set_moist < set_moist_max) set_moist++; // приращиваем переменную drawDigits(String(set_moist), 110+20, 97); // обновляем её на жкране } else if (buttonPress(moist_down)){ // если кнопка вних if (set_moist > set_moist_min) set_moist--; // уменьшаем перменную drawDigits(String(set_moist), 110+20, 97); // обновляем её на экране } } // функция меню установок часов реального времени void timeMenuFunc(){ int8_t H = time.Hours; // создаём переменную и записываем текущие часы int8_t i = time.minutes; // создаём переменную и записываем текущие минуты if (buttonPress(back)){ // если нажата кнопка назад realtime = time.gettime("H:i"); // обновляем переменную часов реального времени drawTimeMenu(erase); // стираем текущее меню drawMainMenu(draw); // рисуем главное меню menuState = mainMenu; // обновляем переменную состояний меню } else if (buttonPress(date)){ // если нажата кнопка даты time.settime(-1, i, H); // записываем время drawTimeMenu(erase); // стираем текущее меню drawDateMenu(draw); // рисуем главное меню menuState = dateMenu; // обновляем переменную состояний меню } else if(buttonPress(up2)){ // если нажата кнопка часы вверх H++; // прибавляем часы if (H == 24) H = 0; // ограничиваем часы до 23 time.settime(-1, -1, H); // устанавливаем новое время drawDigits(String(time.Hours), 80, 80+47); // выводим на экран } else if(buttonPress(down2)){ // если нажата кнопка часы вниз H--; // убавляем часы if (H == -1) H = 23; // ограничиваем часы до 00 time.settime(-1, -1, H); // устанавливаем новое время drawDigits(String(time.Hours), 80, 80+47); // выводим на экран } else if(buttonPress(up3)){ // если нажата кнопка минуты вверх i++; // прибавляем if (i == 60) i = 0; // ограничиваем часы до 23 time.settime(-1, i, -1); // устанавливаем новое время drawDigits(String(time.minutes), 160, 80+47); // выводим на экран } else if(buttonPress(down3)){ // если нажата кнопка минуты вниз i--; // убавляем if (i == -1) i = 59; // ограничиваем часы до 00 time.settime(-1, i, -1); // устанавливаем новое время drawDigits(String(time.minutes), 160, 80+47); // выводим на экран } } // функция главного меню void mainMenuFunc(){ if (buttonPress(mainDuration) && !pump_flag){ // если нажата кнопка установок насса drawMainMenu(erase); // стираем главное меню drawDurationMenu(draw); // рисуем меню установок работы насоса menuState = durationMenu; // меняем перменную состояний меню } else if(buttonPress(mainDuration) && pump_flag){ // если нажата кнопка установок длительности работы насоса и насос работает pump_stop = true; // экстренная остановка насоса } else if (buttonPress(mainMoisture)) { // если нажата кнопка установки влажности срабатывания насоса drawMainMenu(erase); // стираем главное меню drawMoistureMenu(draw); // рисуем меню установок влажности menuState = moistureMenu; // меняем переменную состояний меню } else if(buttonPress(mainTime)) { // если нажата кнопка установок времени drawMainMenu(erase); // стираем главное меню drawTimeMenu(draw); // рисуем меню установок времени menuState = timeMenu; // обновляем переменную состояний меню } else if (buttonPress(mainPlot)){ // если нажата кнопка меню просмотра графика drawMainMenu(erase); // стираем главное меню drawPlotMenu(draw); // рисуем меню графика menuState = plotMenu; // обновляем переменную состояний меню } } // функция меню графика void plotMenuFunc(){ if (buttonPress(mainMoisture)){ // если нажата кнопка "домой" drawPlotMenu(erase); // стираем график readLog(plot_moist,plot_LDR,pumpPlotFlag,parseRoot(latest));// читаем последний файл лога drawMainMenu(draw); // рисуем главное меню menuState = mainMenu; // меняем состояние меню } else if (buttonPress(plotNext) && plotMenuDay != latest){ // если нажата кнопка следующего файла и мы просматриваем не самый последний файл drawPlotMenu(erase); // стираем текущий график readLog(plot_moist,plot_LDR,pumpPlotFlag,parseRoot(next)); // читаем следующий файл drawPlotMenu(draw); // рисуем новый график } else if (buttonPress(plotPrev) && plotMenuDay != oldest){ // если нажата кнопка предыдущего файла и мы просматриваем не самый старый файл drawPlotMenu(erase); // стираем текущий график readLog(plot_moist,plot_LDR,pumpPlotFlag,parseRoot(prev)); // читаем предыдущий файл drawPlotMenu(draw); // рисуем новый график } } // функция парсирования корневой директории String parseRoot(int8_t action) { File dir = SD.open("/"); // открываем корневой каталог карты File entry; // создаем объект entry byte buf[12]; // создаём буффер для названия файла (8+3) int nFiles = 0; // переменная для хранения кол-ва файлов int j = 0; // переменная-итератор uint32_t Day, Month, Year; // перемнные для хранения даты while(true) { // Начинаем цикл entry = dir.openNextFile(); // Открываем следующий файл, if (!entry) { // если файлов не осталось dir.rewindDirectory(); // указатель на начало дериктории break; // выходим из цикла. } else if (!(entry.isDirectory())) { // Если файл не является директорией nFiles++; // записываем кол-во файлов } // entry.close(); // закрываем файл } // sFiles = new files[nFiles]; // выделяем память для структуры sFiles int i = 0; // создаём переменную-итератор while(true) { // входим в цикл entry = dir.openNextFile(); // открываем файл String check_name = entry.name(); // записываем название файла в строку if (!entry){ // если файлов не осталось dir.rewindDirectory(); // "перематываем" директорию break; // выходим из цикла } else if(!(entry.isDirectory()) && check_name.toInt()!=0){ // если файл не директория и имя файла содержит цифры sFiles[i].Name = entry.name(); // записываем названия файлов в переменные sFiles[i].Name.getBytes(buf, 12); // берем 12 байт названия в буфер uint8_t date_flag = 0; // флаг для вычисления даты String s = ""; // переменная для сбора чисел без символа дефиса int k, j; // итераторы for (k = 0, j = 0; k < 8 || j < 6; k++){ // запускаем цикл if(buf[k] != '-') { // если не дефис: s += char(buf[k]); // накапливаем в строку всё, кроме дефиса j++; // приращиваем j } else { // если дефис: switch(date_flag){ // проверяем флаг даты // если флаг 0, записываем день, устанавливаем флаг равным 1 case 0: Day = s.toInt(); date_flag = 1; s = ""; break; // если флаг 1, записываем месяц, устанавливаем флаг равным 2 case 1: Month = s.toInt(); date_flag = 2; s = ""; break; default: s = ""; break; // обнуляем строку } } } Year = s.toInt(); // записываем год s = ""; // обнуляем строку date_flag = 0; // обнуляем флаг даты sFiles[i].ints = Year*10000 + Month*100 + Day; // записываем в структуру; j = 0; // сбрасываем итератор j i++; // приращиваем итератор i } entry.close(); // закрываем файл } // dir.close(); // закрываем корневую директорию mysort(sFiles, 0, nFiles-1); // сортируем файлы по дате switch(action){ // проверяем переданную переменную case latest: currentFile = nFiles-1; break; // последний файл case next: if(currentFile < nFiles-1) currentFile++; break; // следующий файл case prev: if(currentFile > 0) currentFile--; break; // предыдущий файл default: break; // } // if (currentFile == 0) plotMenuDay = oldest; // если текущий файл - это начало массива, значит мы на самом старом файле // если текущий файл больше нуля и меньше размера массива - мы не на самом новом файле и не на самом старом else if (currentFile > 0 && currentFile < nFiles-1) plotMenuDay = between; else if (currentFile == nFiles-1) plotMenuDay = latest; // если текущий файл - равен размеру массива - мы на самом новом файле return sFiles[currentFile].Name; // возвращаем текущий файл } // функция перестановки элементов для mysort, в нашем случае для структуры files void myswap(files f[], int i, int j) { // files temp; // temp = f[i]; // f[i] = f[j]; // f[j] = temp; // } // // функция быстрой сортировки // void mysort(files f[], int left, int right){ // int i, last; // объявляем итераторы if (left >= right) // если мы перешли через точку сортировки - return; // возвращаемся из рекурсии myswap(f, left, (left+right)/2); // переставляем элеметы, точка сортировки - середина массива last = left; // записываем номер элемента for (i = left+1; i<= right; i++) // обходим массив слева направо if (f[i].ints < f[left].ints) // если текущий элемент меньше левого myswap(f, ++last, i); // переставляем местами и приащиваем last myswap(f, left, last); // переставляем местами левый и последний mysort(f, left, last-1); // функция вызввает сама себя для уменьшенного массива слева mysort(f, last+1, right); // функция вызввает сама себя для уменьшенного массива справа } // // функция обнуления массивов // void zero_out(){ // mutex = true; // memset(plot_moist, 0, sizeof(plot_moist)); // обнуляем массив датчика вляжности memset(plot_LDR, 0, sizeof(plot_LDR)); // обнуляем массив датчика освещённости memset(pumpPlotFlag, false, sizeof(pumpPlotFlag)); // обнуляем массив срабатывания насоса mutex = false; // } // функция прорисовки кнопки насоса void drawPumpButton(bool draw_erase){ // рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color); myGLCD.print(String(duration) + "s" , 260, 35); // выводим текст на экран drawMenuButton(213,4, draw_erase); // рисуем кноку } // функция прорисовки кнопки стоп void drawStopButton(bool draw_erase){ // рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона draw_erase ? myGLCD.setColor(stopButtonColor) : myGLCD.setColor(background_color); myGLCD.print("Stop!" , 240, 35); // выводим текст на экран int x = 213; // координата х для кнопки int y = 4; // координата у для кнопки myGLCD.drawRoundRect(x,y,x+101,y+82); // рисуем квадрат myGLCD.drawRoundRect(x+1,y+1,x+100,y+81); // рисуем квадрат myGLCD.setColor(font_color); // устанавливаем цвет } // функция обновления текущего графика void updatePlot(){ // вычисляем кол-во секунд от 00:00 uint32_t now = (uint32_t)time.Hours*3600+time.minutes*60+time.seconds; int i = map(now, 0, 86399, 0, 311); // приводим значения секунд к номеру элемента массива plot(erase); // стираем график plot_LDR[i] = analogRead(LDR); // считываем показания с датчика plot_moist[i] = readMoisture(); // считываем показания с датчика pumpPlotFlag[i] = pumpLogFlag; // записываем флаг насоса в массив plot(draw); // рисуем график }
Описание функций:
Функции draw....Menu()
- Назначение: прорисовка или стирание пикселей меню;
- Синтаксис: draw....Menu(true/false);
- Параметры: true - рисуем, false - стираем
- Возвращаемые значения: нет;
- Примечание: ....= Main, Duration, Moisture, etc.
Функция plot()
- Назначение: прорисовка или стирание пикселей графиков;
- Синтаксис: plot(true/false);
- Параметры: true - рисуем, false - стираем
- Возвращаемые значения: нет;
- Примечание: функция берет данные из массивов pumpPlotFlag, plot_LDR и plot_moist.
Функции draw....Button()
- Назначение: прорисовка или стирание пикселей кнопок;
- Синтаксис: draw....Button(int x, int y, [bool draw_erase, String bName]);
- Параметры: x - координаты х, y - координаты y (в некоторых функциях bName - название кнопки, draw_erase - рисуеи/стираем)
- Возвращаемые значения: нет;
- Примечание: ....= Small, Up, Down, etc.
Функция saveLog()
- Назначение: сохранеие текущих показаний датчиков;
- Синтаксис: saveLog(String fileName);
- Параметры: fileName - название файла для записи в него данных;
- Возвращаемые значения: нет;
- Примечание: функция записывает строку формата "ДД-ММ-ГГГГ, ЧЧ:ММ:СС, ВВ, ОООО, Ф" с последующим символом новой строки и возврата каретки.
- ДД - день месяца;
- ММ - месяц;
- ГГГГ - год;
- ЧЧ - часы от 0 до 23;
- ММ - минуты;
- СС - секунды;
- ВВ - двухзначные показания датчика влажности;
- ОООО - четырёхзначные показания датчика освещённости;
- Ф - флаг сработавшего насоса: 1, 0;
Функция buttonPress()
- Назначение: поиск файлов в корневом каталоге ;
- Синтаксис: if (buttonPress(int botton)){//};
- Параметры: значение кнопки.
- Возвращаемые значения: bool - кнопка нажата/не нажата (true/false);
- Примечание: возможные значения - back, datem moist_up, moist_down, up1(2,3,4), down1(2,3,4), mainDuration, mainMoisture, mainTime, mainPlot, plotPrev, plotNext.
Функция ....MenuFunc()
- Назначение: логика конкретного меню;
- Синтаксис: ....MenuFunc();
- Параметры: нет;
- Возвращаемые значения: нет;
- Примечание: ....= main, plotm, time, etc.
Функция zero_out()
- Назначение: функция обнуления массивов графика;
- Синтаксис: zeroOut();
- Параметры: нет;
- Возвращаемые значения: нет;
- Примечание: обнуляет массивы plot_moist, plot_LDR, pumpPlotFlag
Функция updatePlot()
- Назначение: обновляет текущий график текущими значениями;
- Синтаксис: updatePlot();
- Параметры: нет;
- Возвращаемые значения: нет;
Функция parseRoot()
- Назначение: поиск файлов в корневом каталоге ;
- Синтаксис: filename = parseRoot(latest);
- Параметры: int8_t action: 0 - самый новый файл, -1 - предыдущий файл, 1 - следующий файл.
- Возвращаемые значения: String - название файла;
- Примечание:
- Функция ищет файлы формата ЧЧ-ММ-ГГ.csv, где дата создания файла является именем файла;
- Файл, в имени которого отсутствуют числа или числа равны нулю будут проигнорированны функцией;
- Функция вызывает функцию mySort(), для сортировки структуры с названиями файлов;
Функция readLog()
- Назначение: Функция чтения и данных из файла;
- Синтаксис: readLog(uint8_t* mos_val, uint16_t* ldr_val, bool* pump_log_flag, String logFile);
- Параметры: mos_val - указатель на массив со значениямаи датчика влажности, ldr_val - указатель на массив со значениями датчика освещенности, pump_log_flag - указатель на массив флага срабатывания насоса, logFile - названия файла для парсирования;
- Возвращаемые значения: нет;
- Примечание:
- Функция записывает значения в массив преобразуя секунды от 0 (00:00) до 86399 (23:59) в итераторы массива от 0 до 311;
- Функция берёт время взятия пробы из файла;
Функция mySort()
- Назначение: функция сортировки, в данном случае структуры files;
- Синтаксис: mySort(files f[], int i, int j);
- Параметры: f[] - структура для сортировки, i - начало массива (или под-массива), j - конец массива (или под-массива);
- Примечание:
- Функция делит массив пополам и ищет слева элемент по значению больший чем средний, а справа меньше чем средний, затем меняет их местами. Затем рекурсивно вызывает себя для под-массивов слева и справа от среднего элемента;
- Функция вызывает функцию mySwap, для перестановки элементов местами;
- Функция является упращённой версией qsort() из книги Б. Кернигган, Д. Ритчи "Язык программирования C, второе издание", стр. 100;
Обсуждение