пятница, 11 января 2019 г.

Таймер-Терморегулятор для аквариума/террариума

Здравсвуйте, представляю вашему вниманию проект на Arduino. Террариум, в котором живёт черепаха. Наше устройство служит для выполнения следующих задач:
1) включение и выключение лампы по расписанию (день-ночь);
2) поддержание температуры в террариуме путём включения и выключения нагревательного элемента;
3) отображение текущего времени суток и температуры воды в террариуме на четырёхсекционном светодиодном семисегментном дисплее (хорошо видно днём и ночью, выполняют функцию настенных часов), яркость дисплея меняется в зависимости от сотояния "день/ночь";
4) Все необходимые пользователю настоечные параметры (уставка температуры, гистерезис, первый и последний часы дня, текущее время) возможно легко задавать без подключения к ПК или иным устройствам, то есть с кнопок.
Вот результат решения этой задачи

Для этого проекта я использовал модули:
Arduino Pro Mini (ATmega328, 5В)
Реле э/м 1-канальное, 5В - 2 шт. Рекомендую вместо них использовать Реле электромеханическое 2-канальное - 1 шт.
Модуль часов реального времени DS1307
Цифровой дисплей TM1637
Кнопка - 4 шт.
термистор
резистор 10 кОм - пачка :)

Схему соединения выложу позже. Назначения пинов ясно из деректив #define в начале кода. Незабываем кнопки (включая reset) и термистор подключать с использованием резисторов.

программный код:
#include
#include
#include
#include
#include
#include

//номера пинов
#define LIGHT 2
#define TEN 3
#define DISPLAY_CLK 4
#define DISPLAY_DIO 5
#define BUTTON_MODE 9
#define BUTTON_MINUS 7
#define BUTTON_PLUS 8
// **************

//количество режимов настройки
#define MIN_TUNER_MODE 2
#define MAX_TUNER_MODE 6
#define TIMEOUT 10
// **************

//адрес настроек в Eeprom
#define EE_ADDRESS 0
// **************

class Timer
{
  long int start_time;
public:
  void start()
  {
    start_time = millis();
  }
  int seconds()
  {
    return (int)((millis() - start_time)/1000);
  }
  long int miliseconds();
  Timer()
  {
    start();
  }
};

struct Settings
{
  float temp_ust,temp_hyst;//температура, поддерживаемая в аквариуме, гистерезис
  int time_light_on,time_light_off;//время включения и выключения света
};

Settings settings;
int8_t DispMSG[] = {10, 16, 31, 6};
TM1637 clock_diplay(DISPLAY_CLK, DISPLAY_DIO);
bool ten_on=false;
Timer notclick;

double Thermister(int RawADC) {
  double Temp;
  Temp = log(((10240000/(1024-RawADC)) - 10000));
  Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
  Temp = Temp - 273.15;   // Kelvin to Celcius
  //Temp = (Temp * 9.0)/ 5.0 + 32.0;    // 1 способ Convert Celcius to Fahrenheit
  //Temp = (Temp * 1.8) + 32.0;   // 2 способ Convert Celcius to Fahrenheit
  return Temp;
  /*
  float RT, VR, ln, TX, T0;
  int VRT;
  VRT=RawADC;
  VRT = (5.00 / 1023.00) * VRT;      //Преобразуем в напряжение
  VR = VCC - VRT;
  RT = VRT / (VR / R);               //Сопротивление RT

  ln = log(RT / RT0);
  TX = (1 / ((ln / B) + (1 / T0))); //Температура с термистора

  TX = TX - 273.15;                 //Преобразуем в цельсии
  return TX;*/
}


void show_error(int8_t err_num)
{
  DispMSG[0] = 14;
  DispMSG[1] = 28;
  DispMSG[2] = 28;
  DispMSG[3]=err_num;
  clock_diplay.display(DispMSG);
  clock_diplay.point( false );
}


void relaySet(tmElements_t tm_)
{
  if( (tm_.Hour >= settings.time_light_on)&&(tm_.Hour <= settings.time_light_off ) )
  {
    digitalWrite(LIGHT, LOW);
    Serial.println("Light ON");
    clock_diplay.set(7);
  }
  else
  {
    digitalWrite(LIGHT, HIGH);
    Serial.println("Light OFF");
    clock_diplay.set(1);
  }

  if( ten_on )
  {
    if (float(Thermister(analogRead(0)))> settings.temp_ust+settings.temp_hyst )
      ten_on=false;
    digitalWrite(TEN, LOW);
    Serial.println("TEN ON");
  }
  else
  {
    if (float(Thermister(analogRead(0))) < settings.temp_ust-settings.temp_hyst )
      ten_on=true;
    digitalWrite(TEN, HIGH);
    Serial.println("TEN OFF");
  }
}

