Простой умный инкубатор.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Добрый день. С умным брудером получилось, пришло время заняться инкубатором.

Задача - сделать простое по материалам и стоимости, но надежное и точное устройство для маленьких и средних бытовых инкубаторов. Для замены примитивных аналоговых терморегуляторов в корпусах то мыльниц и розеток. Получилось не совсем дешево, но компактно и функционально. 

Что мы имеем:

Термометр - народный DS18B20, с залитыми эпоксидным клеем контактами.

Влагомер и доп. термометр - SHT10.

Arduino ProMini в качестве мозга.

LCD 20х4 с I2C контроллером.

Пара твердотельных Omron-овских реле на плате.

Пищалка пьезо.

Двусторонняя плата 50х50мм.

Блок реле с опторазвязкой.

 

 

Что мы умеем:

ПИД регулятор с низкочастотным ШИМ в канале нагревателя. 

Измерение и поддержани заданной влажности.

Управление поворотом лотков с периодом 1-12 часов или случайным образом.

Управление аварийным вытяжным вентелятором при перегреве.

Подачей звуковой сигнализации и включение аварийного реле при аварии.

В качестве бонуса вольтметр входного напряжения. Это для случая работы от резервного аккумулятора.

Возможность использовать нагревател, увлажнитель, вентиляторы как с питанием от сети 220в, так и от постоянки 12в.

Экранное меню где настраиваются все вышеперечисленные параметры и сохранение настроек в EEPROM.

Визуализация в графическом виде на компьютере температур для удобной настройки ПИД коэффициентов.

 

Устройство собрано и проверено. Заказал изготовление плат в Китае, на повестке дня тестирование в реальном инкубаторе. Естественно функционал будет добавляться. Часов нет.

Скетч.

#include <PID_v1.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM2.h>
#include <DallasTemperature.h>
#include <OneWire.h>
//#include <SimpleTimer.h>
#include <Sensirion.h>



int del = 80;                     // переменная ожидания между выборами меню
unsigned int interval = 100;      // интервал сколько будет длиться цикл while, после чего перейдёт к следующему меню.(кол-во итераций)

#define heater_pin 13                  // нагреватель
#define humidifer_pin 12               // увлажнитель
#define fan_pin 11                    // вентилятор
#define alarm_pin 14                   // пин аварии
#define beeper_pin 9                //пищалка по аварии
#define dataPin 5                      //SHT10
#define clockPin 6                     //SHT10 
#define turn_pin 10                // управление поворотом
#define button_minus_pin 2            //пин кнопки "минус"
#define button_plus_pin 3             //пин кнопки "плюс"
#define button_enter_pin 4            //пин кнопки "enter"
#define DS18B20_Pin 7                 //пин термометра
#define setSampleTime 1000            //время цикла ПИД
#define voltmeter_pin 1               //вход А1 через делитель (22к/10к) подключен к питанию модуля. Измеряет до 16В.

boolean button_minus;
boolean button_plus;
boolean button_enter;
boolean turnFlag = 0;              // флаг поворота для случайного периода
float humidity;                  // Влажность
float temp1Ink;                  // Температура DS18B20  
float temp2Ink;                  // Температура SHT10
float dewpoint;                  // Точка росы

unsigned int rawData;
unsigned long currentTime;            // задаем переменные для тайминга поворота
unsigned long loopTime;
unsigned long serialTime; //this will help us know when to talk with processing
unsigned long now;
unsigned long trhMillis = 0;    // период опроса датчиков
byte measActive = false;
byte measType = TEMP;
const unsigned long TRHSTEP   = 3000UL;  // Sensor query period

LiquidCrystal_I2C lcd(0x27, 20, 4);  // инициализация библиотеки дисплея
//SHT1x sht1x(dataPin, clockPin);
OneWire oneWire(DS18B20_Pin);
DallasTemperature sensors(&oneWire);
Sensirion sht = Sensirion(dataPin, clockPin);

double Setpoint, Input, Output;            //объявляем переменные для ПИД
PID myPID(&Input, &Output, &Setpoint, 10, 4, 3, DIRECT); //Инициализируем ПИД-библиотеку и коэффициенты
int WindowSize = setSampleTime;                  // ширина окна терморегулятора 1 секунда.
unsigned long windowStartTime;

//SimpleTimer timer;

/*  EEPROM1 -   tempInk
 EEPROM5 (13) -   set_humidity
 EEPROM7 -   alarmTemp
 EEPROM9 -   fanTemp
 EEPROM11 -  turnPeriod            */




