пятница, 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);
  /**/

}

четверг, 18 июня 2015 г.

Поход к церкви Николы на Липне

            В этом посту я повествую о своём первом опыте одиночного похода выходного дня. Путешествовал я совсем недалеко (около 10 км от кремля) от города Великий Новгород. Однако из-за болот и большого количества рек этот район почти необитаем людьми.
            Путешествовал я с видео-камерой. На в момент написания рассказа (наброски я сделал уже в походе) видео в монтаже.
             День первый. Вечер пятницы. Вышел на берег Сиверсова канала. Дорога вдоль берега заболочена. Немного прошёл вдоль и встал напротив деревни Сковородка. За всё время пребывания на этой стоянке я видел только одного пешехода: парень с удочкой.
Сиверсов канал

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

за переправой
           Дальше началась основная часть похода. При помощи навигации и интуиции нашёл что-то напоминающее на заброшенную дорогу и пошёл по этой Козловой дороге (так называют её местные жители) или вдоль неё (не везде я мог однозначно сказать, что когда-то здесь была дорога). Сначала был густой лес с крупными деревьями, потом - поле, затем - вышел в болото, которое явно когда-то было дорогой. Шёл по этой бывшей дороге по плавучим кочкам ("второе дно") и корням по краям. Проваливался сначала по колено, потом - по пояс. Стоит отметить то, что я не взял с собой вторые штаны для экономии веса. В конце этой части пути увидел между деревьев купол. Стоит отметить, что к тому моменту я уже подустал и обрадовался близости цели, однако радость моя была недолгой. За "Черным лесом" (так это место называют местные) оказался заболоченный луг и озеро, в середине которого церковь.
Козлова дорога в Черном лесу

Стала видна конечная точка маршрута
"...За "Черным лесом" (так это место называют местные) оказался заболоченный луг и озеро, в середине которого церковь."
                Пришлось обходить по лугу. Воды примерно по колено. перешейка между мной и церковью не видно, но на карте (Яндекс народная) обозначена дорога. Нашёл место, где перейти с берега на остров. Там оказалось примерно по пояс. И пошёл по такому же лугу к церкви. Подойдя поближе увидел большое количество рыболовных сетей, заготовленные чурбаны и женщину, коловшую дрова.
Осталось ещё чуть-чуть
                Подошёл к женщине и сказал: "Здравствуйте!" Она: "Ой!" Тут выбежали две собаки и ко мне, я от них и баллончик перцовый выпустил, на который они (собаки) не отреагировали. Женщина зовёт собак, а они не уходят. Кричит мне:
 - Через 20 минут Леонид приедет.
 - И можно будет переночевать здесь?
 - Какой переночевать! Тут церковь!
 - Я в палатке на острове!
 - Вон там, где песочек можно встать, - и показывает, куда идти.
Собаки на по-тихоньку наступают, я - отступаю. Уже в болото полезли. Защищаться от собак мне нечем.
                Пришёл на то место, куда мне указала женщина. Там людские следы: костер тлеет (совсем недавно кто-то ушёл отсюда), дрова приготовлены. Добавил огня, стал сушиться, котелок поставил. Подъезжает лодка. В ней три мужика. Я в трусах, вещи по поляне раскиданы, что-то у костра сушится. Один мужик выходит ко мне:
 - Это тебя собаки...
 - Это меня собаки выгнали.
Рассказываю, что я турист, пришёл пешком. А он мне: "Пришёл! Пешком! По болоту! Вам там в городе совсем делать нечего... чего вам перед телевизором дома не сидится!" Я не стал объяснять, почему мне пред зомбоящиком не сидится. Видимо много туристов их посещать стало (геокешерский тайник заложен в 2006. я ходил в 2015). Надоели им гости.
"Подсушился, поел я и лёг спать."

                Подсушился, поел я и лёг спать. На следующий день предстоит без штанов идти (у меня одни, сушить будет некогда). Планирую возвращаться тем же маршрутом. Важно найти начало Козловой дороги. Место где я вышел, на несколько десятков метров отличается от обозначенного на карте.
Ближе я не подошёл

                День третий. Воскресение. Встал, позавтракал, собрался, пошёл. Дорогу по которой я шёл так и не нашёл, хотя был где-то очень близко. Пейзаж на обратном пути через "Черный лес" заметно отличался: лес на болоте. В поле увидел примятую трактором (наверное) калею и пошёл по ней. Дорога, теперь уже действующая, вывела меня на берег Сиверсова канала как раз в район деревни Сковородка. До берега немного болота, но я уже к тому времени привык. Потом рыбаки переправили меня на берег с цивилизацией. До трассы Новгород-Москва можно было дойти пешком, но мне повезло застопить попутку до Новгорода.
путь обратно
С этой остановки начался поход.
на обратном пути

вторник, 18 ноября 2014 г.

Самая грязная река Ленинградской области

   Летом путешествовал с братом по реке Ижора (самой грязной в Ленинградской области). В Антропшино река очень чистая, но в нас. пункте Заречное (ж.д. станция 34 км) вода ужасно грязная и вонючая. Причина в том, что между этими населёнными пунктами сливают лютые нечистоты. Даже на спутниковых картах это видно: река резко белеет.
   Куда жаловаться не знаю. Местные жители всё это нюхают, не купаются, не ловят рыбу и до сих пор ничего не сделали. Есть ли специальная служба, занимающаяся подобными проблемами?
Напоследок пара фото с той поездки.