Очередная теплица (для комментариев)
- Войдите на сайт для отправки комментариев
Чт, 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 мм. Думаю, высокая стойкость будет и у электродов из угольных стержней от батареек. Многократно видел под открытым небом разложившиеся "в кашу" батарейки с целыми угольными электродами.