void clockDispSet(tmElements_t tm_)
{
  if((tm_.Second%10)>4)
  {
  DispMSG[0]=tm_.Hour/10;
  DispMSG[1]=tm_.Hour%10;
  DispMSG[2]=tm_.Minute/10;
  DispMSG[3]=tm_.Minute%10;
  clock_diplay.display(0,DispMSG[0]);
  clock_diplay.point( ((tm_.Second%2)>0) );
  clock_diplay.display(1,DispMSG[1]);
  clock_diplay.display(2,DispMSG[2]);
  clock_diplay.display(3,DispMSG[3]);
  }
  else
  {
    int temp=int(Thermister(analogRead(0)));
    DispMSG[0]=temp/10;
    DispMSG[1]=temp%10;
    DispMSG[2]=33;//17;
    DispMSG[3]=12;
    clock_diplay.point( false );
    clock_diplay.display(DispMSG);
  }
}

void mode1() {
  tmElements_t tm;
  Serial.print("Sensor_0: ");
  Serial.println(float(Thermister(analogRead(0))), 1);    // выводим показание 1 датчика

  if (RTC.read(tm)) {
    Serial.print("Time = ");
    Serial.print(tm.Hour);
    Serial.write(':');
    Serial.print(tm.Minute);
    Serial.write(':');
    Serial.print(tm.Second);
    Serial.print(", Date (D/M/Y) = ");
    Serial.print(tm.Day);
    Serial.write('/');
    Serial.print(tm.Month);
    Serial.write('/');
    Serial.print(tmYearToCalendar(tm.Year));
    Serial.println();
    relaySet(tm);
    clockDispSet(tm);
  } else {
    setHour();
    Serial.println("There are problem whith DS1307RTC");
    if (RTC.chipPresent()) {
      Serial.println("The DS1307 is stopped.  Please run the SetTime");
      Serial.println("example to initialize the time and begin running.");
      Serial.println();
    } else {
      Serial.println("DS1307 read error!  Please check the circuitry.");
      Serial.println();
    }
    delay(9000);
  }

}

void show_mode(int mode)
{
  tmElements_t tm;
  switch(mode)
  {
    case -1: DispMSG[0] = 17;  DispMSG[1] = 17; clock_diplay.point( false ); DispMSG[2] = 17;  DispMSG[3] = 17;  break;
    case 2:  DispMSG[0] = 29;  DispMSG[1] = 33; clock_diplay.point( true ); DispMSG[2] = (int)settings.temp_ust/10;  DispMSG[3] = (int)settings.temp_ust%10;  break;
    case 3:  DispMSG[0] = 20;  DispMSG[1] = 29; clock_diplay.point( true );  DispMSG[2] = (int)settings.temp_hyst;  DispMSG[3] = (int)(10*settings.temp_hyst)%10; break;
    case 4:  DispMSG[0] = 34;  DispMSG[1] = 17; clock_diplay.point( true );  DispMSG[2] = settings.time_light_on/10;  DispMSG[3]=settings.time_light_on%10;break;
    case 5:  DispMSG[0] = 36;  DispMSG[1] = 17; clock_diplay.point( true );  DispMSG[2] = settings.time_light_off/10;  DispMSG[3]=settings.time_light_off%10;break;
    case 6:  DispMSG[0] = 29; DispMSG[1] = 21; clock_diplay.point( false ); DispMSG[2] = 26; DispMSG[3] = 14; break;
/*
    case 6:
      DispMSG[0] = 25;  DispMSG[1] = 25; clock_diplay.point( true );
      if (RTC.read(tm)) { DispMSG[2] = tm.Minute/10;  DispMSG[3] = tm.Minute%10;}
      else { DispMSG[2] = 0;  DispMSG[3]=0;}
      break;
    case 7:
      DispMSG[0] = 19;  DispMSG[1] = 17; clock_diplay.point( true );
      if (RTC.read(tm))
      {
        DispMSG[2] = tm.Hour/10;
        DispMSG[3] = tm.Hour%10;
      }
      else { DispMSG[2] = 0;  DispMSG[3]=0;}
      break;
      */
  }
  clock_diplay.display(DispMSG);

}

void show_float(float f)
{
  DispMSG[0]=(int)f/10;
  DispMSG[1]=(int)f%10;
  DispMSG[2]=35;
  DispMSG[3]=(int)(f*10)%10;
  clock_diplay.display(0,DispMSG[0]);
  clock_diplay.point( false );
  clock_diplay.display(1,DispMSG[1]);
  clock_diplay.display(2,DispMSG[2]);
  clock_diplay.display(3,DispMSG[3]);

}

