Очередная теплица (для комментариев)
- Войдите на сайт для отправки комментариев
Чт, 05/05/2016 - 20:49
Добрый день.
Это мой перый более менее серьезный проект, после обучающих уроков.
Нарисовал схему и набросал программу для теплицы, хотел бы услышать комментарии от более опытных людей.
//EEPROM 0 Минимальное значение температуры актуатора //EEPROM 1 Максимальное значение температуры актуатора //EEPROM 2 включен ли полив 1 канала //EEPROM 3 день включения полива 1 канала //EEPROM 4 час включения полива 1 канала //EEPROM 5 минута включения полива 1 канала //EEPROM 6 время полива 1 канала //EEPROM 7 включен ли полив 2 канала //EEPROM 8 день включения полива 2 канала //EEPROM 9 час включения полива 2 канала //EEPROM 10 минута включения полива 2 канала //EEPROM 11 время полива 2 канала //EEPROM 12 месяц последнего полива 1 канала //EEPROM 13 день следующего полива 1 канала //EEPROM 14 месяц последнего полива 2 канала //EEPROM 15 день следующего полива 2 канала #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) const char *monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; #include <OneWire.h> #include <LiquidCrystal.h> #include <DS1307RTC.h> #include <TimeLib.h> #include <Wire.h> #include <EEPROM.h> #include <TimerOne.h> #define MENUITERATIONCOUNT 20 #define LCD_RS 7 #define LCD_E 6 #define LCD_DB4 5 #define LCD_DB5 4 #define LCD_DB6 3 #define LCD_DB7 2 #define TERMCONN 12 #define UPBUTTON 13 #define DOWNBUTTON A0 //14 #define OKBUTTON A1 //15 #define OPENACTUATOR1 11 #define CLOSEACTUATOR1 10 #define STATUSACTUATOR1 A6 //16 #define POWERLINEACTUATOR1 0 #define OPENACTUATOR2 9 #define CLOSEACTUATOR2 8 #define STATUSACTUATOR2 A7 //17 #define POWERLINEACTUATOR2 1 #define IRRCHANEL1 A2 //20 #define IRRCHANEL2 A3 //21 #define SCLRTC A5 //19 #define SDARTC A4 //18 LiquidCrystal lcd(LCD_RS, LCD_E, LCD_DB4, LCD_DB5, LCD_DB6, LCD_DB7); // (RS, E, DB4, DB5, DB6, DB7) OneWire ds(TERMCONN); bool lcdActive = true; long previousMillis = 0; // храним время для отключения экрана long interval_2 = 10000; //задержка подсветки bool _actuatorOpen = false; tmElements_t tm; // переменная в которой будет хранится дата и время /* по умолчанию ставятся значения month = 0 day =0 */ int del = 500; // переменная ожидания между выборами меню(время горения ledpin в милисек) byte addr[8]; //адресс термодатчика byte type_s = 0; // тип термодатчика volatile float _currenttemp = 0; volatile int _iterationnumber = -1; void setup() { pinMode(UPBUTTON, INPUT); digitalWrite(UPBUTTON, HIGH); pinMode(DOWNBUTTON, INPUT); digitalWrite(DOWNBUTTON, HIGH); pinMode(OKBUTTON, INPUT); digitalWrite(OKBUTTON, HIGH); pinMode(STATUSACTUATOR1, INPUT); digitalWrite(STATUSACTUATOR1, HIGH); pinMode(STATUSACTUATOR2, INPUT); digitalWrite(STATUSACTUATOR2, HIGH); pinMode(POWERLINEACTUATOR1, INPUT); digitalWrite(POWERLINEACTUATOR1, HIGH); pinMode(POWERLINEACTUATOR1, OUTPUT); pinMode(POWERLINEACTUATOR2, INPUT); digitalWrite(POWERLINEACTUATOR2, HIGH); pinMode(POWERLINEACTUATOR2, OUTPUT); pinMode(OPENACTUATOR1, INPUT); digitalWrite(OPENACTUATOR1, HIGH); pinMode(OPENACTUATOR1, OUTPUT); pinMode(CLOSEACTUATOR1, INPUT); digitalWrite(CLOSEACTUATOR1, HIGH); pinMode(CLOSEACTUATOR1, OUTPUT); pinMode(OPENACTUATOR2, INPUT); digitalWrite(OPENACTUATOR2, HIGH); pinMode(OPENACTUATOR2, OUTPUT); pinMode(CLOSEACTUATOR2, INPUT); digitalWrite(CLOSEACTUATOR2, HIGH); pinMode(CLOSEACTUATOR2, OUTPUT); pinMode(IRRCHANEL1, INPUT); digitalWrite(IRRCHANEL1, HIGH); pinMode(IRRCHANEL1, OUTPUT); pinMode(IRRCHANEL2, INPUT); digitalWrite(IRRCHANEL2, HIGH); pinMode(IRRCHANEL2, OUTPUT); analogReference(DEFAULT); lcd.begin(16, 2); // Задаем размерность экрана printMessage("Init. Wait", 0); printMessage("V 1.0", 1); //инициализация датчика температуры if ( !ds.search(addr)) { //Выполняет поиск следующего 1-Wire устройства lcd.clear(); printMessage("No more addr.", 0); return; } if (OneWire::crc8(addr, 7) != addr[7]) { //Вычисляет CRC код байтов из массива dataArray, длиной length lcd.clear(); printMessage("CRC don't valid", 0); return; } if (addr[0] != 0x10 && addr[0] != 0x22 && addr[0] != 0x28 ) { lcd.clear(); printMessage("No temp probe", 0); } if (addr[0] == 0x10) type_s = 1; //Тип датчика DS18S20 //end инициализация датчика температуры // инициализация часов реального времени if (!RTC.read(tm)) { getDate(__DATE__); getTime(__TIME__); RTC.write(tm); } //проверка и установка времени setDateTime(tm); //end инициализация часов реального времени lcd.clear(); //Сброс даты следующего полива для канала 1 EEPROM.write(12, 255); EEPROM.write(13, 255); //Сброс даты следующего полива для канала 2 EEPROM.update(14, 255); EEPROM.update(15, 255); ActuatorSetUp(); IrregationSetUp(0); // 1 канал полива IrregationSetUp(1);// 2 канал полива Timer1.initialize(30000000); // 300 000 000 = 5 мин, 60 000 000 = 1 мин, 1 000 000 = 1 сек Timer1.attachInterrupt(Check); } void Check() { Timer1.stop(); printMessage("Start check", 0); _currenttemp = GetTemp(); printMessage("temp = ", 1); lcd.print(_currenttemp); RTC.read(tm); if (tm.Hour >= 21 || tm.Hour <= 6) { if (_actuatorOpen) { termostat ((float)EEPROM.read(0) - 10.0); _actuatorOpen = CheckOpenActuator(STATUSACTUATOR1, POWERLINEACTUATOR1) && CheckOpenActuator(STATUSACTUATOR2, POWERLINEACTUATOR2); } } else { _iterationnumber = _iterationnumber + 1; if (_iterationnumber > 3) _iterationnumber = 0; if (_iterationnumber == 0) { termostat (_currenttemp); _actuatorOpen = true; } } irrigation(0); irrigation(1); printMessage((String)tm.Month + "/" + (String)tm.Day + " " + (String)tm.Hour + ":" + (String)tm.Minute, 0); Timer1.start(); } void loop() { if (digitalRead(OKBUTTON)) { delay(del); previousMillis = millis(); lcdActive = true; lcd.display(); ShowMenu(); } if (digitalRead(UPBUTTON)) { delay(del); previousMillis = millis(); lcdActive = true; lcd.display(); } if (digitalRead(DOWNBUTTON)) { delay(del); previousMillis = millis(); lcdActive = true; lcd.display(); } if (lcdActive) LCD_LED_OFF(); // отключение экрана по таймеру } float GetTemp() { byte present = 0; byte data[12]; present = ds.reset(); //Выполняет сброс шины, необходимо перед связью с датчиком ds.select(addr); //Выполняет выбор устройства после сброса, передается ROM Код устройства. ds.write(0x44); //провести измерение температуры и записать данные в оперативную память delay(1500); present = ds.reset(); ds.select(addr); ds.write(0xBE);// считать последовательно 9 байт оперативной памяти for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read();//Считывает информационный байт с устройства } int16_t raw = (data[1] << 8) | data[0]; if (type_s) { raw = raw << 3; // 9 bit resolution default if (data[7] == 0x10) { // "count remain" gives full 12 bit resolution raw = (raw & 0xFFF0) + 12 - data[6]; } } else { byte cfg = (data[4] & 0x60); // at lower res, the low bits are undefined, so let's zero them if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms //// default is 12 bit resolution, 750 ms conversion time } return (float)raw / 16.0; } void printMessage(String message, int row) { lcd.setCursor(0, row); lcd.print(message); } bool getDate(const char *str) { char Month[12]; int Day, Year; uint8_t monthIndex; if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false; for (monthIndex = 0; monthIndex < 12; monthIndex++) { if (strcmp(Month, monthName[monthIndex]) == 0) break; } if (monthIndex >= 12) return false; tm.Day = Day; tm.Month = monthIndex + 1; tm.Year = CalendarYrToTm(Year); return true; } bool getTime(const char *str) { int Hour, Min, Sec; if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false; tm.Hour = Hour; tm.Minute = Min; tm.Second = Sec; return true; } void setDateTime(tmElements_t date) { date.Year = CalendarYrToTm(setDate(tmYearToCalendar(date.Year), 1, "Time")); date.Month = setDate(date.Month, 2, "Time"); date.Day = setDate(date.Day, 3, "Time"); date.Hour = setDate(date.Hour, 4, "Time"); date.Minute = setDate(date.Minute, 5, "Time"); date.Second = 0; RTC.write(date); } int setDate(int data, const int dataType, String mode) { String showingdata = ""; switch (dataType) { case 1: showingdata = "Year"; break; case 2: showingdata = "Month"; break; case 3: showingdata = "Day"; break; case 4: showingdata = "Hours"; break; case 5: showingdata = "Min"; break; case 6: showingdata = "Temp"; break; case 7: showingdata = "On/Off"; break; } lcd.clear(); //очищаем экран int x = 0; unsigned long _previousMillis = millis(); while (1) { if (x > MENUITERATIONCOUNT) { break; } printMessage("SET " + showingdata + " " + mode, 0); printMessage("-1 " + (String)data + " +1", 1); while (millis() - _previousMillis < del) { if (digitalRead(OKBUTTON)) { delay(del); lcd.clear(); return data; } if (digitalRead(UPBUTTON)) { delay(del); lcd.clear(); x = 0; data = data + 1; data = CheckData(data, dataType); } if (digitalRead(DOWNBUTTON)) { delay(del); lcd.clear(); x = 0; data = data - 1; data = CheckData(data, dataType); } } _previousMillis = millis(); x++; } return data; } int CheckData(int data, int dataType) { switch (dataType) { case 2: //month { if (data > 12) { return 1; } if (data < 1) { return 12; } break; } case 3: // day { if (data > 31) { return 1; } if (data < 1) { return 31; } break; } case 4: // hours { if (data > 23) { return 0; } if (data < 0) { return 23; } break; } case 5://minutes { if (data > 59) { return 0; } if (data < 0) { return 59; } break; } case 6: // temperature { if (data > 50) { return 50; } if (data < 15) { return 15; } break; } case 7://on/off { if (data < 0) return 1; if (data > 1) return 0; break; } } return data; } void LCD_LED_OFF()//таймер отключения подсветки { unsigned long currentMillis = millis(); //проверяем не прошел ли нужный интервал, если прошел то if (currentMillis - previousMillis > interval_2) { // сохраняем время последнего переключения previousMillis = currentMillis; lcd.noDisplay(); lcdActive = false; } } void ShowMenu() { printMessage("Press show menu", 0); int x = 0; unsigned long _previousMillis = millis(); while (1) { //бесконечный цикл if (x > MENUITERATIONCOUNT) { break; } while (millis() - _previousMillis < del) { if (digitalRead(OKBUTTON)) { delay(del); ActuatorSetUp(); IrregationSetUp(0); // 1 канал полива IrregationSetUp(1);// 2 канал полива break; } } _previousMillis = millis(); x++; } } //настройка открывания форточек void ActuatorSetUp() { int minTemp = EEPROM.read(0); if (minTemp == 255) { minTemp = 25; } EEPROM.update(0, setDate(minTemp, 6, "close")); minTemp = 255; minTemp = EEPROM.read(1); if (minTemp == 255) { minTemp = 25; } EEPROM.update(1, setDate(minTemp, 6, "open")); } //настройка полива void IrregationSetUp(int _chanelNum) { int offset = 0; if (_chanelNum == 1) offset = 5; //on/off 1 chanel int minTemp = EEPROM.read(2 + offset); if (minTemp == 255) { minTemp = 1; } EEPROM.update(2 + offset, setDate(minTemp, 7, (String)(_chanelNum + 1) + "Irr2")); if (minTemp == 0) return; //day interval minTemp = 255; minTemp = EEPROM.read(3 + offset); if (minTemp == 255) { minTemp = 1; } EEPROM.update(3 + offset, setDate(minTemp, 3, (String)(_chanelNum + 1) + "Irr 3")); //hour on minTemp = 255; minTemp = EEPROM.read(4 + offset); if (minTemp == 255) { minTemp = 19; } EEPROM.update(4 + offset, setDate(minTemp, 4, (String)(_chanelNum + 1) + "Irr 4")); //min on minTemp = 255; minTemp = EEPROM.read(5 + offset); if (minTemp == 255) { minTemp = 0; } EEPROM.update(5 + offset, setDate(minTemp, 5, (String)(_chanelNum + 1) + "Irr 5")); //on time minTemp = 255; minTemp = EEPROM.read(6 + offset); if (minTemp == 255) { minTemp = 5; } EEPROM.update(6 + offset, setDate(minTemp, 5, (String)(_chanelNum + 1) + "Irr 6")); } int termostat (float currentTemp) { int temp_min = EEPROM.read(0); int temp_max = EEPROM.read(1); if (currentTemp > temp_max) { openClosewindows(true); } else if (currentTemp < temp_min) { openClosewindows(false); } } void openClosewindows(bool open) { OpenActuatorByNum(OPENACTUATOR1, STATUSACTUATOR1, open, POWERLINEACTUATOR1); OpenActuatorByNum(OPENACTUATOR2, STATUSACTUATOR2, open, POWERLINEACTUATOR2); } //преобразование значения в состояние форточки int getStatus(int currentValue) { if (currentValue < 320) return 0; else if (currentValue >= 320 && currentValue < 660) return 1; else if (currentValue >= 660 && currentValue < 900) return 2; else return 3; } void OpenActuatorByNum(int portNumber, int statusNumber, bool open, int powerline) { //незабыть удалить значение для отладки при симуляции float devide = 2.7; digitalWrite(powerline, HIGH); int statusActuator = getStatus (analogRead(statusNumber) * devide);// / 341; if ((statusActuator == 3 && open) || (statusActuator == 1 && !open )) { digitalWrite(powerline, LOW); return; } int endstatus = statusActuator; if (open) endstatus = endstatus + 1; else endstatus = endstatus - 1; { while (1) { activateActuator(portNumber); statusActuator = getStatus (analogRead(statusNumber) * devide);// 341; if (statusActuator == endstatus) { break; } } } digitalWrite(powerline, LOW); } // проверка что рама закрыта bool CheckOpenActuator(int statusNumber, int powerline) { //незабыть удалить значение для отладки при симуляции float devide = 2.7; digitalWrite(powerline, HIGH); int statusActuator = getStatus (analogRead(statusNumber) * devide);// / 341; digitalWrite(powerline, LOW); return statusActuator > 1; } void activateActuator(int portNumber) { digitalWrite(portNumber, HIGH); delay (2000); digitalWrite(portNumber, LOW); } void irrigation(int _chanelNum) { if (_chanelNum != 0 && _chanelNum != 1) return; RTC.read(tm); int offset = 0; int offset2 = 0; if (_chanelNum == 1) { offset = 5; offset2 = 2; } int x = EEPROM.read(2 + offset); // ON irregation if (x == 1) { //считаение следующей даты полива int month = EEPROM.read(12 + offset2); if (month == 255 || month > 12) { month = tm.Month; } int day = EEPROM.read(13 + offset2); if (day == 255 || day > 31) { day = tm.Day; } int hour = EEPROM.read(4 + offset); int min = EEPROM.read(5 + offset); //проверям что дата полива наступила if (tm.Month == month && tm.Day == day && ( (tm.Hour == hour && tm.Minute >= min) || (tm.Hour > hour && tm.Minute < min) ) ) { if (_chanelNum == 0) IrrActivate(EEPROM.read(6), IRRCHANEL1); else if (_chanelNum == 1) IrrActivate(EEPROM.read(6 + offset), IRRCHANEL2); else return; //сдвигаем дату следующего полива на количество дней из настройки int daycount = EEPROM.read(3 + offset); day = day + daycount; switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: { if (day > 31) { day = 1; month = month + 1; } } break; case 4: case 6: case 9: case 11: { if (day > 30) { day = 1; month = month + 1; } break; } case 2: { if ((LEAP_YEAR(tm.Year) && day > 29) || (!LEAP_YEAR(tm.Year) && (day > 28))) { day = 1; month = month + 1; } break; } } //сохраняем следующую дату полива EEPROM.update(12 + offset2, month); EEPROM.update(13 + offset2, day); } } } void IrrActivate(int irrmin, int irrchanel) { digitalWrite(irrchanel, HIGH); delay(1000.0 * 60.0 * (long)irrmin); digitalWrite(irrchanel, LOW); }
Схема:
Вместо транзисторов BC546BP собираюсь использовать КТ315, TIP31 - КТ817, TIP32 - КТ816, которых осталось после школы целые коробки
Собственно, чего именно вы хотите услышать? Вопросов, критики, похвал?
Похвал - пожалуйста: похвально, что взялись за такой проект, сам подобный делаю, и финиш ещё ооочень нескоро: https://github.com/Porokhnya/GreenhouseProject
Критики: да особо никакой, каждый ССЗБ. Только учтите, что тонких мест в этом деле - гора. Например: часы 1307 - фтопку, лучше 3231, т.к. 1307 сильно убегают. Датчик температуры один? Маловато для теплицы, имхо. Что будете делать, если кол-во актуаторов или каналов полива захочется увеличить? Плодить переменные или дефайны?
Дружеский совет - сразу разносите код на логические единицы, в разные файлы. Например: код для работы с часами - в один файл, код для работы с LCD - в другой. Иначе очень быстро потонете в длиннющей простыне.
Повторюсь: начинание годное, удачи!
Добрый день.
Скорее критику и замечания по схеме и/или по коду. Насчет часов спасибо за совет не знал.
Теплица маленькая 3 на 2 метра думаю 1 датчика температуры хватит, да и грядок в ней всего 2.
у меня есть вопрос к бывалым:
какие датчики влажности почвы реально работают годами??? а то я не верю, что те , что на тексталите не закиснут через пару месяцев....
Добрый день.
Скорее критику и замечания по схеме и/или по коду. Насчет часов спасибо за совет не знал.
Теплица маленькая 3 на 2 метра думаю 1 датчика температуры хватит, да и грядок в ней всего 2.
еще критика:
для теплицы время вообще не нужно, нужед датчик освещенности (по нему можно определять в том числе и ПРАВИЛЬНОЕ время суток), датчик уличной температуры, датчик внутренеей температуры и датчик влажности воздуха (что-бы не допускать выпадение росы), ну и про полив - нужен датчик температуры воды....
у меня есть вопрос к бывалым:
какие датчики влажности почвы реально работают годами??? а то я не верю, что те , что на тексталите не закиснут через пару месяцев....
Те которые на текстолите разрушаются за месяц где то. Видел народ делал из пары электродов такие датчики (например тут описание http://oldoctober.com/ru/humidity_sensor/)
Добрый день.
Скорее критику и замечания по схеме и/или по коду. Насчет часов спасибо за совет не знал.
Теплица маленькая 3 на 2 метра думаю 1 датчика температуры хватит, да и грядок в ней всего 2.
еще критика:
для теплицы время вообще не нужно, нужед датчик освещенности (по нему можно определять в том числе и ПРАВИЛЬНОЕ время суток), датчик уличной температуры, датчик внутренеей температуры и датчик влажности воздуха (что-бы не допускать выпадение росы), ну и про полив - нужен датчик температуры воды....
Спасибо за совет, в следующей версии добавлю датчики влажности и внешней температуры
какие датчики влажности почвы реально работают годами???
Здесь на форуме видел описание беспроводного датчика влажности. Судя по фото, электроды были из грифелей для цанговых карандашей. Диаметр около 2 мм. Думаю, высокая стойкость будет и у электродов из угольных стержней от батареек. Многократно видел под открытым небом разложившиеся "в кашу" батарейки с целыми угольными электродами.