void setup() {
  Serial.begin(9600);

  lcd.init();          // Старт дисплея
  lcd.backlight();     // Включаем подсветку дисплея
  windowStartTime = millis();
  byte stat;
  byte error = 0;
  delay(15);

  myPID.SetOutputLimits(0, WindowSize); //задаем лимиты ширины ПИД-импульса от 0 до 1 секунды.
  myPID.SetMode(AUTOMATIC);             //включаем ПИД-регулирование
  myPID.SetSampleTime(setSampleTime);

    pinMode(8, OUTPUT);      //пока не используемый 8 вывод. Переводим в 1 чтобы не включать реле.
    digitalWrite(8, HIGH);
  pinMode(heater_pin, OUTPUT);
  pinMode(turn_pin, OUTPUT);      // устанавливаем выводы
  digitalWrite(turn_pin, HIGH);
  pinMode(humidifer_pin, OUTPUT);
  pinMode(fan_pin, OUTPUT);
  digitalWrite(fan_pin, HIGH);
  pinMode(alarm_pin, OUTPUT);
  digitalWrite(alarm_pin, HIGH);
  pinMode(button_minus_pin, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
  pinMode(button_plus_pin, INPUT_PULLUP);
  pinMode(button_enter_pin, INPUT_PULLUP);

  sensors.begin();
  sensors.setResolution(12);    // установить разрешение (точность)
  sensors.setWaitForConversion(false);  // отключить ожидание

  //timer.setInterval(3000, getSensors);

  windowStartTime = millis();
  currentTime = millis();  // считываем время, прошедшее с момента запуска программы

  loopTime = currentTime;

  //EEPROM_write(13, 61.0);
}

void loop() {
  // Input = getTemp();
  unsigned long now = millis();

  //timer.run();
  button_read();
  if (!button_enter) {
    delay(del);
    lcd.clear();
    menu();
  }
  if (!button_minus) {
    delay(del);
    lcd.clear();
    //действие по нажатии кнопки "-"
  }
  if (!button_plus) {
    delay(del);
    lcd.clear();
    //действие по нажатии кнопки "+"
  }
  //send-receive with processing if it's time
  if (millis() > serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime += 500;
  }
  getSensors();
  thermostat();
  humidifer();
  turn();
  fan();
  alarm();
  //unsigned long now1 = millis();
  //Serial.println(now1-now);
}


void button_read() {//функция проверки нажатия кнопки
  button_minus = digitalRead(button_minus_pin); //запоминаем значение кнопки
  button_plus = digitalRead(button_plus_pin); //запоминаем значение кнопки
  button_enter = digitalRead(button_enter_pin); //запоминаем значение кнопки
  if (!button_minus || !button_plus || !button_enter) beeper(50);
}

//меню
void menu() {
  temp_setup();
  hum_setup();
  turn_setup();
  alarm_setup();
  vent_setup();
  //data_time_setup();
}




//устанавливаем температуру в меню
void temp_setup() {
  float tempInk;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(2, 0);
  lcd.print("TEMP.INK SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }
    button_read();
    //EEPROM_write(1, 37.5);
    EEPROM_read(1, tempInk); 
    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(1, tempInk + 0.1);
      if (tempInk > 40) {     //проверяем, если выше 40,
        EEPROM_write(1, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(1, tempInk - 0.1);
      if (tempInk < 30.0) {     //проверяем, если выше 30,
        EEPROM_write(1, 30.0);  //пишем в память 30
      }
      lcd.clear();
    }
    lcd.setCursor(2, 0);
    lcd.print("TEMP.INK = ");
    lcd.print(tempInk, 1);
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}


//устанавливаем влажность
void hum_setup() {
  float set_humidity;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(3, 0);
  lcd.print("HUMIDITY SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    //EEPROM_write(13, 65.0);
    //EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
    EEPROM_read(13, set_humidity);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(13, set_humidity + 0.5);
      if (set_humidity > 100) {     //проверяем, если выше 40,
        EEPROM_write(13, 100.0);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(13, set_humidity - 0.5);
      if (set_humidity < 20) {     //проверяем, если выше 40,
        EEPROM_write(13, 20.0);  //пишем в память 40
      }
      lcd.clear();
    }

    lcd.setCursor(2, 0);
    lcd.print("Humidity = ");
    lcd.print(set_humidity, 1);
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }

}

//устанавливаем поворот
void turn_setup() {
  int turnPeriod;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("TURN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(11, turnPeriod);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(11, turnPeriod + 1);
      if (turnPeriod > 13) {     //проверяем, если выше 40,
        EEPROM_write(11, 13);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(11, turnPeriod - 1);
      if (turnPeriod < 0) {     //проверяем, если выше 40,
        EEPROM_write(11, 0);  //пишем в память 40
      }
      lcd.clear();
    }
    EEPROM_read(11, turnPeriod);
    lcd.setCursor(2, 0);
    lcd.print("PERIOD = ");
    lcd.print(turnPeriod);
    lcd.print(" Hour");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}


//устанавливаем сигнализацию
void alarm_setup() {
  int alarmTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("ALARM SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
    break;
    }

    button_read();
    EEPROM_read(5, alarmTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(5, alarmTemp + 1);
      if (alarmTemp > 10) {     //проверяем, если выше 40,
        EEPROM_write(5, 10);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(5, alarmTemp - 1);
      if (alarmTemp < 1) {     //проверяем, если выше 40,
        EEPROM_write(5, 1);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(2, 0);
    lcd.print("T.Alarm + - ");
    lcd.print(alarmTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }

}

//устанавливаем вентиляцию
void vent_setup() {
  int fanTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("FAN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(9, fanTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(9, fanTemp + 1);
      if (fanTemp > 40) {     //проверяем, если выше 40,
        EEPROM_write(9, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(9, fanTemp - 1);
      if (fanTemp < 20) {     //проверяем, если выше 40,
        EEPROM_write(9, 20);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(4, 0);
    lcd.print("T.Fan = ");
    lcd.print(fanTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}




void getSensors(){

  unsigned long curMillis = millis();          // Get current time
  sensors.requestTemperatures();

  // Demonstrate non-blocking calls
  if (curMillis - trhMillis >= TRHSTEP) {      // Time for new measurements?
    measActive = true;
    measType = TEMP;
    sht.meas(TEMP, &rawData, NONBLOCK);        // Start temp measurement
    trhMillis = curMillis;
  }
  if (measActive && sht.measRdy()) {           // Check measurement status
    if (measType == TEMP) {                    // Process temp or humi?
      measType = HUMI;
      temp2Ink = sht.calcTemp(rawData);     // Convert raw sensor data
      temp1Ink = sensors.getTempCByIndex(0);
      sht.meas(HUMI, &rawData, NONBLOCK);      // Start humi measurement
    } 
    else {
      measActive = false;
      humidity = sht.calcHumi(rawData, temp2Ink); // Convert raw sensor data
      dewpoint = sht.calcDewpoint(humidity, temp2Ink);

    }
  }
 // logData();
}



//void logData() {
//  Serial.print("temp1Ink = ");   
//  Serial.print(temp1Ink);
//  Serial.print("temp2Ink = ");   
//  Serial.print(temp2Ink);
//  Serial.print(" C, Humidity = ");  
//  Serial.print(humidity);
//  Serial.print(" %, Dewpoint = ");  
//  Serial.print(dewpoint);
//  Serial.println(" C");
//}



//используем терморегулятор
void thermostat() {
  now = millis();
  float tempPoint;
  EEPROM_read_mem(1, &tempPoint, sizeof(tempPoint));
  EEPROM_read(1, tempPoint);
  Setpoint = tempPoint;
  myPID.Compute();


  if (now - windowStartTime > WindowSize) { //время для перещелкивания периода окна
    windowStartTime = windowStartTime + WindowSize;
    voltmeter();                        //запускаем функцию измерения напряжения
    Input = temp1Ink;
    lcd.setCursor(1, 0);                 // устанавливаем курсор в 0-ом столбце, 0 строка (начинается с 0)
    lcd.print("T1= ");
    lcd.print(temp1Ink, 1);              // печать температуры на дисплей
    lcd.print((char)223);
    lcd.print("  (");
    lcd.print(Setpoint, 1);
    lcd.print(")");
    lcd.setCursor(1, 1);
    lcd.print("T2= ");
    lcd.print((temp2Ink - 0.4), 1);            // печать температуры на дисплей
    lcd.print((char)223);
    lcd.setCursor(11, 1);
    lcd.print(" (*");
    lcd.print(temp1Ink - temp2Ink, 1);
    lcd.print(")");
    //lcd.print((char)223);
    //lcd.print("  (");
    //lcd.print(Setpoint, 1);
    //lcd.print(")");

  }
  if (Output > (now - windowStartTime)) digitalWrite(heater_pin, HIGH);
  else digitalWrite(heater_pin, LOW);

}

//управляем влажностью
void humidifer() {
  //float humidity;
  float set_humidity;
  unsigned long humMillis = 0;
  unsigned long curMillis = millis();
  //  if (curMillis - humMillis >= humStep) {  // Time to toggle the LED state?
  //    humMillis = curMillis;
  //    //humidity = sht1x.readHumidity();
  //  }
  EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
  EEPROM_read(13, set_humidity);
  lcd.setCursor(1, 2);                 // устанавливаем курсор в 0-ом столбце, 1 строка (начинается с 0)
  lcd.print(" H= ");
  lcd.print(humidity, 1);           // печать влажности на дисплей
  lcd.print("%");
  lcd.print("  (");
  lcd.print(set_humidity, 1);
  lcd.print(")");
  if (set_humidity > humidity) digitalWrite(humidifer_pin, HIGH); //сравниваем измеренную влажность с заданной
  else digitalWrite(humidifer_pin, LOW); //если влажность низкая, включаем увлажнитель

}


//управляем поворотом
void turn() {
  int turnPeriod;                //период поворота лотков в часах
  int turnCommand;
  EEPROM_read(11, turnPeriod);
  lcd.setCursor(12, 3);
  lcd.print("P");
  lcd.print(turnPeriod);
  if (turnPeriod == 0) return;           //если нулевой период поворота, то не поворачиваем яйца.
  if (turnPeriod < 13) turnCommand = turnPeriod;
  else if (turnPeriod > 12 && turnFlag == 0) { //если произошел поворот (сброшен флаг) и значение в памяти 13, то
    turnCommand = random(1, 6);        //берем случайное значение часов 1-6
    turnFlag = 1;                     //защелкиваем флаг вычисления случайных значений до следующего поворота
  }
  currentTime = millis();
  if (currentTime >= (loopTime + turnCommand * 3600000)) {  // 3600000 сравниваем текущий таймер с переменной loopTime + период поворота в часах.
    digitalWrite(turn_pin, !digitalRead(turn_pin));       // включаем/выключаем реле поворота
    loopTime = currentTime;    // в loopTime записываем новое значение
    turnFlag = 0;    //сбрасываем флаг поворота
  }
}

//управляем авариями
void alarm() {
  float tempInk = sensors.getTempCByIndex(0);
  int alarmTemp;
  float setTemp;
  EEPROM_read(5, alarmTemp);
  EEPROM_read_mem(1, &setTemp, sizeof(setTemp));
  EEPROM_read(1, setTemp);
  lcd.setCursor(0, 3);
  lcd.print("A");
  lcd.print(setTemp + alarmTemp, 1);
  if (millis() > 1800000) {
    if (tempInk > (setTemp + alarmTemp) || tempInk < (setTemp - alarmTemp)) {
      beeper(10);
      digitalWrite(alarm_pin, LOW); //если измеренная температура выше заданной на величину аварии
    }
    else digitalWrite(alarm_pin, HIGH); //то включаем аварийный сигнал.
  }
}

void beeper(int duration) {
  tone(beeper_pin, 2000, duration);
}


//управляем вентиляторами
void fan() {
  //float tempInk = sht1x.readTemperatureC();
  float fanTemp;
  float setTemp;
  EEPROM_read(9, fanTemp);
  EEPROM_read_mem(1, &setTemp, sizeof(setTemp));
  EEPROM_read(1, setTemp);
  lcd.setCursor(6, 3);
  lcd.print("F");
  lcd.print(setTemp + fanTemp, 1);
  if (temp1Ink > (setTemp + fanTemp)) digitalWrite(fan_pin, LOW); //если измеренная температура выше заданной на величину аварии
  else digitalWrite(fan_pin, HIGH); //то включаем аварийный сигнал.

}

void voltmeter(){
  float outputValue = 0;     
  outputValue = float(analogRead(voltmeter_pin))/63,9;
  //Serial.print("Voltage = " );                      
  //Serial.println(outputValue); 
  lcd.setCursor(15, 3);
  lcd.print("V");
  lcd.print(outputValue, 1);
}

/********************************************
 * Serial Communication functions / helpers
 ********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param

void SerialReceive()
{

  // read the bytes sent from Processing
  int index = 0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while (Serial.available() && index < 26)
  {
    if (index == 0) Auto_Man = Serial.read();
    else if (index == 1) Direct_Reverse = Serial.read();
    else foo.asBytes[index - 2] = Serial.read();
    index++;
  }

  // if the information we got was in the correct format,
  // read it into the system
  if (index == 26  && (Auto_Man == 0 || Auto_Man == 1) && (Direct_Reverse == 0 || Direct_Reverse == 1))
  {
    Setpoint = double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the
    //   value of "Input"  in most cases (as
    //   in this one) this is not needed.
    if (Auto_Man == 0)                    // * only change the output if we are in
    { //   manual mode.  otherwise we'll get an
      Output = double(foo.asFloat[2]);    //   output blip, then the controller will
    }                                     //   overwrite.

    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //

    if (Auto_Man == 0) myPID.SetMode(MANUAL); // * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //

    if (Direct_Reverse == 0) myPID.SetControllerDirection(DIRECT); // * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}

// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings.  much easier than getting
// floats from processing to here no?
void SerialSend()
{
  Serial.print("PID ");
  Serial.print(Setpoint);
  Serial.print(" ");
  Serial.print(Input);
  Serial.print(" ");
  Serial.print(Output);
  Serial.print(" ");
  Serial.print(myPID.GetKp());
  Serial.print(" ");
  Serial.print(myPID.GetKi());
  Serial.print(" ");
  Serial.print(myPID.GetKd());
  Serial.print(" ");
  if (myPID.GetMode() == AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");
  Serial.print(" ");
  if (myPID.GetDirection() == DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}






 

 

 

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Плата центрального модуля и плата датчика влажности с обвязкой.

Схему не рисовал, вроде и так понятно все и подписано. Дисплей, кнопки, блок реле подключаются к штыревым разъемам на мамке. Вместо двухамперных твердотельных релюшек на борту можно использовать внешние твердотельные реле или симисторы с опторазвязкой. Для этого сигналы управления выведены на отдельный разъем. Вместо стандатртного блока реле можно подвесить свой блок управления нагрузкой, например на силовых MOSFET ключах для 12В нагрузки. Термометр и гигрометр подключаются в телефонные RJ11 розетки. При подключении их не в свое гнездо ничего не сгорит т.к. контакты питания симметричны. 

Вот так выглядит прототип устройства в корпусе из электрокоробки. Только там еще принт с надписями сверху приклеен, позже сфотографирую.

 

Ну вот как-то так. Не знаю что еще писать. Спрашивайте если интересно. Про инкубацию знаю много, сам птицевод с xx-летним стажем. Паять тоже умею, с Ардуиной пока учусь. Вот.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Да, саоме важное забыл сказать. Тут не Даллас не Сенсерион не дают задержки в библиотеках при измерении и скетч за 50 миллисекунд весь пробегает. Это важно для правильной работы ПИД. Пришлось замысловатую либу раскопать для SHT10, зато она умеет точку росы (!) вычислять. Не знаю только для чего мне в инкубаторе точка росы? 

Araris
Araris аватар
Offline
Зарегистрирован: 09.11.2012

Качественно реализовано. Респект автору.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Спасибо. Я уж думал никто не зайдет сюда, всем веселее про загрузчик и программатор меня пинать.

Вот визуализатор - стандартный PID-Fronend на процессинге из ПИД библиотеки. Зеленая линия - заданная температура, красная - измеренная. Синяя - пропорциональный коэффициент. Обратите внимание как он бодро реагирует на изменения температуры (я пальцами термометр трогал).

 

А вот так пока внутренности выглядят. 

 

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Теперь вот так оно выглядит. В нижней строчке Т. аварии, Т. включения вентилятора, периодичность поворота и вольтметр. Во второй строке в скобочках разница в температуре между термометрами.

http://www.youtube.com/watch?v=ZGWFOssu-Bs&feature=youtu.be

Видео на котором почти ничего не понятно. Для интриги :)))

Код подправил немного:

#include <PID_v1.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM2.h>
#include <DallasTemperature.h>
#include <OneWire.h>
//#include <SimpleTimer.h>
#include <Sensirion.h>



int del = 80;                     // переменная ожидания между выборами меню
unsigned int interval = 100;      // интервал сколько будет длиться цикл while, после чего перейдёт к следующему меню.(кол-во итераций)

#define heater_pin 13                  // нагреватель
#define humidifer_pin 12               // увлажнитель
#define fan_pin 11                    // вентилятор
#define alarm_pin 14                   // пин аварии
#define beeper_pin 9                //пищалка по аварии
#define dataPin 5                      //SHT10
#define clockPin 6                     //SHT10 
#define turn_pin 10                // управление поворотом
#define button_minus_pin 2            //пин кнопки "минус"
#define button_plus_pin 3             //пин кнопки "плюс"
#define button_enter_pin 4            //пин кнопки "enter"
#define DS18B20_Pin 7                 //пин термометра
#define setSampleTime 1000            //время цикла ПИД
#define voltmeter_pin 1               //вход А1 через делитель (22к/10к) подключен к питанию модуля. Измеряет до 16В.

boolean button_minus;
boolean button_plus;
boolean button_enter;
boolean turnFlag = 0;              // флаг поворота для случайного периода
float humidity;                  // Влажность
float temp1Ink;                  // Температура DS18B20  
float temp2Ink;                  // Температура SHT10
float dewpoint;                  // Точка росы

unsigned int rawData;
unsigned long currentTime;            // задаем переменные для тайминга поворота
unsigned long loopTime;
unsigned long serialTime; //this will help us know when to talk with processing
unsigned long now;
unsigned long trhMillis = 0;    // период опроса датчиков
byte measActive = false;
byte measType = TEMP;
const unsigned long TRHSTEP   = 3000UL;  // Sensor query period

LiquidCrystal_I2C lcd(0x27, 20, 4);  // инициализация библиотеки дисплея
//SHT1x sht1x(dataPin, clockPin);
OneWire oneWire(DS18B20_Pin);
DallasTemperature sensors(&oneWire);
Sensirion sht = Sensirion(dataPin, clockPin);

double Setpoint, Input, Output;            //объявляем переменные для ПИД
PID myPID(&Input, &Output, &Setpoint, 10, 4, 3, DIRECT); //Инициализируем ПИД-библиотеку и коэффициенты
int WindowSize = setSampleTime;                  // ширина окна терморегулятора 1 секунда.
unsigned long windowStartTime;

//SimpleTimer timer;

/*  EEPROM1 -   tempInk
 EEPROM5 (13) -   set_humidity
 EEPROM7 -   alarmTemp
 EEPROM9 -   fanTemp
 EEPROM11 -  turnPeriod            */




void setup() {
  Serial.begin(9600);

  lcd.init();          // Старт дисплея
  lcd.backlight();     // Включаем подсветку дисплея
  windowStartTime = millis();
  byte stat;
  byte error = 0;
  delay(15);

  myPID.SetOutputLimits(0, WindowSize); //задаем лимиты ширины ПИД-импульса от 0 до 1 секунды.
  myPID.SetMode(AUTOMATIC);             //включаем ПИД-регулирование
  myPID.SetSampleTime(setSampleTime);

    pinMode(8, OUTPUT);      //пока не используемый 8 вывод. Переводим в 1 чтобы не включать реле.
    digitalWrite(8, HIGH);
  pinMode(heater_pin, OUTPUT);
  pinMode(turn_pin, OUTPUT);      // устанавливаем выводы
  digitalWrite(turn_pin, HIGH);
  pinMode(humidifer_pin, OUTPUT);
  pinMode(fan_pin, OUTPUT);
  digitalWrite(fan_pin, HIGH);
  pinMode(alarm_pin, OUTPUT);
  digitalWrite(alarm_pin, HIGH);
  pinMode(button_minus_pin, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
  pinMode(button_plus_pin, INPUT_PULLUP);
  pinMode(button_enter_pin, INPUT_PULLUP);

  sensors.begin();
  sensors.setResolution(12);    // установить разрешение (точность)
  sensors.setWaitForConversion(false);  // отключить ожидание

  //timer.setInterval(3000, getSensors);

  windowStartTime = millis();
  currentTime = millis();  // считываем время, прошедшее с момента запуска программы

  loopTime = currentTime;

  //EEPROM_write(13, 61.0);
}

void loop() {
  // Input = getTemp();
  unsigned long now = millis();

  //timer.run();
  button_read();
  if (!button_enter) {
    delay(del);
    lcd.clear();
    menu();
  }
  if (!button_minus) {
    delay(del);
    lcd.clear();
    //действие по нажатии кнопки "-"
  }
  if (!button_plus) {
    delay(del);
    lcd.clear();
    //действие по нажатии кнопки "+"
  }
  //send-receive with processing if it's time
  if (millis() > serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime += 500;
  }
  getSensors();
  thermostat();
  humidifer();
  turn();
  fan();
  alarm();
  //unsigned long now1 = millis();
  //Serial.println(now1-now);
}


void button_read() {//функция проверки нажатия кнопки
  button_minus = digitalRead(button_minus_pin); //запоминаем значение кнопки
  button_plus = digitalRead(button_plus_pin); //запоминаем значение кнопки
  button_enter = digitalRead(button_enter_pin); //запоминаем значение кнопки
  if (!button_minus || !button_plus || !button_enter) beeper(50);
}

//меню
void menu() {
  temp_setup();
  hum_setup();
  turn_setup();
  alarm_setup();
  vent_setup();
  //data_time_setup();
}




//устанавливаем температуру в меню
void temp_setup() {
  float tempInk;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(2, 0);
  lcd.print("TEMP.INK SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }
    button_read();
    //EEPROM_write(1, 37.5);
    EEPROM_read(1, tempInk); 
    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(1, tempInk + 0.1);
      if (tempInk > 40) {     //проверяем, если выше 40,
        EEPROM_write(1, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(1, tempInk - 0.1);
      if (tempInk < 30.0) {     //проверяем, если выше 30,
        EEPROM_write(1, 30.0);  //пишем в память 30
      }
      lcd.clear();
    }
    lcd.setCursor(2, 0);
    lcd.print("TEMP.INK = ");
    lcd.print(tempInk, 1);
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}


//устанавливаем влажность
void hum_setup() {
  float set_humidity;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(3, 0);
  lcd.print("HUMIDITY SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    //EEPROM_write(13, 65.0);
    //EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
    EEPROM_read(13, set_humidity);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(13, set_humidity + 0.5);
      if (set_humidity > 100) {     //проверяем, если выше 40,
        EEPROM_write(13, 100.0);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(13, set_humidity - 0.5);
      if (set_humidity < 20) {     //проверяем, если выше 40,
        EEPROM_write(13, 20.0);  //пишем в память 40
      }
      lcd.clear();
    }

    lcd.setCursor(2, 0);
    lcd.print("Humidity = ");
    lcd.print(set_humidity, 1);
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }

}

//устанавливаем поворот
void turn_setup() {
  int turnPeriod;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("TURN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(11, turnPeriod);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(11, turnPeriod + 1);
      if (turnPeriod > 13) {     //проверяем, если выше 40,
        EEPROM_write(11, 13);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(11, turnPeriod - 1);
      if (turnPeriod < 0) {     //проверяем, если выше 40,
        EEPROM_write(11, 0);  //пишем в память 40
      }
      lcd.clear();
    }
    EEPROM_read(11, turnPeriod);
    lcd.setCursor(2, 0);
    lcd.print("PERIOD = ");
    lcd.print(turnPeriod);
    lcd.print(" Hour");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}


//устанавливаем сигнализацию
void alarm_setup() {
  int alarmTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("ALARM SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
    break;
    }

    button_read();
    EEPROM_read(5, alarmTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(5, alarmTemp + 1);
      if (alarmTemp > 10) {     //проверяем, если выше 40,
        EEPROM_write(5, 10);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(5, alarmTemp - 1);
      if (alarmTemp < 1) {     //проверяем, если выше 40,
        EEPROM_write(5, 1);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(2, 0);
    lcd.print("T.Alarm + - ");
    lcd.print(alarmTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }

}

//устанавливаем вентиляцию
void vent_setup() {
  int fanTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("FAN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(9, fanTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(9, fanTemp + 1);
      if (fanTemp > 40) {     //проверяем, если выше 40,
        EEPROM_write(9, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(9, fanTemp - 1);
      if (fanTemp < 20) {     //проверяем, если выше 40,
        EEPROM_write(9, 20);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(4, 0);
    lcd.print("T.Fan = ");
    lcd.print(fanTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}




void getSensors(){

  unsigned long curMillis = millis();          // Получаем текущее время работы
  sensors.requestTemperatures();

  // Demonstrate non-blocking calls
  if (curMillis - trhMillis >= TRHSTEP) {      // время для нового измерения?
    measActive = true;
    measType = TEMP;
    sht.meas(TEMP, &rawData, NONBLOCK);        // измеряем температуру.
    trhMillis = curMillis;
  }
  if (measActive && sht.measRdy()) {           // проверяем статус измерения
    if (measType == TEMP) {                    // обрабатываем температуру или влажность?
      measType = HUMI;
      temp2Ink = sht.calcTemp(rawData);     // Конвертируем сырые данные с сенсора
      temp1Ink = sensors.getTempCByIndex(0);
      sht.meas(HUMI, &rawData, NONBLOCK);      // измеряем влажность
    } 
    else {
      measActive = false;
      humidity = sht.calcHumi(rawData, temp2Ink); // конвертируем данные с сенсора
      dewpoint = sht.calcDewpoint(humidity, temp2Ink);

    }
  }
 // logData();
}



//void logData() {
//  Serial.print("temp1Ink = ");   
//  Serial.print(temp1Ink);
//  Serial.print("temp2Ink = ");   
//  Serial.print(temp2Ink);
//  Serial.print(" C, Humidity = ");  
//  Serial.print(humidity);
//  Serial.print(" %, Dewpoint = ");  
//  Serial.print(dewpoint);
//  Serial.println(" C");
//}



//используем терморегулятор
void thermostat() {
  now = millis();
  float tempPoint;
  EEPROM_read_mem(1, &tempPoint, sizeof(tempPoint));
  EEPROM_read(1, tempPoint);
  Setpoint = tempPoint;
  myPID.Compute();


  if (now - windowStartTime > WindowSize) { //время для перещелкивания периода окна
    windowStartTime = windowStartTime + WindowSize;
    voltmeter();                        //запускаем функцию измерения напряжения
    Input = temp1Ink;
    lcd.setCursor(1, 0);                 // устанавливаем курсор в 0-ом столбце, 0 строка (начинается с 0)
    lcd.print("T1= ");
    lcd.print(temp1Ink, 1);              // печать температуры на дисплей
    lcd.print((char)223);
    lcd.print("  (");
    lcd.print(Setpoint, 1);
    lcd.print(")");
    lcd.setCursor(1, 1);
    lcd.print("T2= ");
    lcd.print((temp2Ink - 0.4), 1);            // печать температуры на дисплей
    lcd.print((char)223);
    lcd.setCursor(11, 1);
    lcd.print(" (*");
    lcd.print(temp1Ink - temp2Ink, 1);
    lcd.print(")");
    //lcd.print((char)223);
    //lcd.print("  (");
    //lcd.print(Setpoint, 1);
    //lcd.print(")");

  }
  if (Output > (now - windowStartTime)) digitalWrite(heater_pin, HIGH);
  else digitalWrite(heater_pin, LOW);

}

//управляем влажностью
void humidifer() {
  //float humidity;
  float set_humidity;
  unsigned long humMillis = 0;
  unsigned long curMillis = millis();
  //  if (curMillis - humMillis >= humStep) {  
  //    humMillis = curMillis;
  //    //humidity = sht1x.readHumidity();
  //  }
  EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
  EEPROM_read(13, set_humidity);
  lcd.setCursor(1, 2);                 // устанавливаем курсор в 0-ом столбце, 1 строка (начинается с 0)
  lcd.print(" H= ");
  lcd.print(humidity, 1);           // печать влажности на дисплей
  lcd.print("%");
  lcd.print("  (");
  lcd.print(set_humidity, 1);
  lcd.print(")");
  if (set_humidity > humidity) digitalWrite(humidifer_pin, HIGH); //сравниваем измеренную влажность с заданной
  else digitalWrite(humidifer_pin, LOW); //если влажность низкая, включаем увлажнитель

}


//управляем поворотом
void turn() {
  int turnPeriod;                //период поворота лотков в часах
  int turnCommand;
  EEPROM_read(11, turnPeriod);
  lcd.setCursor(10, 3);
  lcd.print("P");
  lcd.print(turnPeriod);
  if (turnPeriod == 0) return;           //если нулевой период поворота, то не поворачиваем яйца.
  if (turnPeriod < 13) turnCommand = turnPeriod;
  else if (turnPeriod > 12 && turnFlag == 0) { //если произошел поворот (сброшен флаг) и значение в памяти 13, то
    turnCommand = random(1, 6);        //берем случайное значение часов 1-6
    turnFlag = 1;                     //защелкиваем флаг вычисления случайных значений до следующего поворота
  }
  currentTime = millis();
  if (currentTime >= (loopTime + turnCommand * 3600000)) {  // 3600000 сравниваем текущий таймер с переменной loopTime + период поворота в часах.
    digitalWrite(turn_pin, !digitalRead(turn_pin));       // включаем/выключаем реле поворота
    loopTime = currentTime;    // в loopTime записываем новое значение
    turnFlag = 0;    //сбрасываем флаг поворота
  }
}

//управляем авариями
void alarm() {
  float tempInk = sensors.getTempCByIndex(0);
  int alarmTemp;
  float setTemp;
  EEPROM_read(5, alarmTemp);
  EEPROM_read_mem(1, &setTemp, sizeof(setTemp));
  EEPROM_read(1, setTemp);
  lcd.setCursor(0, 3);
  lcd.print("A");
  lcd.print(setTemp + alarmTemp, 1);
  if (millis() > 1800000) {
    if (tempInk > (setTemp + alarmTemp) || tempInk < (setTemp - alarmTemp)) {
      beeper(10);
      digitalWrite(alarm_pin, LOW); //если измеренная температура выше заданной на величину аварии
    }
    else digitalWrite(alarm_pin, HIGH); //то включаем аварийный сигнал.
  }
}

void beeper(int duration) {
  tone(beeper_pin, 2000, duration);
}


//управляем вентиляторами
void fan() {
  //float tempInk = sht1x.readTemperatureC();
  int fanTemp;
  EEPROM_read(9, fanTemp);
  lcd.setCursor(6, 3);
  lcd.print("F");
  lcd.print(fanTemp);
  if (temp1Ink > fanTemp) digitalWrite(fan_pin, LOW); //если измеренная температура выше заданной на величину аварии
  else digitalWrite(fan_pin, HIGH); //то включаем аварийный сигнал.

}

void voltmeter(){
  float outputValue = 0;     
  outputValue = float(analogRead(voltmeter_pin))/63,9;
  //Serial.print("Voltage = " );                      
  //Serial.println(outputValue); 
  lcd.setCursor(14, 3);
  lcd.print("V");
  lcd.print(outputValue, 1);
}

/********************************************
 * Serial Communication functions / helpers
 ********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param

void SerialReceive()
{

  // read the bytes sent from Processing
  int index = 0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while (Serial.available() && index < 26)
  {
    if (index == 0) Auto_Man = Serial.read();
    else if (index == 1) Direct_Reverse = Serial.read();
    else foo.asBytes[index - 2] = Serial.read();
    index++;
  }

  // if the information we got was in the correct format,
  // read it into the system
  if (index == 26  && (Auto_Man == 0 || Auto_Man == 1) && (Direct_Reverse == 0 || Direct_Reverse == 1))
  {
    Setpoint = double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the
    //   value of "Input"  in most cases (as
    //   in this one) this is not needed.
    if (Auto_Man == 0)                    // * only change the output if we are in
    { //   manual mode.  otherwise we'll get an
      Output = double(foo.asFloat[2]);    //   output blip, then the controller will
    }                                     //   overwrite.

    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //

    if (Auto_Man == 0) myPID.SetMode(MANUAL); // * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //

    if (Direct_Reverse == 0) myPID.SetControllerDirection(DIRECT); // * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}

// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings.  much easier than getting
// floats from processing to here no?
void SerialSend()
{
  Serial.print("PID ");
  Serial.print(Setpoint);
  Serial.print(" ");
  Serial.print(Input);
  Serial.print(" ");
  Serial.print(Output);
  Serial.print(" ");
  Serial.print(myPID.GetKp());
  Serial.print(" ");
  Serial.print(myPID.GetKi());
  Serial.print(" ");
  Serial.print(myPID.GetKd());
  Serial.print(" ");
  if (myPID.GetMode() == AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");
  Serial.print(" ");
  if (myPID.GetDirection() == DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}






 

tamerlan37
Offline
Зарегистрирован: 22.10.2013

Здравствуйте в начале указано что заказаны платы в Китае ,тоесть это будет шилд своего рода?Если да то какая цена?Сделал что-то похожее но пока до запуска руки не дошли.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Добрый. Ну вон же плата, чуть выше картинкой опубликована. Выглядеть будет как и эта самоделка, только заводского исполнения. Образец можно в моей прошлой теме про брудер посмотреть. http://arduino.ru/forum/proekty/bruder-avtomat
шилом на верное не совсем правильно называть плату для Про Мини.

lasarus
Offline
Зарегистрирован: 21.02.2015

Здравствуйте! Не подскажете какой датчик влажности используете, делаю себе подобный инкубатор но для паучьих коконов, там все тоже только параметры влажности и температуры отличаются, количество поворотов 4-6 в сутки... мне и код написали , но все равно куча проблем... датчик влажности завис на 99% иногда после отключения света не может проинициализироватся и вообще виснет... 

Можно ли ваш код както подпилить под мое железо?

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Здравствуйте. Датчик самый дешевый из хороших - SHT10. Мой код подпилить можно, не вижу проблем.

Вод код с автоматической программой. Периодичность поворота задается в меню. 


#include <PID_v1.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM2.h>
#include "RTClib.h"
#include <DallasTemperature.h>
#include <OneWire.h>
#include <Sensirion.h>
//#include <avr/wdt.h>

int del = 80;                     // переменная ожидания между выборами меню
unsigned int interval = 100;      // интервал сколько будет длиться цикл while, после чего перейдёт к следующему меню.(кол-во итераций)

//#define EXT_HEATING                  // ИСПОЛЬЗУЕМ ДОП.НАГРЕВАТЕЛЬ ВМЕСТО УВЛАЖНИТЕЛЯ. Если нужен увлажнитель, просто закомментируйте эту строку.
#define heater_pin 13                  // нагреватель
#define humidifer_pin 12               // увлажнитель
#define fan_pin 11                     // вентилятор
#define alarm_pin 14                   // пин аварии
#define beeper_pin 9                   //пищалка по аварии
#define dataPin 5                      //SHT10
#define clockPin 6                     //SHT10 
#define turn_pin 10                    // управление поворотом
#define extend_heater_pin 8	       // дополнительный нагреватель
#define button_minus_pin 2            //пин кнопки "минус"
#define button_plus_pin 3             //пин кнопки "плюс"
#define button_enter_pin 4            //пин кнопки "enter"
#define DS18B20_Pin 7                 //пин термометра
#define setSampleTime 1000            //время цикла ПИД
#define voltmeter_pin 1               //вход А1 через делитель (22к/10к) подключен к питанию модуля. Измеряет до 16В.
#define T_correction -0.4             // коррекция температуры SHT10

boolean button_minus;                // статус нажатия кнопок
boolean button_plus;
boolean button_enter;
boolean turnFlag = 0;              // флаг поворота для случайного периода
float humidity;                    // Влажность
float temp1Ink;                    // Температура DS18B20
float temp2Ink;                    // Температура SHT10
//float dewpoint;                    // Точка росы

unsigned int rawData;
unsigned long currentTime;            // задаем переменные для тайминга поворота
unsigned long loopTime;
unsigned long serialTime; //this will help us know when to talk with processing
unsigned long now;
unsigned long trhMillis = 0;             // период опроса датчиков
byte measActive = false;
byte measType = TEMP;
const unsigned long TRHSTEP   = 300UL;  // Период опроса датчиков
unsigned int currentDay;                // текущий день в юникс-формате

LiquidCrystal_I2C lcd(0x27, 20, 4);  // инициализация библиотеки дисплея
//SHT1x sht1x(dataPin, clockPin);
OneWire oneWire(DS18B20_Pin);
DallasTemperature sensors(&oneWire);
Sensirion sht = Sensirion(dataPin, clockPin);

double Setpoint, Input, Output;            //объявляем переменные для ПИД
PID myPID(&Input, &Output, &Setpoint, 5, 4, 3, DIRECT); //Инициализируем ПИД-библиотеку и коэффициенты
int WindowSize = setSampleTime;                  // ширина окна терморегулятора 1 секунда.
unsigned long windowStartTime;
unsigned long alarmDelay;
RTC_DS1307 RTC;

//SimpleTimer timer;

/*
 EEPROM1 -   startDayUnixtime
 EEPROM5 -
 EEPROM7 -  alarmTemp
 EEPROM9 -  fanTemp
 EEPROM11 -  turnPeriod
 EEPROM13 - Humidity
 EEPROM15 - isRunning
 */


void setup() {
  Serial.begin(9600);

  RTC.begin();         // Старт часов
  lcd.init();          // Старт дисплея
  Wire.begin();        // старт I2C
  lcd.backlight();     // Включаем подсветку дисплея
  windowStartTime = millis();
  byte stat;
  byte error = 0;
  delay(15);
  // wdt_enable (WDTO_8S); //взводим сторожевой таймер на 8 секунд.

  myPID.SetOutputLimits(0, WindowSize); //задаем лимиты ширины ПИД-импульса от 0 до 1 секунды.
  myPID.SetMode(AUTOMATIC);             //включаем ПИД-регулирование
  myPID.SetSampleTime(setSampleTime);

  pinMode(extend_heater_pin, OUTPUT);      //пин дополнительного нагревателя. Переводим в 1 чтобы не включать реле.
  digitalWrite(extend_heater_pin, HIGH);
  pinMode(heater_pin, OUTPUT);
  pinMode(turn_pin, OUTPUT);      // устанавливаем выводы
  digitalWrite(turn_pin, HIGH);
  pinMode(humidifer_pin, OUTPUT);
  pinMode(fan_pin, OUTPUT);
  digitalWrite(fan_pin, HIGH);
  pinMode(alarm_pin, OUTPUT);
  digitalWrite(alarm_pin, HIGH);
  pinMode(button_minus_pin, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
  pinMode(button_plus_pin, INPUT_PULLUP);
  pinMode(button_enter_pin, INPUT_PULLUP);
  alarmDelay = millis();

  sensors.begin();
  sensors.setResolution(12);    // установить разрешение (точность)
  sensors.setWaitForConversion(false);  // отключить ожидание

  //RTC.adjust(DateTime(__DATE__, __TIME__));   //раскоментируйте для установки системмных даты и времени
  
}


void loop() {
  // Input = getTemp();
  // unsigned long now1 = millis();
  unsigned int startDayUnixtime;          // хранящийся в памяти день старта программы в юникс-формате
  float needTemp = 37.6;                  // нужная для текущего дня температура инкубации (по умолчанию)
  float needHum = 60.5;                   // ---- влажность
  int ventTime = 2;                       // длительность проветривания
  boolean needTurn = false;               // нужен ли поворот яиц?
  unsigned int currentTime_day;           //текущий день в юникс-формате (сколько дней минуло с 1 января 1970)

  DateTime now = RTC.now();
  currentTime_day = (now.unixtime() / 86400L);
  lcd.setCursor(0, 3);
  if (now.hour() < 10) lcd.print(" ");
  lcd.print(now.hour(), DEC);
  lcd.print(":");
  if (now.minute() < 10)lcd.print(0);
  lcd.print(now.minute(), DEC);
  lcd.print(" ");
  if (now.day() < 10)lcd.print(" ");
  lcd.print(now.day(), DEC);
  lcd.print(".");
  if (now.month() < 10)lcd.print(0);
  lcd.print(now.month(), DEC);
  lcd.print(".");
  lcd.print(now.year()-2000, DEC);

  //timer.run();
  button_read();
  if (!button_enter) {
    delay(del);
    lcd.clear();
    menu();
  }
  if (!button_minus) {
    delay(del);
    lcd.clear();
    alarmDelay = millis();    // задержка аварии по нажатии кнопки Минус
  }
  if (!button_plus) {
    delay(del);
    lcd.clear();
    digitalWrite(turn_pin, !digitalRead(turn_pin));       // включаем/выключаем реле поворота по кнопке Плюс
  }
  //send-receive with processing if it's time
  if (millis() > serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime += 500;
  }
  EEPROM_read(1, startDayUnixtime);
  currentDay = (currentTime_day - startDayUnixtime);

  // ЭТА СЕКЦИЯ ХРАНИТ РЕЖИМЫ АВТОМАТИЧЕСКОЙ ИНКУБАЦИИ. ИХ МОЖНО НАСТРАИВАТЬ ПОД СВОИ ХОТЕЛКИ!!!
  //проще всего это сделать, предварительно нарисовав таблицу инкубации для любого типа яиц
  //Настройки идут с конца таблицы к началу т.к. последующие режимы перезаписываются предыдущими
  //после данные по температуре, влажности, длительности вентиляции и необходимости поворота передаются в функции.

  if (currentDay < 18) {  // если день инкубации до 18-го  (если дней больше 18-ти, то сработают назначенные выше параметры по умолчанию)
    needTemp = 37.0;      // то температура 37
    needHum = 85.5;       // влажность 85,5%
    ventTime = 10;        // время вентиляции 10 минут (включается в 7 и 19 часов ежедневно)
    needTurn = false;     // поворот лотков отключен
  }
  if (currentDay < 15) {  // если день инкубации до 15-го, то задаем новые значения тем же переменным.
    needTemp = 37.4;      // если же день инкубации больше 15, но меньше 18, то условие не сработают и
    needHum = 48.0;       // значения останутся прежними. И так до самого начала инкубации.
    ventTime = 10;        // Можно назначать разное количество временных промежутков и наборов параметров для них.
    needTurn = true;
  }
  if (currentDay < 12) {
    needTemp = 37.6;
    needHum = 60.0;
    ventTime = 3;
    needTurn = true;
  }
  if (currentDay < 2) {
    needTemp = 38.2;
    needHum = 60.0;
    ventTime = 0;
    needTurn = true;
  }
  // конец секции задания режима автоматической инкубации.

  getSensors();
  thermostat(needTemp, needHum);    // влажность передаем только для вывода на дисплей )))
  humidifer(needHum);
  turn(needTurn);
  fan(ventTime);
  alarm(needTemp);
  ext_heater(needTemp);
  //outpuPower();
  //  wdt_reset();
  //unsigned long now1 = millis();
  //Serial.println(now1-now);
}


void button_read() {//функция проверки нажатия кнопки
  button_minus = digitalRead(button_minus_pin); //запоминаем значение кнопки
  button_plus = digitalRead(button_plus_pin); //запоминаем значение кнопки
  button_enter = digitalRead(button_enter_pin); //запоминаем значение кнопки
  if (!button_minus || !button_plus || !button_enter) beeper(50);
  //wdt_reset();
}


//меню
void menu() {
  turn_setup();
  alarm_setup();
  vent_setup();
  startInk();
  //data_time_setup();
}

// записываем в память день начала инкубации в юникс-формате
void startInk() {
  unsigned int currentTime_day;
  unsigned int memoryDay;
  DateTime now = RTC.now();
  currentTime_day = (now.unixtime() / 86400L);
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("START INK");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) break;
    button_read();
    EEPROM_read(1, memoryDay);
    //Serial.print(currentTime_day);
    //Serial.print("-");
    //Serial.print(memoryDay);
    //Serial.print("=");
    //Serial.println(currentTime_day-memoryDay);
    
    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(1, currentTime_day);
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(1, memoryDay + 1);
      lcd.clear();
    }
    EEPROM_read(1, memoryDay);
    lcd.setCursor(5, 0);
    lcd.print("DAY = ");
    lcd.print(currentTime_day - memoryDay);
    lcd.setCursor(0, 3);
    lcd.print("START  NEXT  +1");
    delay(del);
  }
}

//устанавливаем поворот
void turn_setup() {
  int turnPeriod;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("TURN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(11, turnPeriod);
    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(11, turnPeriod + 1);
      if (turnPeriod > 13) {     //проверяем, если выше 13,
        EEPROM_write(11, 13);  //пишем в память 13
      }
      lcd.clear();
    }
    if (!button_plus) {
      EEPROM_write(11, turnPeriod - 1);
      if (turnPeriod < 0) {     //проверяем, если ниже 0,
        EEPROM_write(11, 0);  //пишем в память 0
      }
      lcd.clear();
    }
    EEPROM_read(11, turnPeriod);
    lcd.setCursor(2, 0);
    lcd.print("PERIOD = ");
    if (turnPeriod < 13)lcd.print(turnPeriod);
    if (turnPeriod > 12) lcd.print("RND");
    lcd.print(" Hour");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}


//устанавливаем сигнализацию
void alarm_setup() {
  int alarmTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(4, 0);
  lcd.print("ALARM SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }
    button_read();
    EEPROM_read(5, alarmTemp);
    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(5, alarmTemp + 1);
      if (alarmTemp > 10) {     //проверяем, если выше 40,
        EEPROM_write(5, 10);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(5, alarmTemp - 1);
      if (alarmTemp < 1) {     //проверяем, если выше 40,
        EEPROM_write(5, 1);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(2, 0);
    lcd.print("T.Alarm + - ");
    lcd.print(alarmTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }

}

//устанавливаем вентиляцию
void vent_setup() {
  int fanTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(3, 0);
  lcd.print("A.FAN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(9, fanTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      EEPROM_write(9, fanTemp + 1);
      if (fanTemp > 40) {     //проверяем, если выше 40,
        EEPROM_write(9, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      EEPROM_write(9, fanTemp - 1);
      if (fanTemp < 20) {     //проверяем, если выше 40,
        EEPROM_write(9, 20);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(4, 0);
    lcd.print("T.Fan = ");
    lcd.print(fanTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 3);
    lcd.print("minus  NEXT  plus");
    delay(del);
  }
}


void getSensors() {
  unsigned long curMillis = millis();          // Получаем текущее время работы
  if (curMillis - trhMillis >= TRHSTEP) {    // время для нового измерения?
    sensors.requestTemperatures();
    temp1Ink = sensors.getTempCByIndex(0);
  }

  // Demonstrate non-blocking calls
  if (curMillis - trhMillis >= TRHSTEP * 4) {    // время для нового измерения?
    measActive = true;
    measType = TEMP;
    sht.meas(TEMP, &rawData, NONBLOCK);        // измеряем температуру.
    trhMillis = curMillis;
  }
  if (measActive && sht.measRdy()) {           // проверяем статус измерения
    if (measType == TEMP) {                    // обрабатываем температуру или влажность?
      measType = HUMI;
      temp2Ink = sht.calcTemp(rawData);     // Конвертируем сырые данные с сенсора
      temp2Ink = (temp2Ink + (T_correction)); // Корректируем показания текрмометра
      sht.meas(HUMI, &rawData, NONBLOCK);      // измеряем влажность
    }
    else {
      measActive = false;
      humidity = sht.calcHumi(rawData, temp2Ink); // конвертируем данные с сенсора
      //dewpoint = sht.calcDewpoint(humidity, temp2Ink);

    }
  }
}

//используем терморегулятор
void thermostat(float tempPoint, float set_humidity) {
  double now1;
  now1 = millis();
  Setpoint = tempPoint;
  myPID.Compute();

  if (now1 - windowStartTime > WindowSize) { //время для перещелкивания периода окна
    windowStartTime = windowStartTime + WindowSize;
    //voltmeter();                        //запускаем функцию измерения напряжения
    Input = temp1Ink;
    lcd.setCursor(1, 0);                 // устанавливаем курсор в 0-ом столбце, 0 строка (начинается с 0)
    lcd.print("T1= ");
    lcd.print(temp1Ink, 1);              // печать температуры на дисплей
    lcd.print((char)223);
    lcd.print("  (");
    lcd.print(Setpoint, 1);
    lcd.print(")");
    lcd.setCursor(1, 1);
    lcd.print("T2= ");
    lcd.print(temp2Ink, 1);            // печать температуры на дисплей
    lcd.print((char)223);
    lcd.setCursor(11, 1);
    lcd.print(" (*");
    lcd.print(temp1Ink - temp2Ink, 1);
    lcd.print(") ");
    lcd.setCursor(1, 2);                 // устанавливаем курсор в 2-ом столбце, 3 строка (начинается с 0)
    lcd.print(" H= ");
    lcd.print(humidity, 1);           // печать влажности на дисплей
    lcd.print("%");
    lcd.print("  (");
    lcd.print(set_humidity, 1);
    lcd.print(")");
    lcd.setCursor(15, 3);
    lcd.print("D");
    lcd.print(currentDay);        // текущий день инкубации
  }
  if (Output > (now1 - windowStartTime)) digitalWrite(heater_pin, HIGH);
  else digitalWrite(heater_pin, LOW);

}

//управляем влажностью
void humidifer(float set_humidity) {
  //float humidity;
  unsigned long humMillis = 0;
  unsigned long curMillis = millis();
  //  if (curMillis - humMillis >= humStep) {
  //    humMillis = curMillis;
  //    //humidity = sht1x.readHumidity();
  //  }
  if (set_humidity > humidity) digitalWrite(humidifer_pin, HIGH); //сравниваем измеренную влажность с заданной
  else digitalWrite(humidifer_pin, LOW); //если влажность низкая, включаем увлажнитель

}

//управляем поворотом
void turn(boolean needTurn) {
  int turnPeriod;                //период поворота лотков в часах
  int turnCommand;
  EEPROM_read(11, turnPeriod);
  //lcd.setCursor(10, 3);
  //lcd.print("P");
  //lcd.print(turnPeriod);
  if (turnPeriod == 0) return;           //если нулевой период поворота, то не поворачиваем яйца.
  if (turnPeriod < 13) turnCommand = turnPeriod;
  else if (turnPeriod > 12 && turnFlag == 0) { //если произошел поворот (сброшен флаг) и значение в памяти 13, то
    turnCommand = random(1, 6);        //берем случайное значение часов 1-6
    turnFlag = 1;                     //защелкиваем флаг вычисления случайных значений до следующего поворота
  }
  currentTime = millis();
  if (needTurn == true) {
    if (currentTime >= (loopTime + turnCommand * 3600000)) {  // 3600000 сравниваем текущий таймер с переменной loopTime + период поворота в часах.
      digitalWrite(turn_pin, !digitalRead(turn_pin));       // включаем/выключаем реле поворота
      loopTime = currentTime;    // в loopTime записываем новое значение
      turnFlag = 0;    //сбрасываем флаг поворота
    }
  }
}

//управляем авариями
void alarm(float needTemp) {
  float tempInk = sensors.getTempCByIndex(0);
  int alarmTemp;
  EEPROM_read(5, alarmTemp);
  //lcd.setCursor(0, 3);
  //lcd.print("A");
  //lcd.print(needTemp + alarmTemp, 1);
  if ((millis() - alarmDelay) > 1800000) {
    if (tempInk > (needTemp + alarmTemp) || tempInk < (needTemp - alarmTemp)) {
      beeper(10);
      digitalWrite(alarm_pin, LOW); //если измеренная температура выше заданной на величину аварии
    }
    else digitalWrite(alarm_pin, HIGH); //то включаем аварийный сигнал.
  }
}

void beeper(int duration) {
  tone(beeper_pin, 2000, duration);
}


//управляем вентиляторами
void fan(int fanTime) {
  //float tempInk = sht1x.readTemperatureC();
  DateTime now = RTC.now();
  int fanTemp;
  EEPROM_read(9, fanTemp);
  //lcd.setCursor(6, 3);
  //lcd.print("F");
  //lcd.print(fanTemp);
  if ((now.hour() == 7 && now.minute() < fanTime) || (now.hour() == 19 && now.minute() < fanTime) || (temp1Ink > fanTemp)) {
    digitalWrite(fan_pin, LOW);
    //если наступило время проветривания или измеренная температура выше заданной на величину авариито включаем продувку.
    digitalWrite(extend_heater_pin, HIGH);  // при этом отключаем обогрев
    digitalWrite(heater_pin, LOW);
  }
  else digitalWrite(fan_pin, HIGH); //иначе выключаем.


}

// вольтметр
//void voltmeter() {
//  float outputValue = 0;
//  outputValue = float(analogRead(voltmeter_pin)) / 63, 9;
//  //if(outputValue < 4.5) beeper(50);
//  //Serial.print("Voltage = " );
//  //Serial.println(outputValue);
//  lcd.setCursor(14, 3);
//  lcd.print("V");
//  lcd.print(outputValue, 1);
//}
//
//// Печать мощности нагрвателя
//void outpuPower() {
//  lcd.setCursor(14, 3);
//  lcd.print("W");
//  lcd.print(Output, 0);
//  lcd.print(" ");
//}

void ext_heater(float needTemp) { // управление дополнительным нагревателем на 8 ножке через блок реле.
  float tempInk = sensors.getTempCByIndex(0);
  if (tempInk < (needTemp - 3)) digitalWrite(extend_heater_pin, LOW);
  else digitalWrite(extend_heater_pin, HIGH);
}


/********************************************
 * Serial Communication functions / helpers
 ********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param

void SerialReceive()
{

  // read the bytes sent from Processing
  int index = 0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while (Serial.available() && index < 26)
  {
    if (index == 0) Auto_Man = Serial.read();
    else if (index == 1) Direct_Reverse = Serial.read();
    else foo.asBytes[index - 2] = Serial.read();
    index++;
  }

  // if the information we got was in the correct format,
  // read it into the system
  if (index == 26  && (Auto_Man == 0 || Auto_Man == 1) && (Direct_Reverse == 0 || Direct_Reverse == 1))
  {
    Setpoint = double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the
    //   value of "Input"  in most cases (as
    //   in this one) this is not needed.
    if (Auto_Man == 0)                    // * only change the output if we are in
    { //   manual mode.  otherwise we'll get an
      Output = double(foo.asFloat[2]);    //   output blip, then the controller will
    }                                     //   overwrite.

    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //

    if (Auto_Man == 0) myPID.SetMode(MANUAL); // * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //

    if (Direct_Reverse == 0) myPID.SetControllerDirection(DIRECT); // * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}

// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings.  much easier than getting
// floats from processing to here no?
void SerialSend()
{

  //Serial.print(millis() / 1000);
  Serial.print("PID ");
  Serial.print(Setpoint);
  Serial.print(" ");
  Serial.print(Input);
  Serial.print(" ");
  Serial.print(Output);
  Serial.print(" ");
  Serial.print(myPID.GetKp());
  Serial.print(" ");
  Serial.print(myPID.GetKi());
  Serial.print(" ");
  Serial.print(myPID.GetKd());
  Serial.print(" ");
  if (myPID.GetMode() == AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");
  Serial.print(" ");
  if (myPID.GetDirection() == DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}
prometheus
Offline
Зарегистрирован: 31.05.2011

В алгоритме заложено недостаточное кол-во проветриваний.

За 1 час весь объем воздуха нужно менять не менее 2-х раз. Для этого вентиляцию включают каждые 10 минут на 10 секунд. Иначе эмбрионы могут задохнуться (задохлики).

Выбор датчика температуры не ахти, его точность 0,5 градуса - это много. Нужно калибровать и вносить корректировки, чтобы на рабочем режиме (37-38 градусов) значения температуры были правильные.

Ну и главное - это конструкция самого инкубатора. Если сделать от балды - будет разброс по температуре внутри рабочей камеры. Правильный температурный гистерезис должен быть не более 0,3 градусов.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Спасибо. Я инкубирую яйца постоянно и сотнями. В курсе. Воздухообмен идет постоянно как и работа внутреннего вентилятора. Но проветривание это другое. Это отключение обогрева и охлаждние яиц. Имитация вставания наседеи с гнезда. Яйцо охлаждается, обьем содержимого уменьшается, через скорлупу в яйцо проникает воздух...
это особено актуально для крупных яиц.
По калибровке термометров - у меня есть доступ к поверенному лабораторному оборудованию и имею возможность точно откалибровать Далласы. С разбросом больше 0,3 мне еще не попадались.
гистерезиса нет, есть ПИД.

prometheus
Offline
Зарегистрирован: 31.05.2011

>>Но проветривание это другое.

>>Это отключение обогрева и охлаждние яиц.

НЕТ. Речь не об Охлаждении, а об Проветривании.

Проветривание - удаление углекислых газов выделяемых яйцом.

Охлаждать яйца совсем не обязательно, главное Проветривание. Для примера в промышленных инкубаторах яйца никогда не охлаждают, только проветривание.

 >>гистерезиса нет, есть ПИД.

Вы не поняли. Я про разницу температуры внутри инкубатора.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

В промышленных инкубаторах идет постоянная загрузка яйца партиями и один средний режим поддерживается постоянно. Поэтому и не охлаждают. Гусиные и страусиные с охлаждением лучше выводимость демонстрируют. Воздухообмен 6-кратный я при конструировании инкубатора сразу предусматриваю. 2 раза в час слишком мало, особенно для бройлеров. Вы вообще много сами вывели циплят?

demon969
Offline
Зарегистрирован: 24.04.2012

Можете поястинить про переворот. Я так понал что переворот лотков включается через определенное настраеваемое время, но я не понял на какое время поворот включается? Когда реле поворота должно выключаться?

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Концевик в конце хода лотков. Системы поворота разные могут быть. Есть редукторные асинхронники, шаговые двигатели, моторы постоянного тока и коллекторные. Актуаторы тоже можно. Соответственно и схема управления реверсом разная. Это не проблемы автоматики на самом деле.

prometheus
Offline
Зарегистрирован: 31.05.2011

>>2 раза в час слишком мало, особенно для бройлеров

Много/мало определяется количеством яиц и объемом камеры, производительностью вентилятора. У вас, как я понял, бытовой на 100 яиц. Проветривание каждый 10 минут по 10 секунд - хватит с головой.

>>Вы вообще много сами вывели циплят?

Мне много не нужно. Больше сотни за вывод не вывожу, только для себя.

 

Полезную информацию по конструкции и режимам инкубации можно посмотреть тут http://fermer.ru/forum/samodelnye-inkubatory/163938

Особено полезно видео с объяснениями насчет проветривания.

 

 

prometheus
Offline
Зарегистрирован: 31.05.2011

demon969 пишет:

Можете поястинить про переворот. Я так понал что переворот лотков включается через определенное настраеваемое время, но я не понял на какое время поворот включается? Когда реле поворота должно выключаться?

Берите вот такую штуку и про концевики забудьте :)

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Нет, не бытовой и не на 100. Скажем от 1000 и самострой. Не вижу смысла в цикличном проветривании при наличии постоянного притока свежего воздуха и оттока отработаного. В промышленных инкубаторах управляемые заслрнки видели?

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Не дороговато ли с такой доставкой?
Народные РД09 я на Авите по 100-200р беру и от 220 работает с одним реле и 2 герконами.
позвольте людям иметь свое мнение пожалуйста. Вот так работает поворот в инкубаторе на 1000 яиц на РД09 http://m.youtube.com/watch?v=FydII7kDRq0

prometheus
Offline
Зарегистрирован: 31.05.2011

Umka пишет:
Не дороговато ли с такой доставкой? Народные РД09 я на Авите по 100-200р беру и от 220 работает с одним реле и 2 герконами. позвольте людям иметь свое мнение пожалуйста. Вот так работает поворот в инкубаторе на 1000 яиц на РД09 http://m.youtube.com/watch?v=FydII7kDRq0

Ну, на вкус и цвет...

Чем больше в системе узлов и агрегатов - тем меньше надежность.

Для сравнения как это с применением линейных двигателей работает.

http://www.youtube.com/watch?v=E86fIIEwFik

Я же в небольших инкубаторах использую шаговые двигатели на 12V

demon969
Offline
Зарегистрирован: 24.04.2012

Umka. Выше вы писали, что датчики не дают задержки. Собственно меня интересует DS18B20. Каким образом убрана задержка? Специальной библиотекой или вот этим

sensors.begin();
  sensors.setResolution(12);    // установить разрешение (точность)
  sensors.setWaitForConversion(false);  // отключить ожидание

 

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Самому датчику в любом случае нужно время для вычисления температуры. Этими строчками отключается delay в библиотеке и датчики опрашиваются по таймеру. Просто работа мк не останавливается на время конверсии. Иначе ПИД лажает.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Я видел инкубаторы Кравчика. Он их по 60тр и больше продает. Можно конечно купить и не морочится с Ардуинами. С другой стороны можно купить ципленка, курицу несушку или готовое мясо и не иметь сношений с инкубацией. Я жадный. Мне 200р дешевле 6000 за привод поворота.

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

Umka пишет:
Я видел инкубаторы Кравчика. Он их по 60тр и больше продает. Можно конечно купить и не морочится с Ардуинами. С другой стороны можно купить ципленка, курицу несушку или готовое мясо и не иметь сношений с инкубацией. Я жадный. Мне 200р дешевле 6000 за привод поворота.

В точку. +1000 )))

Kordo
Offline
Зарегистрирован: 18.03.2015

Доброно времени суток! Извените за дремучесть. очень хочется посмотреть схему устройства полностью. Я не профи в смысле электроники. Заранее благодарен

 

paf
Offline
Зарегистрирован: 25.01.2013

Вам придется сходить на перепел ком, найти не сложно. Без регистрации никак.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Да нету схемы, всё видно по описанию пинов и печатке. Не вижу смысла рисовать еще и схему. Плата по сути соединение в кучу двух датчиков, релюшек и Ардуины и диплеем.

busega
Offline
Зарегистрирован: 11.10.2015

Umke уважуха!!!

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Суппер!!!!

Я этим летом для пробы взял 7 кур подрощеных, несушек. Очень понравилось. Планирую на следующий год стационарный курятник ставить и взять еще цесарок на яйца и разведение.

Ну и понятное дело потребуется инкубатор. Так что спасибо за труд.

busega
Offline
Зарегистрирован: 11.10.2015
Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Первый мой, а второй без ПИД и с реле электромагнитным на нагрев. Они на все пишут ПИД, но не в одном нет настроек этих коэффициентов. Разве что для паяльной станции термостат, там есть, но он не инкубаторный.
А xm-18 в остальном ничего блочек. Там 2 ступени нагрева, управление влажностью и поворотом, задумка нормальная, их даже в промышленные китайские шкафы ставят. Но вот реле я бы сразу на твердотельные поменял. Контроллер там свой китайский и доки только на китайском и прошивок я не встречал.

busega
Offline
Зарегистрирован: 11.10.2015

Благодарю за информацию. Но для меня 80 зелёных - дороговато. Наверное, придётся самому ковыряться.

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

busega пишет:

Благодарю за информацию. Но для меня 80 зелёных - дороговато. Наверное, придётся самому ковыряться.

Так там вроде и голую плату за недорого предлагают. Детали в магазине, схема-скетч бесплатно. Куда уж демократичней.

busega
Offline
Зарегистрирован: 11.10.2015

Ну да плата 5 баксиков.

busega
Offline
Зарегистрирован: 11.10.2015

Чем отличается ПИД регулятор от релейного в эксплуатации для меня (эксплуатанта).

Nemo
Offline
Зарегистрирован: 07.03.2015

bwn пишет:

Так там вроде и голую плату за недорого предлагают. Детали в магазине, схема-скетч бесплатно. Куда уж демократичней.

Там не только голые платы, но и КИТ-наборы есть. И услуга по установке SMD-компоненов присутствует в дополнительных услугах. Если кого пугает. И не только по инкубаторам.

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

Ну не знаю, купить тестолит, пару дней на разводку потратить, повозиться с двухсторонкой и сэкономить 350р?
Практически ничем, не считая того, что реле с течением времени прикажет долго жить и искать аналогичное может оказаться еще тем геммороем. Пределы регулирования будут более грубыми. На всех подобных устройствах наиболее удобны электронные ключи.
Релейные регуляторы удобны на термостатировании мощных нагрузок, имеющих большую инерцию, типа бойлеров, систем отопления и т.п.

У Носова и вовсе обходились лампочкой и термометром.))))

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

Так человек сказал, что ему дорого. Хотя я плату за такие деньги поленился бы делать. ИМХО.

terminal
Offline
Зарегистрирован: 02.09.2015

busega пишет:

Чем отличается ПИД регулятор от релейного в эксплуатации для меня (эксплуатанта).

С ПИД температура поддерживается точнее, нет перегрева, это очень важно. На пальцах обЪясняю. Допустим в инкубаторе температура 37,6 градусов, соответственно ее нужно поднять. Если схема на реле то при его включении вся мошность обогревателей греет воздух к примеру +6 градусов, и на выходе получаем прегрев  43,6. При такой температуре яйца сварятся. Теперь про ПИД. Там все по другому температура 37,6 ПИД регулятор вычисляет нужную мощность и включает нагреватель не на всю мощность а только на +0,2 градуса. На выходе 37,8.

busega
Offline
Зарегистрирован: 11.10.2015

Благодарю за ответ.

 

agent01
Offline
Зарегистрирован: 29.10.2015

скажите пожалуйста как в этом коде заменить sht10
на dth11

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Категорически не советую использовать DHT11. Хотяб DHT22 возьмите или AM2123 например. https://drive.google.com/open?id=0B0vRuSZ7oRUeRGgwZlNnY3VVQzQ

agent01
Offline
Зарегистрирован: 29.10.2015

Umka пишет:

Категорически не советую использовать DHT11. Хотяб DHT22 возьмите или AM2123 например. https://drive.google.com/open?id=0B0vRuSZ7oRUeRGgwZlNnY3VVQzQ

я очень уважаю ваше мнение 

но уменя инкубатор на 25-30 яиц

неохота мне платить большие деньги

темболее что DHT11 уже имееться в наличии

вот с кодом проблема

а так бы уже все запустил время всетаки

мне вообщето таких наворотов как у вас ненужно

мне контроль темп. и автомат. переворот яиц

но поскольку с ардуино пока оочень далек

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

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Денег нет, наворотов не нужно, код не получается... даже не знаю что тут посоветовать. Может китайский термостат за 200р? 

agent01
Offline
Зарегистрирован: 29.10.2015
#include <PID_v1.h>
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
#include <EEPROM2.h>
#include <DallasTemperature.h>
#include <OneWire.h>
#include <SimpleTimer.h>
#include <avr/wdt.h>
#include "DHT.h"


int del = 80;                     // переменная ожидания между выборами меню
unsigned int interval = 300;      // интервал сколько будет длиться цикл while, после чего перейдёт к следующему меню.(кол-во итераций)

//#define EXT_HEATING                    // ИСПОЛЬЗУЕМ ДОП.НАГРЕВАТЕЛЬ ВМЕСТО УВЛАЖНИТЕЛЯ. Если нужен увлажнитель, просто закомментируйте эту строку.
#define heater_pin 13                  // нагреватель
//#define humidifer_pin 12               // увлажнитель
#define fan_pin 11                     // вентилятор
#define alarm_pin 14                   // пин аварии
#define beeper_pin 9                   //пищалка по аварии
#define turn_pin 10                    // управление поворотом
#define extend_heater_pin 8          // дополнительный нагреватель
#define button_minus_pin 2            //пин кнопки "минус"
#define button_plus_pin 3             //пин кнопки "плюс"
#define button_enter_pin 4            //пин кнопки "enter"
#define DS18B20_Pin 7                 //пин термометра
#define setSampleTime 1000            //время цикла ПИД
#define voltmeter_pin 1               //вход А1 через делитель (22к/10к) подключен к питанию модуля. Измеряет до 16В.
//#define T_correction -0.0             // коррекция температуры SHT10
//#define h_histeresis 5.0              // гистерезис влажности
#define DHTPIN 5     // what pin we're connected to
#define DHTTYPE DHT11   // DHT 22  (AM2302)

boolean button_minus;
boolean button_plus;
boolean button_enter;
boolean bottomView = 0;            // флаг индикации нижней строки
float humidity;                    // Влажность
float temp1Ink;                    // Температура DS18B20
float temp2Ink;                    // Температура SHT10
float dewpoint;                    // Точка росы

unsigned int rawData;
unsigned long currentTime;            // задаем переменные для тайминга поворота
unsigned long serialTime; //this will help us know when to talk with processing
unsigned long now;
unsigned long trhMillis = 0;    // период опроса датчиков

const unsigned long TRHSTEP   = 300UL;  // Период опроса датчиков

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
RTC_DS1307 rtc;
OneWire oneWire(DS18B20_Pin);
DallasTemperature sensors(&oneWire);
DHT dht(DHTPIN, DHTTYPE);

double Setpoint, Input, Output;            //объявляем переменные для ПИД
PID myPID(&Input, &Output, &Setpoint, 10000.0, 0.5, 10.5, DIRECT); //Инициализируем ПИД-библиотеку и коэффициенты
int WindowSize = setSampleTime;                  // ширина окна терморегулятора 1 секунда.
unsigned long windowStartTime;
unsigned long alarmDelay;

//SimpleTimer timer;

/*  EEPROM1 -     tempInk (float)
    EEPROM(13) -  set_humidity (float)
    EEPROM5 -     +-alarmTemp (float)
    EEPROM9 -     fanTemp (int)
    EEPROM11 -    turnPeriod (int)         */

////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(9600);

  lcd.init();          // Старт дисплея
  lcd.backlight();     // Включаем подсветку дисплея
  windowStartTime = millis();
  byte stat;
  byte error = 0;
  float tempInk;
  float set_humidity;
  float alarmTemp;
  rtc.begin();         // Старт часов
  //int fanTemp;
  //int turnPeriod;
  rtc.adjust(DateTime(__DATE__, __TIME__));
  // EEPROM_read(1, tempInk);
  // tempInk = constrain(tempInk, 36.7, 38.7);
  // EEPROM_write(1, tempInk);
  // EEPROM_read(13, set_humidity);
  // set_humidity = constrain(set_humidity, 40.0, 90.0);
  // EEPROM_write(13, set_humidity);
  // EEPROM_read(9, fanTemp);
  // fanTemp = constrain(fanTemp, 38, 39);
  // EEPROM_write(9, fanTemp);
  // EEPROM_read(5, alarmTemp);
  // alarmTemp = constrain(alarmTemp, 1.0, 4.0);
  // EEPROM_write(5, alarmTemp);
  // EEPROM_read(11, turnPeriod);
  // alarmTemp = constrain(turnPeriod, 0, 13);
  // EEPROM_write(11, turnPeriod);
  
  delay(15);
  wdt_enable (WDTO_8S); //взводим сторожевой таймер на 8 секунд.
  myPID.SetOutputLimits(0, WindowSize); //задаем лимиты ширины ПИД-импульса от 0 до 1 секунды.
  myPID.SetMode(AUTOMATIC);             //включаем ПИД-регулирование
  myPID.SetSampleTime(setSampleTime);

  pinMode(extend_heater_pin, OUTPUT);      //пин дополнительного нагревателя. Переводим в 1 чтобы не включать реле.
  digitalWrite(extend_heater_pin, LOW);
  pinMode(heater_pin, OUTPUT);
  pinMode(turn_pin, OUTPUT);      // устанавливаем выводы
  digitalWrite(turn_pin, LOW);
  pinMode(humidifer_pin, OUTPUT);
  pinMode(fan_pin, OUTPUT);
  digitalWrite(fan_pin, LOW);
  pinMode(alarm_pin, OUTPUT);
  digitalWrite(alarm_pin, HIGH);
  pinMode(button_minus_pin, INPUT_PULLUP); //подтягиваем входы кнопок к плюсу встроенными резисторами
  pinMode(button_plus_pin, INPUT_PULLUP);
  pinMode(button_enter_pin, INPUT_PULLUP);
  alarmDelay = millis();
  dht.begin();
  sensors.begin();
  sensors.setResolution(12);    // установить разрешение (точность)
  sensors.setWaitForConversion(false);  // отключить ожидание
  //timer.setInterval(3000, getSensors);
  windowStartTime = millis();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////

void loop() {
 
  // Input = getTemp();
  unsigned long now = millis();
 {
 DateTime now = rtc.now();
 lcd.setCursor(11, 0); 
 lcd.print(now.hour(), DEC);
 lcd.print(':');
 lcd.print(now.minute(), DEC);
 }
    


  //timer.run();
  button_read();
  if (!button_enter) {
    delay(del);
    lcd.clear();
    menu();
  }
  if (!button_minus) {
    delay(del);
    lcd.clear();
    alarmDelay = millis();    // задержка аварии по нажатии кнопки Минус
  }
  if (!button_plus) {
    delay(del);
    lcd.clear();
    digitalWrite(turn_pin, !digitalRead(turn_pin));       // включаем/выключаем реле поворота по кнопке Плюс
  }
  if (!button_plus && !button_plus) {
    delay(del);
    lcd.clear();
   // bottomView = !bottomView;       // переключаем режим показа нижней строки.
  }
  //send-receive with processing if it's time
  if (millis() > serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime += 500;
  }
  getSensors();
  thermostat();
//#ifdef EXT_HEATING
  extend_heater();
//#else
  humidifer();
//#endif
  turn();
  fan();
  alarm();
  //outpuPower();
  wdt_reset();
  //unsigned long now1 = millis();
  //Serial.println(now1-now);
}

///////////////////////////////////////////////////////////////////////////////////////////////
void button_read() {//функция проверки нажатия кнопки
  button_minus = digitalRead(button_minus_pin); //запоминаем значение кнопки
  button_plus = digitalRead(button_plus_pin); //запоминаем значение кнопки
  button_enter = digitalRead(button_enter_pin); //запоминаем значение кнопки
  if (!button_minus || !button_plus || !button_enter) beeper(50);
  wdt_reset();
}

//меню////////////////////////////////////////////////////////////////////////////////////////
void menu() {
  temp_setup();
  hum_setup();
  turn_setup();
  alarm_setup();
  vent_setup();
  //data_time_setup();
}



//устанавливаем температуру в меню///////////////////////////////////////////////////////////
void temp_setup() {
  float tempInk;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(0, 0);
  lcd.print("TEMP.INK SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }
    button_read();
    //EEPROM_write(1, 37.7);
    EEPROM_read(1, tempInk);
    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      x = 0;
      EEPROM_write(1, tempInk + 0.1);
      if (tempInk > 40) {     //проверяем, если выше 40,
        EEPROM_write(1, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      x = 0;
      EEPROM_write(1, tempInk - 0.1);
      if (tempInk < 30.0) {     //проверяем, если выше 30,
        EEPROM_write(1, 30.0);  //пишем в память 30
      }
      lcd.clear();
    }
    lcd.setCursor(0, 0);
    lcd.print("TEMP.INK = ");
    lcd.print(tempInk, 1);
    lcd.setCursor(0, 1);
    lcd.print("minus NEXT plus");
    delay(del);
  }
}


//устанавливаем влажность////////////////////////////////////////////////////////////////////////
void hum_setup() {
  float set_humidity;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(0, 0);
  lcd.print("HUMIDITY SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    //EEPROM_write(13, 65.0);
    //EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
    EEPROM_read(13, set_humidity);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      x = 0;
      EEPROM_write(13, set_humidity + 0.5);
      if (set_humidity > 100) {     //проверяем, если выше 40,
        EEPROM_write(13, 100.0);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      x = 0;
      EEPROM_write(13, set_humidity - 0.5);
      if (set_humidity < 20) {     //проверяем, если выше 40,
        EEPROM_write(13, 20.0);  //пишем в память 40
      }
      lcd.clear();
    }

    lcd.setCursor(0, 0);
    lcd.print("Humidity = ");
    lcd.print(set_humidity, 1);
    lcd.setCursor(0, 1);
    lcd.print("minus NEXT plus");
    delay(del);
  }

}

//устанавливаем поворот/////////////////////////////////////////////////////////////////////////
void turn_setup() {
  int turnPeriod;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(0, 0);
  lcd.print("TURN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(11, turnPeriod);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      x = 0;
      EEPROM_write(11, turnPeriod + 1);
      if (turnPeriod > 13) {     //проверяем, если выше 40,
        EEPROM_write(11, 13);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      x = 0;
      EEPROM_write(11, turnPeriod - 1);
      if (turnPeriod < 0) {     //проверяем, если выше 40,
        EEPROM_write(11, 0);  //пишем в память 40
      }
      lcd.clear();
    }
    EEPROM_read(11, turnPeriod);
    lcd.setCursor(0, 0);
    lcd.print("PERIOD = ");
    lcd.print(turnPeriod);
    lcd.print(" Hour");
    lcd.setCursor(0, 1);
    lcd.print("minus NEXT plus");
    delay(del);
  }
}


//устанавливаем сигнализацию/////////////////////////////////////////////////////////////////////
void alarm_setup() {
  float alarmTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(0, 0);
  lcd.print("ALARM SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    //EEPROM_write(5, 2.5);
    EEPROM_read(5, alarmTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      x = 0;
      EEPROM_write(5, alarmTemp + 0.1);
      if (alarmTemp > 10.0) {     //проверяем, если ниже 10,
        EEPROM_write(5, 10.0);  //пишем в память 10
      }
      lcd.clear();
    }

    if (!button_plus) {
      x = 0;
      EEPROM_write(5, alarmTemp - 0.1);
      if (alarmTemp < 1.0) {     //проверяем, если выше 40,
        EEPROM_write(5, 1.0);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(0, 0);
    lcd.print("T.Alarm + - ");
    lcd.print(alarmTemp, 1);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 1);
    lcd.print("minus NEXT plus");
    delay(del);
  }
}

//устанавливаем вентиляцию/////////////////////////////////////////////////////////////////
void vent_setup() {
  int fanTemp;
  lcd.clear();
  delay(del);
  button_read();
  lcd.setCursor(0, 0);
  lcd.print("FAN SETUP");
  delay(1000);
  int x = 0;
  while (1) {
    x++;
    if (x > interval) {
      break;
    }

    button_read();
    EEPROM_read(9, fanTemp);

    if (!button_enter) {
      delay(del);
      lcd.clear(); //очищаем экран
      break;
    }
    if (!button_minus) {
      x = 0;
      EEPROM_write(9, fanTemp + 1);
      if (fanTemp > 40) {     //проверяем, если выше 40,
        EEPROM_write(9, 40);  //пишем в память 40
      }
      lcd.clear();
    }

    if (!button_plus) {
      x = 0;
      EEPROM_write(9, fanTemp - 1);
      if (fanTemp < 20) {     //проверяем, если выше 40,
        EEPROM_write(9, 20);  //пишем в память 40
      }
      lcd.clear();
    }
    lcd.setCursor(0, 0);
    lcd.print("T.Fan = ");
    lcd.print(fanTemp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.setCursor(0, 1);
    lcd.print("minus NEXT plus");
    delay(del);
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////
void getSensors() {

  unsigned long curMillis = millis();          // Получаем текущее время работы
  if (curMillis - trhMillis >= TRHSTEP) {    // время для нового измерения?
    sensors.requestTemperatures();
    temp1Ink = sensors.getTempCByIndex(0);
  }

   if (curMillis - trhMillis >= TRHSTEP * 4) {    // время для нового измерения?
    temp2Ink = dht.readTemperature();
//    temp2Ink = (temp2Ink + (T_correction)); // Корректируем показания текрмометра
    humidity = dht.readHumidity();
    trhMillis = curMillis;
  }
  // logData();
}


//////////////////////////////////////////////////////////////////////////////////////////////////
//void logData() {
//  Serial.print("temp1Ink = ");
//  Serial.print(temp1Ink);
//  Serial.print("temp2Ink = ");
//  Serial.print(temp2Ink);
//  Serial.print(" C, Humidity = ");
//  Serial.print(humidity);
//  Serial.print(" %, Dewpoint = ");
//  Serial.print(dewpoint);
//  Serial.println(" C");
//}



//используем терморегулятор
void thermostat() {
  now = millis();
  float tempPoint;
  float set_humidity;
  EEPROM_read_mem(1, &tempPoint, sizeof(tempPoint));
  EEPROM_read(1, tempPoint);
  EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
  EEPROM_read(13, set_humidity);
  Setpoint = tempPoint;
  myPID.Compute();


  if (now - windowStartTime > WindowSize) { //время для перещелкивания периода окна
    windowStartTime = windowStartTime + WindowSize;
    voltmeter();                        //запускаем функцию измерения напряжения
    Input = temp1Ink;
    lcd.setCursor(0, 0);                 // устанавливаем курсор в 0-ом столбце, 0 строка (начинается с 0)
    lcd.print("T");
    lcd.print(temp1Ink, 1);              // печать температуры на дисплей
    //lcd.print((char)223);
    //lcd.print("  (");
    //lcd.print(Setpoint, 1);
    //lcd.print(")");
    lcd.setCursor(5, 0);
    lcd.print("T");
    lcd.print(temp2Ink, 1);            // печать температуры на дисплей
    //lcd.print((char)223);
    //lcd.setCursor(12, 0);
    //lcd.print(" ");
    //lcd.print(temp1Ink - temp2Ink, 1);
    //lcd.print(" ");
    //lcd.print((char)223);
    //lcd.print("  (");
    //lcd.print(Setpoint, 1);
    //lcd.print(")");
    lcd.setCursor(0, 1);                 // устанавливаем курсор в 2-ом столбце, 3 строка (начинается с 0)
    lcd.print("H");
    lcd.print(humidity, 1);           // печать влажности на дисплей
    
    
  }
  if (Output > (now - windowStartTime)) digitalWrite(heater_pin, HIGH);
  else digitalWrite(heater_pin, LOW);

}

//управляем влажностью/////////////////////////////////////////////////////////////////////////
void humidifer() {
  //float humidity;
  float set_humidity;
  //  if (curMillis - humMillis >= humStep) {
  //    humMillis = curMillis;
  //    //humidity = sht1x.readHumidity();
  //  }
  //EEPROM_read_mem(13, &set_humidity, sizeof(set_humidity));
  EEPROM_read(13, set_humidity);
  if (set_humidity > humidity) digitalWrite(humidifer_pin, HIGH); //сравниваем измеренную влажность с заданной
  lcd.setCursor(5, 1);
  lcd.print("#");//вывод инфо состояния на LCD
//  if (set_humidity < humidity + h_histeresis) digitalWrite(humidifer_pin, LOW);
  lcd.setCursor(5, 1);
  lcd.print("-");//вывод инфо состояния
}

//управляем поворотом//////////////////////////////////////////////////////////////////////////
void turn() {
  int turnPeriod;                //период поворота лотков в часах
  int turnCommand;
  boolean turnFlag = 0;          // флаг поворота для случайного периода
  static unsigned long loopTime;
  EEPROM_read(11, turnPeriod);
  if (turnPeriod == 0) return;           //если нулевой период поворота, то не поворачиваем яйца.
  if (turnPeriod < 13) turnCommand = turnPeriod;
  else if (turnPeriod > 12 && turnFlag == 0) { //если произошел поворот (сброшен флаг) и значение в памяти 13, то
    turnCommand = random(1, 6);        //берем случайное значение часов 1-6
    turnFlag = 1;                     //защелкиваем флаг вычисления случайных значений до следующего поворота
  }
  currentTime = millis()/1000;
  //lcd.setCursor(6, 1);
  //lcd.print("P");
  Serial.println(currentTime);
  Serial.println(loopTime + turnCommand * 3600UL);
  if (currentTime > (loopTime + turnCommand * 3600UL)) {  // 3600000 сравниваем текущий таймер с переменной loopTime + период поворота в часах.
    digitalWrite(turn_pin, !digitalRead(turn_pin));       // включаем/выключаем реле поворота
    loopTime = currentTime;    // в loopTime записываем новое значение
    turnFlag = 0;    //сбрасываем флаг поворота
  }
  //lcd.print((loopTime - currentTime + turnCommand * 3600UL) / 60UL);
  //lcd.print(" ");
}

//управляем авариями/////////////////////////////////////////////////////////////////////////////
void alarm() {
  float tempInk = sensors.getTempCByIndex(0);
  float alarmTemp;
  float setTemp;
  EEPROM_read(5, alarmTemp);
  EEPROM_read_mem(1, &setTemp, sizeof(setTemp));
  EEPROM_read(1, setTemp);
  //lcd.setCursor(0, 3);
  //lcd.print("A");
  //lcd.print(setTemp + alarmTemp, 1);
  if ((millis() - alarmDelay) > 1800000) {
    if (tempInk > (setTemp + alarmTemp) || tempInk < (setTemp - alarmTemp)) {
      beeper(10);
      digitalWrite(alarm_pin, LOW); //если измеренная температура выше заданной на величину аварии
    }
    else digitalWrite(alarm_pin, HIGH); //то включаем аварийный сигнал.
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void beeper(int duration) {
  tone(beeper_pin, 2000, duration);
}


//управляем вентиляторами////////////////////////////////////////////////////////////////////////////
void fan() {
  //float tempInk = sht1x.readTemperatureC();
  int fanTemp;
  EEPROM_read(9, fanTemp);
  //lcd.setCursor(6, 3);
  //lcd.print("F");
  //lcd.print(fanTemp);
  if (temp1Ink > fanTemp) digitalWrite(fan_pin, HIGH); //если измеренная температура выше заданной на величину аварии
  else digitalWrite(fan_pin, LOW); //то включаем аварийный сигнал.

}

// вольтметр/////////////////////////////////////////////////////////////////////////////////////////
void voltmeter() {
  float outputValue = 0;
  outputValue = float(analogRead(voltmeter_pin)) / 63, 9;
  //if(outputValue < 4.5) beeper(50);
  //Serial.print("Voltage = " );
  //Serial.println(outputValue);
  lcd.setCursor(6, 1);
  lcd.print("V");
  lcd.print(outputValue, 1);
}

// Печать мощности нагрвателя///////////////////////////////////////////////////////////////////////
void outpuPower() {
  //lcd.setCursor(15, 3);
  //lcd.print("W");
  //lcd.print(Output, 0);
  //lcd.print(" ");
}

void extend_heater() { // управление допnnnnnnnnnnm,.kkkолнительным нагревателем на 8 ножке через блок реле.///////////
  float tempInk = sensors.getTempCByIndex(0);
  float setTemp;
  EEPROM_read_mem(1, &setTemp, sizeof(setTemp));
  EEPROM_read(1, setTemp);
  if (tempInk < (setTemp - 3)) //{
    digitalWrite(extend_heater_pin, HIGH);
//#ifdef EXT_HEATING
    //digitalWrite(humidifer_pin, LOW);
//#endif
  //}
  else digitalWrite(extend_heater_pin, LOW);
//#ifdef EXT_HEATING
  //digitalWrite(humidifer_pin, HIGH);
//#endif
}

/********************************************
* Serial Communication functions / helpers
********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param

///////////////////////////////////////////////////////////////////////////////////////////////////
void SerialReceive(){
  // read the bytes sent from Processing
  int index = 0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while (Serial.available() && index < 26){
    if (index == 0) Auto_Man = Serial.read();
    else if (index == 1) Direct_Reverse = Serial.read();
    else foo.asBytes[index - 2] = Serial.read();
    index++;
  }

  // if the information we got was in the correct format,
  // read it into the system
  if (index == 26  && (Auto_Man == 0 || Auto_Man == 1) && (Direct_Reverse == 0 || Direct_Reverse == 1))
  {
    Setpoint = double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the
    //   value of "Input"  in most cases (as
    //   in this one) this is not needed.
    if (Auto_Man == 0)                    // * only change the output if we are in
    { //   manual mode.  otherwise we'll get an
      Output = double(foo.asFloat[2]);    //   output blip, then the controller will
    }                                     //   overwrite.

    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //

    if (Auto_Man == 0) myPID.SetMode(MANUAL); // * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //

    if (Direct_Reverse == 0) myPID.SetControllerDirection(DIRECT); // * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}


///////////////////////////////////////////////////////////////////////////////////////////////
void SerialSend(){
  Serial.print(millis() / 1000);
  Serial.print("PID ");
  Serial.print(Setpoint);
  Serial.print(" ");
  Serial.print(Input);
  Serial.print(" ");
  Serial.print(Output);
  Serial.print(" ");
  Serial.print(myPID.GetKp());
  Serial.print(" ");
  Serial.print(myPID.GetKi());
  Serial.print(" ");
  Serial.print(myPID.GetKd());
  Serial.print(" ");
  if (myPID.GetMode() == AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");
  Serial.print(" ");
  if (myPID.GetDirection() == DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}

в начале все показывает, а вот в настройках  пишет humidity:ноу

темп:ноу и т.д

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

в eeprom надо данные вбить. 

agent01
Offline
Зарегистрирован: 29.10.2015

а как часы выставить 

и дату?

а то кнопками

немогу нет функции такой 

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

часы и дату? Этого нет в основном скетче. А в автопрограммах есть закоментированная строчка вначале, установка с компа времени при компиляции. А еще я где-то делал по нажатию 2 кнопок меню установки даты-времени.

lock95
Offline
Зарегистрирован: 15.02.2016

Кстати, если нужно подобрать коэффициенты ПИД регулятора можно воспользоваться библиотекой PID_AutoTune_v0.h.

Подбор коэффициентов происходит очень долго, несколько часов. У меня за ночь подобрались коэффициенты. 

#include <PID_v1.h>
#include <PID_AutoTune_v0.h>

#include <OneWire.h>
int DS18B20_Pin = 9; //DS18S20 Signal pin on digital 9
//Temperature chip i/o
OneWire ds(DS18B20_Pin); // on digital pin 9

#define RelayPin 10
int relayPin = 10;
int WindowSize = 3000;
unsigned long windowStartTime;


byte ATuneModeRemember=2;
double input=80, output=50, setpoint=37.81;
double kp=2,ki=0.5,kd=2;

double kpmodel=1.5, taup=100, theta[50];
double outputStart=5; //5
double aTuneStep=500, aTuneNoise=1, aTuneStartValue=500;
unsigned int aTuneLookBack=20; //200

boolean tuning = true;
unsigned long  modelTime, serialTime;

PID myPID(&input, &output, &setpoint,kp,ki,kd, DIRECT);
PID_ATune aTune(&input, &output);

//set to false to connect to the real world
boolean useSimulation = false;

void setup()
{
  //Setup the pid 
  myPID.SetOutputLimits(0, WindowSize);
  myPID.SetMode(AUTOMATIC);


  if(useSimulation)
  {
    for(byte i=0;i<50;i++)
    {
      theta[i]=outputStart;
    }
    modelTime = 0;
  }

  if(tuning)
  {
    tuning=false;
    changeAutoTune();
    tuning=true;
  }
  
  serialTime = 0;
  Serial.begin(9600);
  
  windowStartTime = millis(); //from pid test Relay
  pinMode(RelayPin,OUTPUT);  //from pid test Relay

}

void loop()
{

 unsigned long now = millis();
 float temperature = getTemp();
 float tempF = temperature;// * 9/5 + 32;

  if(!useSimulation)
  { //pull the input in from the real world
    input = tempF;
  }
  
  if(tuning)
  {
    byte val = (aTune.Runtime());
    if (val!=0)
    {
      tuning = false;
    }
    if(!tuning)
    { //we're done, set the tuning parameters
      kp = aTune.GetKp();
      ki = aTune.GetKi();
      kd = aTune.GetKd();
      myPID.SetTunings(kp,ki,kd);
      AutoTuneHelper(false);
    }
  }
  else myPID.Compute();
  
  if(useSimulation)
  {
    theta[30]=output;
    if(now>=modelTime)
    {
      modelTime +=100; 
      DoModel();
    }
  }
  else
  {
 //    analogWrite(0,output); // commented out by me
   unsigned long now = millis();
  if(now - windowStartTime>WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if(output > now - windowStartTime) digitalWrite(RelayPin,HIGH);
  else digitalWrite(RelayPin,LOW);
  }
  
  //send-receive with processing if it's time
  if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime+=1000;
  }
}

void changeAutoTune()
{
 if(!tuning)
  {
    //Set the output to the desired starting frequency.
    output=aTuneStartValue;
    aTune.SetNoiseBand(aTuneNoise);
    aTune.SetOutputStep(aTuneStep);
    aTune.SetLookbackSec((int)aTuneLookBack);
    AutoTuneHelper(true);
    tuning = true;
  }
  else
  { //cancel autotune
    aTune.Cancel();
    tuning = false;
    AutoTuneHelper(false);
  }
}

void AutoTuneHelper(boolean start)
{
  if(start)
    ATuneModeRemember = myPID.GetMode();
  else
    myPID.SetMode(ATuneModeRemember);
}


void SerialSend()
{Serial.print("Temprege: ");Serial.println(input);
  Serial.print("setpoint: ");Serial.print(setpoint); Serial.print(" ");
  Serial.print("input: ");Serial.print(input); Serial.print(" ");
  Serial.print("output: ");Serial.print(output); Serial.print(" ");
  if(tuning){
Serial.println(" tuning mode ");

  } else {
    Serial.print("Temprege: ");Serial.println(input);
    Serial.print("kp: ");Serial.print(myPID.GetKp());Serial.print(" ");
    Serial.print("ki: ");Serial.print(myPID.GetKi());Serial.print(" ");
    Serial.print("kd: ");Serial.print(myPID.GetKd());Serial.println();
  }
}

void SerialReceive()
{
  if(Serial.available())
  {
   char b = Serial.read(); 
   Serial.flush(); 
   if((b=='1' && !tuning) || (b!='1' && tuning))changeAutoTune();
  }
}

void DoModel()
{
  //cycle the dead time
  for(byte i=0;i<49;i++)
  {
    theta[i] = theta[i+1];
  }
  //compute the input
  input = (kpmodel / taup) *(theta[0]-outputStart) + input*(1-1/taup) + ((float)random(-10,10))/100;

}

float getTemp(){
 //returns the temperature from one DS18S20 in DEG Celsius

 byte data[12];
 byte addr[8];

 if ( !ds.search(addr)) {
   //no more sensors on chain, reset search
   ds.reset_search();
   return -1000;
 }

 if ( OneWire::crc8( addr, 7) != addr[7]) {
   Serial.println("CRC is not valid!");
   return -1000;
 }

 if ( addr[0] != 0x10 && addr[0] != 0x28) {
   Serial.print("Deviceot recognized");
   return -1000;
 }

 ds.reset();
 ds.select(addr);
 ds.write(0x44,1); // start conversion, with parasite power on at the end

 byte present = ds.reset();
 ds.select(addr);  
 ds.write(0xBE); // Read Scratchpad

 
 for (int i = 0; i < 9; i++) { // we need 9 bytes
  data[i] = ds.read();
 }
 
 ds.reset_search();
 
 byte MSB = data[1];
 byte LSB = data[0];

 float tempRead = ((MSB << 8) | LSB); //using two's compliment
 float TemperatureSum = tempRead / 16;
 
 return TemperatureSum;
 
}

 

lean_74
Онлайн
Зарегистрирован: 22.12.2015

Umka пишет:

Категорически не советую использовать DHT11. Хотяб DHT22 возьмите или AM2123 например. https://drive.google.com/open?id=0B0vRuSZ7oRUeRGgwZlNnY3VVQzQ

Здравствуйте, чтобы не плодить темы, продолжу в этой, тем более, что взял за основу скеч от Umka, но так как с микроконтроллерами недавно занимаюсь, вопрос по датчику влажности Si7021, как правильно с ним работать? у него есть внутренний нагреватель, в демо примере он 30 сек включен, 20 выключен, измерения делаются раз в секунду. Сам вопрос: надо включать этот внутренний нагреватель или нет

#include <Wire.h>
#include <Si7021.h>

SI7021 si7021;

// BASIC DEMO
// ----------
// Print humidity and temperature to the serial monitor while toggling the heater on and off
// periodically.

// NOTE ON RESOLUTION:
// -------------------
// Changing the resolution of the temperature will also change the humidity resolution.
// Likewise, changing the resolution of the humidity will also change temperature resolution.
// Two functions are provided to change the resolution, both functions are similar except they have
// different masks to the registers, so setTempRes(14) will change the temp resolution to 14-bit
// but humidity will also be set to 12-bit. Setting the humidity resolution to 8-bit setHumidityRes(8)
// will change the temperature resolution to 12-bit.
// Resolution Table:
//                    14-bit Temp <-> 12-bit Humidity
//                    13-bit Temp <-> 10-bit Humidity
//                    12-bit Temp <->  8-bit Humidity
//                    11-bit Temp <-> 11-bit Humidity

// NOTE ON HEATER:
// ---------------
// The HTRE bit in the user register is what turns the heater on and off. This register
// is stored on non-volatile memory to it will keep its state when power is removed. If
// the heater is enabled and power is removed before the program had chance to turn it off
// then the heater will remain enabled the next time it is powered on. The heater has to
// be explicitly turned off. In the begin() function for this library, a reset procedure
// will take place and reset the user register to default, so the heater will be turned off
// and the resolution will also be set to default, 14-bit temperature and 12-bit humidity.
// Keep this in mind if testing and swapping sensors/libraries.

void setup()
{
Serial.begin(115200);
si7021.begin(); // Runs : Wire.begin() + reset()
  while(!Serial); // Wait for serial monitor to open
Serial.println("BASIC DEMO");
Serial.println("------------------------------------------");
si7021.setHumidityRes(12); // Humidity = 12-bit / Temperature = 14-bit
}

void loop()
{
static uint8_t heaterOnOff; // Create static variable for heater control
si7021.setHeater(heaterOnOff); // Turn heater on or off
Serial.print("Heater Status = ");
Serial.println(si7021.getHeater() ? "ON" : "OFF");

  for(int i = (heaterOnOff ? 20 : 30); i>0; i--)
  {
  Serial.print("Humidity : ");
  Serial.print(si7021.readHumidity()); // Read humidity and print to serial monitor
  Serial.print(" %\t");
  Serial.print("Temp : ");
  Serial.print(si7021.readTemp()); // Read temperature and print to serial monitor
  Serial.print(" C\t");
  Serial.println(i); // Print count down for heater change
  delay(500);
  }

heaterOnOff = !heaterOnOff; // Toggle heater on/off variable
}

?