void setFloat(float *var,float minimum,float maximum)
{
  Serial.println("Set float");
  show_float(*var);
  Serial.println(*var);
  delay(500);
  while( (digitalRead(BUTTON_MODE) == LOW) &&(notclick.seconds()  {
    if(digitalRead(BUTTON_PLUS) == HIGH)
    {
      notclick.start();
      *var = (*var) + 0.1;
 //     if( (*var)>maximum )
 //       (*var) = minimum;
      Serial.println(*var);
      show_float(*var);
      delay(250);
    }
    if(digitalRead(BUTTON_MINUS) == HIGH)
    {
      notclick.start();
      *var = (*var) - 0.1;
      if( (*var)< minimum )
        (*var) = maximum;
      Serial.println(*var);
      show_float(*var);
      delay(250);
    }
  }
}

void show_int(int dm0,int dm1,int var)
{
  DispMSG[0]=dm0;
  DispMSG[1]=dm1;
  DispMSG[2]=var/10;
  DispMSG[3]=var%10;
  clock_diplay.display(0,DispMSG[0]);
  clock_diplay.point( true );
  clock_diplay.display(1,DispMSG[1]);
  clock_diplay.display(2,DispMSG[2]);
  clock_diplay.display(3,DispMSG[3]);

}

void setInt(int dm0,int dm1,int *var,int minimum,int maximum)
{
  Serial.println("Set int");
  show_int(dm0,dm1,*var);
  delay(500);
  while(( digitalRead(BUTTON_MODE) == LOW)&&(notclick.seconds()  {
    if(digitalRead(BUTTON_PLUS) == HIGH)
    {
      notclick.start();
      (*var)++;
      if( (*var)>maximum )
        (*var) = minimum;
      show_int(dm0,dm1,*var);
      Serial.println(*var);
      delay(250);
    }
    if(digitalRead(BUTTON_MINUS) == HIGH)
    {
      notclick.start();
      (*var)--;
      if( (*var)< minimum )
        (*var) = maximum;
      show_int(dm0,dm1,*var);
      Serial.println(*var);
      delay(250);
    }
  }
}


void setTemp()
{
  float temp = settings.temp_ust;
  setFloat(&temp,10,40);
  settings.temp_ust = temp;
  EEPROM.put(EE_ADDRESS, settings);
}

void setHyst()
{
  setFloat(&(settings.temp_hyst),0.1,3);
  EEPROM.put(EE_ADDRESS, settings);
}

void setTimeLightOn()
{
  setInt(0,26, &(settings.time_light_on),0,23 );
  EEPROM.put(EE_ADDRESS, settings);
}

void setTimeLightOff()
{
  setInt(0,15, &(settings.time_light_off),0,23 );
  EEPROM.put(EE_ADDRESS, settings);
}

void setHour()
{
  tmElements_t tm;
  int h,m;
  if(RTC.read(tm))
  {
    h = tm.Hour;
    m = tm.Minute;
  }
  else
  {
    h = 12;
    m = 0;
  }
  setInt(19,17,&h,0,23);
  setInt(17,17,&m,0,59);
  tm.Hour = h;
  tm.Minute = m;
  tm.Second = 0;
  tm.Day = 1;
  tm.Month = 1;
  tm.Year = 18;

  RTC.write(tm);
}

void mode2()
{
 int mode=MIN_TUNER_MODE;//переменная выбора режима
 show_mode(-1);
 delay(500);
 while( (mode<=MAX_TUNER_MODE)&&(notclick.seconds() {
  if(digitalRead(BUTTON_MODE) == HIGH)
  {
    notclick.start();
    mode++;
    show_mode(-1);
    delay(500);
  }
  if ( (digitalRead(BUTTON_PLUS) == HIGH) || (digitalRead(BUTTON_MINUS) == HIGH) )
  {
    notclick.start();
    switch(mode)
    {
      case 2: setTemp();break;
      case 3: setHyst();break;
      case 4: setTimeLightOn();break;
      case 5: setTimeLightOff();break;
      case 6: setHour();break;
    }
  }

  Serial.print("mode: ");
  Serial.println(mode);
  show_mode(mode);
 }
}

void print2digits(int number) {
  if (number >= 0 && number < 10) {
    Serial.write('0');
  }
  Serial.print(number);
}





void loop()
{
  if(digitalRead(BUTTON_MODE) == LOW)
  {
    mode1();
  }
  else
  {
    notclick.start();
    mode2();
  }
}

void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for serial
  delay(200);
  Serial.println("Akvarium v6 geting settings and testing.");
  Serial.println("-------------------");

  EEPROM.get(EE_ADDRESS, settings);
  if( !( (settings.temp_ust > -50. )&&(settings.temp_ust < 100. ) ) )
  {
    Serial.println("install default settings.");
    settings.temp_ust = 22;
    settings.temp_hyst = 1;
    settings.time_light_on=9;
    settings.time_light_off=21;
    EEPROM.put(EE_ADDRESS, settings);
  }

  clock_diplay.init();
  //Установка яркости горения сегментов
  /*
   * BRIGHT_TYPICAL = 2 Средний
   * BRIGHT_DARKEST = 0 Тёмный
   * BRIGHTEST = 7      Яркий
   */
  clock_diplay.set(BRIGHT_TYPICAL);
  //Задание на включение разделителя
  clock_diplay.point(true);
  //Выводим массив на дисплей
  clock_diplay.display(DispMSG);
  pinMode(LIGHT, OUTPUT);
  pinMode(TEN, OUTPUT);
  pinMode(A0, INPUT);
  pinMode(BUTTON_MODE, INPUT);
  pinMode(BUTTON_MINUS, INPUT);
  pinMode(BUTTON_PLUS, INPUT);
/**/
  digitalWrite(LIGHT, LOW);
  delay(1500);
  digitalWrite(LIGHT, HIGH);
  delay(1500);
  /**/

}