Экономим электричество или таймер времени для ночного тарифа в электросетях

Как часто вам приходится настраивать различные таймеры? Будильник на смартфоне, таймер на хлебопечке, чтобы к завтраку был свежий хлеб, да и мало ли когда нужно что-то начать. Не менее важной оказывается задача вовремя отключить или закончить действие.

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

Итак, стоит задача включать электрический котел отопления для обогрева дома в ночной период времени, когда тарификация хоть немного щадящая. Что из этого получилось — смотрите ниже.

image

Покупать готовое реле времени за 6-8 тысяч рублей (не говорим о простых девайсах, втыкаемых в розетку), которые будут замыкать нужные контакты по срабатыванию событий или по времени, просто неспортивно. Кроме того, контроллер с аналогичными функциями, а то и с хорошим запасом функционала обойдется раза в 4 дешевле, не говоря о разминке для мозгов. Началось все в новогодние праздники.

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

Отсюда определился список устройств, необходимых для выполнения задачи:

1. Arduino Nano (чем дешевле модуль, тем лучше, так как задачи не ресурсоёмкие);
2. Модуль времени RTC;
3. Модуль реле ( 2 реле);
4. Адаптер питания для ардуино. Был выбран DC-DC преобразователь-стабилизатор 12В-5В;
5. Дисплей 4х20 символов для красоты (в наличии были 4-х строчный и 2-х строчный);
6. Адаптер I2C для дисплея.

Вот так это оказалось соединено в итоге:

image

Собрать это все на макетке было делом достаточно простым. Изучая Хабр, я давненько приметил работу с I2C -шиной и уж больно мне понравился дизайн часов товарища, поэтому я позаимствовал часть его кода и сделал основные часы в таком же стиле.

Дальше шел долгий процесс изучения работы с EEPROM и модулем часов, чтобы при сбое питания таймер времени и сами часы не сбивались. Поначалу часы выключались, но длительное изучение форумов привело к мысли, что в модуле RTC стоит Li-ION аккумулятор и он просто сел от долгого путешествия из Китая в Россию. Поддержка питанием в течении пары суток позволила восполнить разряд и больше время не сбивалось.

Осталось соединить все модули покрепче, написать код и начать отладку.

Скетч реле времени
// Date and time functions using a DS1307 RTC connected via I2C and Wire lib


#include <EEPROM.h>
#include <Wire.h>
#include <DS1307.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
#define timePIN 10
#define RELE_1 12 //объявляем работу 1 реле на пине 7
#define RELE_2 11 //объявляем работу 2 реле на пине 8

//на пин A4 цепляем SDA монитора и SDA часов
//на пин A5 цепляем SCL монитора и SCL часов


int FullMinutesTimerOn = EEPROM.read(0);
int FullMinutesTimerOff = EEPROM.read(2); 
int rtc[7];
byte rr[7];
int ledPin =  13;
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display
int button1 = 10; //кнопка на пин D10
int button2 = 9; //кнопка на пин D9
int button3 = 8; //кнопка на пин D8
int button4 = 7; //кнопка на пин D7
int button5 = 6; //кнопка на пин D6
int led = 5;
int rejim=0; //переменная режима изменения настроек таймера
int PrevSec=0;
int CurSec=0;
boolean lastButton=LOW;


byte LT[8] = 
{


  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] =
{

  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100
};
byte MB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte block[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
// loop counter
int count = 0;



void setup () {
  DDRC|=_BV(2) |_BV(3);  // POWER:Vcc Gnd
  PORTC |=_BV(3);  // VCC PINC3
  pinMode(ledPin, OUTPUT);  
  pinMode(timePIN, OUTPUT); 
  pinMode(RELE_1, OUTPUT); //инициируем реле только на выход  
  pinMode(RELE_2, OUTPUT); //инициируем реле только на выход
  digitalWrite(RELE_1, HIGH); // запускаем реле выключенными
  digitalWrite(RELE_2, HIGH); // запускаем реле выключенными 
  
  RTC.get(rtc,true);
  lcd.init();                      // initialize the lcd 
  lcd.backlight();
  lcd.home();

  lcd.createChar(0,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,MB);
  lcd.createChar(7,block);

  // sets the LCD's rows and colums:
  lcd.clear();  



  RTC.SetOutput(DS1307_SQW32KHZ);
  pinMode(led, OUTPUT);
  pinMode(button1, INPUT);
  pinMode(button2, INPUT);
  pinMode(button3, INPUT);
}



void custom0(int x)
{ // uses segments to build the number 0

  lcd.setCursor(x,0); // set cursor to column 0, line 0 (first row)
  lcd.write(0);  // call each segment to create
  lcd.write(1);  // top half of the number
  lcd.write(2);
  lcd.setCursor(x, 1); // set cursor to colum 0, line 1 (second row)
  lcd.write(3);  // call each segment to create
  lcd.write(4);  // bottom half of the number
  lcd.write(5);
}

void custom1(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(2);
  lcd.print(" ");
  lcd.setCursor(x,1);
  lcd.write(4);
  lcd.write(7);
  lcd.write(4);
}

void custom2(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(4);
}

void custom3(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5); 
}

void custom4(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(7);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom5(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void custom6(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom7(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom8(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom9(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);

}

void digitalClockDisplay(){
  // digital clock display of the time
  

  printDigits(rtc[2]/10,0); 
  printDigits(rtc[2]%10,4); 

  printDigits(rtc[1]/10,9);
  printDigits(rtc[1]%10,13);

   
  if (rtc[0]%10%2==0){
    lcd.setCursor(7, 0);
    lcd.print("+ ");
    lcd.setCursor(7, 1);
    lcd.print(" +");
    lcd.setCursor(3, 1);
    lcd.print("+");
    lcd.setCursor(12, 0);
    lcd.print("+");
    lcd.setCursor(3, 0);
    lcd.print(" ");
    lcd.setCursor(12, 1);
    lcd.print(" ");
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print(" +");
    lcd.setCursor(7, 1);
    lcd.print("+ ");
    lcd.setCursor(3, 0);
    lcd.print("+");
    lcd.setCursor(12, 1);
    lcd.print("+");
    lcd.setCursor(3, 1);
    lcd.print(" ");
    lcd.setCursor(12, 0);
    lcd.print(" ");
  }
  //нарисовали двоеточие

}



void printDigits(int digits, int x){
  // utility function for digital clock display: prints preceding colon and leading 0

  switch (digits) {
  case 0:  
    custom0(x);
    break;
  case 1:  
    custom1(x);
    break;
  case 2:  
    custom2(x);
    break;
  case 3:  
    custom3(x);
    break;
  case 4:  
    custom4(x);
    break;
  case 5:  
    custom5(x);
    break;
  case 6:  
    custom6(x);
    break;
  case 7:  
    custom7(x);
    break;
  case 8:  
    custom8(x);
    break;
  case 9:  
    custom9(x);
    break;

  }


}


void loop () {
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1]; //присваиваем минуты
  int Second = rtc[0]; // присваиваем секунды
  int FullMinutes = Hour * 60 + Minute; //приводим значение текущего времени к минутам
  int FullMinutesTimerOn= hourOn*60+minOn; //приводим значение ВКЛЮЧЕНИЯ таймера к минутам
  int FullMinutesTimerOff= hourOff*60+minOff; //приводим значение ВЫКЛЮЧЕНИЯ таймера к минутам
  int sutki=0; // по умолчанию, таймер работает в одних сутках
  
  if (hourOff-hourOn < 0) sutki=1; //если время выключения на следующие сутки, то включаем триггер сутки=1
  else sutki=0;
  
  if (sutki==1 && (FullMinutes >= FullMinutesTimerOn || FullMinutes <= FullMinutesTimerOff) ) 
{
lcd.setCursor(16, 2);
lcd.print("VKL1"); 
digitalWrite(RELE_1, LOW);
 // если сутки перескакивают, то проверяем время оператором ИЛИ
}
else if (sutki==1)
{
  lcd.print("    ");
    digitalWrite(RELE_1, HIGH);
}
  
  if (sutki == 0 && (FullMinutes >= FullMinutesTimerOn && FullMinutes <= FullMinutesTimerOff )) 
  {
    lcd.setCursor(16, 2);
    lcd.print("VKL0");
   digitalWrite(RELE_1, LOW);
  } // если сутки НЕ перескакивают, то проверяем время оператором И
  else if (sutki == 0) 
  {
    lcd.print("    ");
    digitalWrite(RELE_1, HIGH);
  }
  
  RTC.get(rtc,true);
  digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
  strokatimera();
           
if (rejim==0) 
  {
    lcd.setCursor(0, 2);
    lcd.print ("ojidayu komandi");
    }
     if (rejim==1) 
  {
    setTimerOn();
    }
  if (rejim==2) 
  {
      setTimerOff();
  }
  if (rejim==3) 
  {
      setTime();
    }

if (digitalRead (button5) == HIGH) 
{ 
  rejim++;
  if (rejim>3) rejim=0;
  lcd.clear();
  //}
}
  
}

void setTimerOn() 
{
  int hourOn= EEPROM.read(110); 
  int minOn= EEPROM.read(111); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VKL");
  
      if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     hourOn++;
    if (hourOn >=24) hourOn=0;
        }
   
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minOn++;
    if (minOn >=60) 
    {
      minOn=0;
      hourOn++;
      if (hourOn >=24) hourOn=0;
    }
    }
 strokatimera();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(110, hourOn);
      EEPROM.write(111, minOn);
      lcd.clear();
      }
    }

void setTimerOff() 
{
  int hourOff= EEPROM.read(112); 
  int minOff= EEPROM.read(113); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VIKL");
  
    if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     hourOff++;
    if (hourOff >=24) hourOff=0;
        }
   
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minOff++;
    if (minOff >=60) 
    {
      minOff=0;
      hourOff++;
      if (hourOff >=24) hourOff=0;
    }
    }
 strokatimera();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(112, hourOff);
      EEPROM.write(113, minOff);
      lcd.clear();
      }
}

void setTime()
{
  lcd.setCursor(0, 2);
  //lcd.print ("nastroika time_test");
   lcd.print ("pustaya nastroika");
}

void strokatimera()
{
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
    lcd.print(hourOn);
    lcd.print(":");
    lcd.print(minOn); 
    lcd.print("-");
    lcd.print(hourOff);
    lcd.print(":");
    lcd.print(minOff);
  }
 


А вот так это работает:



Для меня осталось загадкой, почему контроллер не хочет реагировать на нажатие отдельных кнопок изменения минут и часов, но охотно реагирует на нажатие другой кнопки, меняя минуты. Исходя из этого, допилил код до рабочего, хотя и индусского состояния, так как первый релиз таймера надо запускать в работу, а с остальным разбираться позже. Грешу на перегрев при пайке, хотя вывод на эти контакты стандартного Blink дает нормальное напряжение на пине. На данный момент пин D6 позволяет войти в меню изменения, а пин D9 листает минуты. Может кто подскажет, если сталкивался с подобным?

P.S. Уже после написания статьи я столкнулся с тем, что задачи, поставленные перед контроллером, надо расширять. Дело в том, что данный контроллер управляет электрическим котлом в дачном доме, а с ростом температуры стала повышаться влажность. Пришлось добавить в систему вентиляции канальный вентилятор аналогичный этому:

image

… и на второе реле повесить его включение. Опытным путем было установлено, что удачнее всего будет включать его каждый час на заданный промежуток времени. При этом включается он только с 9 до 21 часа, а ночью тепло из дома не выбрасывается. Регулировка времени включенного состояния также производится с кнопок, а вот время работы пока зашито в программе. На данный момент вытяжка включается на 10 минут каждый час. Оставлен задел на то, чтобы включать вытяжку автоматически, когда включается освещение в ванной или туалете.
Текущий код, который уверенно работает уже месяц, ниже:

Действующий код
// Date and time functions using a DS1307 RTC connected via I2C and Wire lib


#include <EEPROM.h>
#include <Wire.h>
#include <DS1307.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
//#define timePIN 10
#define RELE_1 12 //объявляем работу 1 реле на пине 7
#define RELE_2 11 //объявляем работу 2 реле на пине 8


//на пин A4 цепляем SDA монитора и SDA часов
//на пин A5 цепляем SCL монитора и SCL часов


int FullMinutesTimerOn = EEPROM.read(0);
int FullMinutesTimerOff = EEPROM.read(2); 
int rtc[7];
byte rr[7];
int ledPin =  13;
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display
int button1 = 10; //кнопка на пин D10
int button2 = 9; //кнопка на пин D9
int button3 = 8; //кнопка на пин D8
int button4 = 7; //кнопка на пин D7
int button5 = 6; //кнопка на пин D6
int led = 5;
int rejim=0; //переменная режима изменения настроек таймера
int PrevSec=0;
int CurSec=0;
boolean lastButton=LOW;


byte LT[8] = 
{


  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] =
{

  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100
};
byte MB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte block[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
// loop counter
int count = 0;



void setup () {
  DDRC|=_BV(2) |_BV(3);  // POWER:Vcc Gnd
  PORTC |=_BV(3);  // VCC PINC3
  pinMode(ledPin, OUTPUT);  
  //pinMode(timePIN, OUTPUT); 
  pinMode(RELE_1, OUTPUT); //инициируем реле только на выход  
  pinMode(RELE_2, OUTPUT); //инициируем реле только на выход
  digitalWrite(RELE_1, HIGH); // запускаем реле выключенными
  digitalWrite(RELE_2, HIGH); // запускаем реле выключенными 
  
  RTC.get(rtc,true);
  lcd.init();                      // initialize the lcd 
  lcd.backlight();
  lcd.home();

  lcd.createChar(0,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,MB);
  lcd.createChar(7,block);

  // sets the LCD's rows and colums:
  lcd.clear();  



  RTC.SetOutput(DS1307_SQW32KHZ);
  pinMode(led, OUTPUT);
  pinMode(button1, INPUT);
  pinMode(button2, INPUT);
  pinMode(button3, INPUT);
}



void custom0(int x)
{ // uses segments to build the number 0

  lcd.setCursor(x,0); // set cursor to column 0, line 0 (first row)
  lcd.write(0);  // call each segment to create
  lcd.write(1);  // top half of the number
  lcd.write(2);
  lcd.setCursor(x, 1); // set cursor to colum 0, line 1 (second row)
  lcd.write(3);  // call each segment to create
  lcd.write(4);  // bottom half of the number
  lcd.write(5);
}

void custom1(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(2);
  lcd.print(" ");
  lcd.setCursor(x,1);
  lcd.write(4);
  lcd.write(7);
  lcd.write(4);
}

void custom2(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(4);
}

void custom3(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5); 
}

void custom4(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(7);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom5(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void custom6(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom7(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom8(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom9(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);

}

void digitalClockDisplay(){
  // digital clock display of the time
  

  printDigits(rtc[2]/10,0); 
  printDigits(rtc[2]%10,4); 

  printDigits(rtc[1]/10,9);
  printDigits(rtc[1]%10,13);

   
  if (rtc[0]%10%2==0){
    lcd.setCursor(7, 0);
    lcd.print("+ ");
    lcd.setCursor(7, 1);
    lcd.print(" +");
    lcd.setCursor(3, 1);
    lcd.print("+");
    lcd.setCursor(12, 0);
    lcd.print("+");
    lcd.setCursor(3, 0);
    lcd.print(" ");
    lcd.setCursor(12, 1);
    lcd.print(" ");
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print(" +");
    lcd.setCursor(7, 1);
    lcd.print("+ ");
    lcd.setCursor(3, 0);
    lcd.print("+");
    lcd.setCursor(12, 1);
    lcd.print("+");
    lcd.setCursor(3, 1);
    lcd.print(" ");
    lcd.setCursor(12, 0);
    lcd.print(" ");
  }
  //нарисовали двоеточие

}



void printDigits(int digits, int x){
  // utility function for digital clock display: prints preceding colon and leading 0

  switch (digits) {
  case 0:  
    custom0(x);
    break;
  case 1:  
    custom1(x);
    break;
  case 2:  
    custom2(x);
    break;
  case 3:  
    custom3(x);
    break;
  case 4:  
    custom4(x);
    break;
  case 5:  
    custom5(x);
    break;
  case 6:  
    custom6(x);
    break;
  case 7:  
    custom7(x);
    break;
  case 8:  
    custom8(x);
    break;
  case 9:  
    custom9(x);
    break;

  }


}


void loop () {
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  int minVent= EEPROM.read(114); //время работы вентилятора
  
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1]; //присваиваем минуты
  int Second = rtc[0]; // присваиваем секунды
  int FullMinutes = Hour * 60 + Minute; //приводим значение текущего времени к минутам
  int FullMinutesTimerOn= hourOn*60+minOn; //приводим значение ВКЛЮЧЕНИЯ таймера к минутам
  int FullMinutesTimerOff= hourOff*60+minOff; //приводим значение ВЫКЛЮЧЕНИЯ таймера к минутам
  int sutki=0; // по умолчанию, таймер работает в одних сутках
  
  if (hourOff-hourOn < 0) sutki=1; //если время выключения на следующие сутки, то включаем триггер сутки=1
  else sutki=0;
    
  if (sutki==1 && (FullMinutes >= FullMinutesTimerOn || FullMinutes <= FullMinutesTimerOff) ) 
{
lcd.setCursor(16, 2);
lcd.print("VKL1"); 
digitalWrite(RELE_1, LOW);
 // если сутки перескакивают, то проверяем время оператором ИЛИ
}
else if (sutki==1)
{
  lcd.print("    ");
    digitalWrite(RELE_1, HIGH);
}
  
  if (sutki == 0 && (FullMinutes >= FullMinutesTimerOn && FullMinutes <= FullMinutesTimerOff )) 
  {
    lcd.setCursor(16, 2);
    lcd.print("VKL0");
   digitalWrite(RELE_1, LOW);
  } // если сутки НЕ перескакивают, то проверяем время оператором И
  else if (sutki == 0) 
  {
    lcd.print("    ");
    digitalWrite(RELE_1, HIGH);
  }
  
  if ((Minute >= 0 && Minute <= minVent) && (Hour >= 9 && Hour <= 21))
  {
    lcd.setCursor(17, 1);
    lcd.print("VEN");
    digitalWrite(RELE_2, LOW);
  }
  if (Minute >= 0 && Minute > minVent)
    {
    lcd.setCursor(17, 1);
    lcd.print("   ");
    digitalWrite(RELE_2, HIGH);
   }
  
  RTC.get(rtc,true);
  digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
  strokatimera();
           
if (rejim==0) 
  {
    lcd.setCursor(0, 2);
    lcd.print ("ojidayu komandi");
    }
     if (rejim==1) 
     {
      setTimerOn();
     }
   if (rejim==2) 
   {
      setTimerOff();
   }
  if (rejim==3) 
  {
      setTime();
    }
  if (rejim==4)
  {
  setVent();
  }  

if (digitalRead (button5) == HIGH) 
{ 
  rejim++;
  if (rejim>4) rejim=0;
  lcd.clear();
  //}
}
  
}

void setTimerOn() 
{
  int hourOn= EEPROM.read(110); 
  int minOn= EEPROM.read(111); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VKL");
  
      if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
      hourOn++;
      if (hourOn >=24) hourOn=0;
      }
   
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minOn++;
    if (minOn >=60) 
    {
      minOn=0;
      hourOn++;
      if (hourOn >=24) hourOn=0;
    }
    }
 strokatimera();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(110, hourOn);
      EEPROM.write(111, minOn);
      lcd.clear();
      }
    }

void setTimerOff() 
{
  int hourOff= EEPROM.read(112); 
  int minOff= EEPROM.read(113); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VIKL");
  
    if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
       hourOff++;
       if (hourOff >=24) hourOff=0;
      }
   
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minOff++;
    if (minOff >=60) 
    {
      minOff=0;
      hourOff++;
      if (hourOff >=24) hourOff=0;
     }
    }
 strokatimera();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(112, hourOff);
      EEPROM.write(113, minOff);
      lcd.clear();
      }
}

void setTime()
{
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1];
  //lcd.clear();
  lcd.setCursor(0, 2);
  lcd.print ("nastroika time");
  RTC.get(rtc,true);
  
  //lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
   // lcd.print(Hour);
   // lcd.print(":");
   // lcd.print(Minute);
  
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    Minute++;
    if (Minute >=60) 
    {
      Minute=0;
      Hour++;
      if (Hour >=24) Hour=0;
    }
   }
 
    if (digitalRead(button2) == HIGH)
    {
      RTC.set(DS1307_MIN,Minute);
      RTC.set(DS1307_HR,Hour);
      lcd.clear();
      }
  
   
}

void strokatimera()
{
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  int minVent= EEPROM.read(114);
  lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
    lcd.print(hourOn);
    lcd.print(":");
    lcd.print(minOn); 
    lcd.print("-");
    lcd.print(hourOff);
    lcd.print(":");
    lcd.print(minOff);
    lcd.print("  ");
    lcd.print("VENT:");
    lcd.print(minVent);
    
  }
    
void setVent()
{
  
  int minVent= EEPROM.read(114); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika Vent");
  
      if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minVent++;
    if (minVent >=60) 
    {
      minVent=0;
     }
    }
 
 strokatimera();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(114, minVent);
      lcd.clear();
     }
    }


В дальнейшем этот контроллер планируется подключить к OpenHab, чтобы удаленно наблюдать и управлять системой отопления и вентиляции дома.

Версия 1.1 прошивки, которая позволяет:
  • управлять котлом по заданному времени
  • управлять котлом по дням недели (new)
  • управлять системой вентиляции по времени (каждый час на заданное количество минут включается)
  • отображает текущий день и отопление в сегодняшнем дне (в правом верхнем углу экрана номер дня и + либо — режим включения сегодня


Добавлена работа по дням
// Date and time functions using a DS1307 RTC connected via I2C and Wire lib


#include <EEPROM.h>
#include <Wire.h>
#include <DS1307.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
//#define timePIN 10
#define RELE_1 12 //объявляем работу 1 реле на пине 7
#define RELE_2 11 //объявляем работу 2 реле на пине 8

//
//на пин A4 цепляем SDA монитора и SDA часов
//на пин A5 цепляем SCL монитора и SCL часов


int FullMinutesTimerOn = EEPROM.read(0);
int FullMinutesTimerOff = EEPROM.read(2); 
int rtc[7];
byte rr[7];
int ledPin =  13;
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display
int button1 = 10; //кнопка на пин D10
int button2 = 9; //кнопка на пин D9
int button3 = 8; //кнопка на пин D8
int button4 = 7; //кнопка на пин D7
int button5 = 6; //кнопка на пин D6
int led = 5;
int rejim=0; //переменная режима изменения настроек таймера
int i=1; //счетчик дней в меню настройки дней отопления
int PrevSec=0;
int CurSec=0;
int Day=0;
boolean lastButton=LOW;


byte LT[8] = 
{


  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] =
{

  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100
};
byte MB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte block[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
// loop counter
int count = 0;



void setup () {
  DDRC|=_BV(2) |_BV(3);  // POWER:Vcc Gnd
  PORTC |=_BV(3);  // VCC PINC3
  pinMode(ledPin, OUTPUT);  
  //pinMode(timePIN, OUTPUT); 
  pinMode(RELE_1, OUTPUT); //инициируем реле только на выход  
  pinMode(RELE_2, OUTPUT); //инициируем реле только на выход
  digitalWrite(RELE_1, HIGH); // запускаем реле выключенными
  digitalWrite(RELE_2, HIGH); // запускаем реле выключенными 
  
  RTC.get(rtc,true);
  lcd.init();                      // initialize the lcd 
  lcd.backlight();
  lcd.home();

  lcd.createChar(0,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,MB);
  lcd.createChar(7,block);

  // sets the LCD's rows and colums:
  lcd.clear();  
  RTC.SetOutput(DS1307_SQW32KHZ);
  pinMode(led, OUTPUT);
  pinMode(button1, INPUT);
  pinMode(button2, INPUT);
  pinMode(button3, INPUT);
}



void custom0(int x)
{ // uses segments to build the number 0

  lcd.setCursor(x,0); // set cursor to column 0, line 0 (first row)
  lcd.write(0);  // call each segment to create
  lcd.write(1);  // top half of the number
  lcd.write(2);
  lcd.setCursor(x, 1); // set cursor to colum 0, line 1 (second row)
  lcd.write(3);  // call each segment to create
  lcd.write(4);  // bottom half of the number
  lcd.write(5);
}

void custom1(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(2);
  lcd.print(" ");
  lcd.setCursor(x,1);
  lcd.write(4);
  lcd.write(7);
  lcd.write(4);
}

void custom2(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(4);
}

void custom3(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5); 
}

void custom4(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(7);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom5(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void custom6(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom7(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom8(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom9(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);

}

void digitalClockDisplay(){
  // digital clock display of the time
  printDigits(rtc[2]/10,0); 
  printDigits(rtc[2]%10,4); 
  printDigits(rtc[1]/10,9);
  printDigits(rtc[1]%10,13);

   
  if (rtc[0]%10%2==0){
    lcd.setCursor(7, 0);
    lcd.print("+ ");
    lcd.setCursor(7, 1);
    lcd.print(" +");
    lcd.setCursor(3, 1);
    lcd.print("+");
    lcd.setCursor(12, 0);
    lcd.print("+");
    lcd.setCursor(3, 0);
    lcd.print(" ");
    lcd.setCursor(12, 1);
    lcd.print(" ");
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print(" +");
    lcd.setCursor(7, 1);
    lcd.print("+ ");
    lcd.setCursor(3, 0);
    lcd.print("+");
    lcd.setCursor(12, 1);
    lcd.print("+");
    lcd.setCursor(3, 1);
    lcd.print(" ");
    lcd.setCursor(12, 0);
    lcd.print(" ");
  }
  //нарисовали двоеточие

}



void printDigits(int digits, int x){
  // utility function for digital clock display: prints preceding colon and leading 0

  switch (digits) {
  case 0:  
    custom0(x);
    break;
  case 1:  
    custom1(x);
    break;
  case 2:  
    custom2(x);
    break;
  case 3:  
    custom3(x);
    break;
  case 4:  
    custom4(x);
    break;
  case 5:  
    custom5(x);
    break;
  case 6:  
    custom6(x);
    break;
  case 7:  
    custom7(x);
    break;
  case 8:  
    custom8(x);
    break;
  case 9:  
    custom9(x);
    break;

  }


}


void loop () {
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  int minVent= EEPROM.read(114); //время работы вентилятора
  int rejimDay=0;
  int Day = rtc[3]; //присваиваем день
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1]; //присваиваем минуты
  int Second = rtc[0]; // присваиваем секунды
  int FullMinutes = Hour * 60 + Minute; //приводим значение текущего времени к минутам
  int FullMinutesTimerOn= hourOn*60+minOn; //приводим значение ВКЛЮЧЕНИЯ таймера к минутам
  int FullMinutesTimerOff= hourOff*60+minOff; //приводим значение ВЫКЛЮЧЕНИЯ таймера к минутам
  int sutki=0; // по умолчанию, таймер работает в одних сутках
  
  lcd.setCursor(16, 0);
  lcd.print(Day); 
  
  if (hourOff-hourOn < 0) sutki=1; //если время выключения на следующие сутки, то включаем триггер сутки=1
  else sutki=0;
  
  if (EEPROM.read(114+Day)==1 && sutki==1)
  {
    rejimDay=1;
    lcd.print("+");
  }
  else
  {
    rejimDay=0;
    lcd.print("-");
  }
  
    
  if (sutki==1 && (FullMinutes >= FullMinutesTimerOn || FullMinutes <= FullMinutesTimerOff) ) 
{
lcd.setCursor(16, 2);
lcd.print("VKL"); 
digitalWrite(RELE_1, LOW);
 // если сутки перескакивают, то проверяем время оператором ИЛИ
}
else if (sutki==1)
{
  lcd.print("   ");
    digitalWrite(RELE_1, HIGH);
}
  
  if (sutki == 0 && (FullMinutes >= FullMinutesTimerOn && FullMinutes <= FullMinutesTimerOff )) 
  {
    lcd.setCursor(16, 2);
    lcd.print("VKL");
   digitalWrite(RELE_1, LOW);
  } // если сутки НЕ перескакивают, то проверяем время оператором И
  else if (sutki == 0) 
  {
    lcd.print("   ");
    digitalWrite(RELE_1, HIGH);
  }
  
  if ((Minute >= 0 && Minute <= minVent) && (Hour >= 9 && Hour <= 21))
  {
    lcd.setCursor(17, 1);
    lcd.print("VEN");
    digitalWrite(RELE_2, LOW);
  }
  if (Minute >= 0 && Minute > minVent)
    {
    lcd.setCursor(17, 1);
    lcd.print("   ");
    digitalWrite(RELE_2, HIGH);
  }
  
  RTC.get(rtc,true);

           
  if (rejim==0) 
    {
    lcd.setCursor(0, 2);
    lcd.print ("ojidayu komandi");
    strokatimera();
    digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
    }
   if (rejim==1) 
   {
    setTimerOn();
   }
  if (rejim==2) 
  {
      setTimerOff();
  }
  if (rejim==3) 
  {
      setTime();
    }
  if (rejim==4)
  {
  setVent();
  }  
  if (rejim==5)
  {
  setTimerDay1();
  } 
  

if (digitalRead (button5) == HIGH) 
{ 
  rejim++;
  delay(100);
  if (rejim>5) rejim=0;
  lcd.clear();
  //}
}
  
}

void setTimerOn() 
{
  int hourOn= EEPROM.read(110); 
  int minOn= EEPROM.read(111); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VKL");
  
      if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     hourOn++;
    if (hourOn >=24) hourOn=0;
        }
   
  if (digitalRead(button2)==HIGH) //нажимая нижнюю кнопку меняем минуты
  {
    minOn++;
    if (minOn >=60) 
    {
      minOn=0;
      hourOn++;
      if (hourOn >=24) hourOn=0;
    }
    }
 strokatimera();
 digitalClockDisplay();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(110, hourOn);
      EEPROM.write(111, minOn);
      lcd.clear();
      }
    }

void setTimerOff() 
{
  int hourOff= EEPROM.read(112); 
  int minOff= EEPROM.read(113); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VIKL");
  
    if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     hourOff++;
    if (hourOff >=24) hourOff=0;
        }
   
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minOff++;
    if (minOff >=60) 
    {
      minOff=0;
      hourOff++;
      if (hourOff >=24) hourOff=0;
    }
    }
 strokatimera();
 digitalClockDisplay();
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(112, hourOff);
      EEPROM.write(113, minOff);
      lcd.clear();
      }
}

void setTime()
{
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1];
  //lcd.clear();
  lcd.setCursor(0, 2);
  lcd.print ("nastroika time");
  RTC.get(rtc,true);
  digitalClockDisplay();
  //lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
   // lcd.print(Hour);
   // lcd.print(":");
   // lcd.print(Minute);
  
  if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    Minute++;
    if (Minute >=60) 
    {
      Minute=0;
      Hour++;
      if (Hour >=24) Hour=0;
    }
    }
 
    if (digitalRead(button2) == HIGH)
    {
      RTC.set(DS1307_MIN,Minute);
      RTC.set(DS1307_HR,Hour);
      lcd.clear();
      }
  
   
}

void strokatimera()
{
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  int minVent= EEPROM.read(114);
  lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
    lcd.print(hourOn);
    lcd.print(":");
    lcd.print(minOn); 
    lcd.print("-");
    lcd.print(hourOff);
    lcd.print(":");
    lcd.print(minOff);
    lcd.print("  ");
    lcd.print("VENT:");
    lcd.print(minVent);
    
  }
    
void setVent()
{
  strokatimera();
  int minVent= EEPROM.read(114); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika Vent");
  
      if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minVent++;
    if (minVent >=60) 
    {
      minVent=0;
     }
   }
 
 digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(114, minVent);
      lcd.clear();
    }
}
void setTimerDay1()
{
  
  int z;
  //lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print ("nastroika dney");
  //lcd.setCursor(0, 1);
    
   lcd.setCursor (0,3);
   lcd.print (EEPROM.read(115));
   lcd.print (" ");
   lcd.print (EEPROM.read(116));
   lcd.print (" ");
   lcd.print (EEPROM.read(117));
   lcd.print (" ");
   lcd.print (EEPROM.read(118));
   lcd.print (" ");
   lcd.print (EEPROM.read(119));
   lcd.print (" ");
   lcd.print (EEPROM.read(120));
   lcd.print (" ");
   lcd.print (EEPROM.read(121));
   lcd.print (" "); 
  
  //lcd.clear();
      
    z=EEPROM.read(114+i); // присваеваем текущему дню состояние вкл/выкл из памяти
    if (i==1)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("ponedelnik");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==2)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("vtornik");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==3)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("sreda");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==4)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("chetverg");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==5)
    {
      lcd.setCursor(1, 2);
      lcd.print ("pyatnica");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==6)
    {
      lcd.setCursor(1, 2);
      lcd.print ("subbota");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==7)
    {
      lcd.setCursor(1, 2);
      lcd.print ("voskresen'e");
      lcd.print (" ");
      lcd.print (z);
    }
   
    if (digitalRead(button3)==HIGH) //переключаем дни недели
    {
      lcd.clear();
     i++;
     delay(150);
     if  (i>7) {i=1;}
    }
    
    if (digitalRead(button2)==HIGH)
    {
     delay(150);
     z=!z;
     if (z!= EEPROM.read(114+i))
      {
        EEPROM.write(114+i,z);
      }
    }
    
   
}     



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

Добавлено отключение вытяжки
// Date and time functions using a DS1307 RTC connected via I2C and Wire lib


#include <EEPROM.h>
#include <Wire.h>
#include <DS1307.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
//#define timePIN 10
#define RELE_1 11 //поменяли местами реле котла и вентиляции
#define RELE_2 12 //сейчас выключает вентиляцию, правое реле


//
//на пин A4 цепляем SDA монитора и SDA часов
//на пин A5 цепляем SCL монитора и SCL часов


int FullMinutesTimerOn = EEPROM.read(0);
int FullMinutesTimerOff = EEPROM.read(2); 
int rtc[7];
byte rr[7];
int ledPin =  13;
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display
int button1 = 10; //кнопка на пин D10
int button2 = 9; //кнопка на пин D9
int button3 = 8; //кнопка на пин D8
int button4 = 7; //кнопка на пин D7
int button5 = 6; //кнопка на пин D6
int vanna = 2; //вход с ванны на пин D2
int led = 5;
int rejim=0; //переменная режима изменения настроек таймера
int i=1; //счетчик дней в меню настройки дней отопления
int PrevSec=0;
int CurSec=0;
int Day=0;
int vannatimer=0;
boolean lastButton=LOW;


byte LT[8] = 
{


  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] =
{

  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] =
{


  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100
};
byte MB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte block[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
// loop counter
int count = 0;



void setup () {
  DDRC|=_BV(2) |_BV(3);  // POWER:Vcc Gnd
  PORTC |=_BV(3);  // VCC PINC3
  pinMode(ledPin, OUTPUT);  
  //pinMode(timePIN, OUTPUT); 
  pinMode(RELE_1, OUTPUT); //инициируем реле только на выход  
  pinMode(RELE_2, OUTPUT); //инициируем реле только на выход
  digitalWrite(RELE_1, HIGH); // запускаем реле выключенными
  digitalWrite(RELE_2, HIGH); // запускаем реле выключенными
  
  
  RTC.get(rtc,true);
  lcd.init();                      // initialize the lcd 
  lcd.backlight();
  lcd.home();

  lcd.createChar(0,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,MB);
  lcd.createChar(7,block);

  // sets the LCD's rows and colums:
  lcd.clear();  
  RTC.SetOutput(DS1307_SQW32KHZ);
  pinMode(led, OUTPUT);
  pinMode(button1, INPUT);
  pinMode(button2, INPUT);
  pinMode(button3, INPUT);
  pinMode(vanna, INPUT); // инициируем вход из ванной-туалета
}



void custom0(int x)
{ // uses segments to build the number 0

  lcd.setCursor(x,0); // set cursor to column 0, line 0 (first row)
  lcd.write(0);  // call each segment to create
  lcd.write(1);  // top half of the number
  lcd.write(2);
  lcd.setCursor(x, 1); // set cursor to colum 0, line 1 (second row)
  lcd.write(3);  // call each segment to create
  lcd.write(4);  // bottom half of the number
  lcd.write(5);
}

void custom1(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(2);
  lcd.print(" ");
  lcd.setCursor(x,1);
  lcd.write(4);
  lcd.write(7);
  lcd.write(4);
}

void custom2(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(4);
}

void custom3(int x)
{
  lcd.setCursor(x,0);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5); 
}

void custom4(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(7);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom5(int x)
{
  lcd.setCursor(x,0);
  lcd.write(3);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void custom6(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom7(int x)
{
  lcd.setCursor(x,0);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);
}

void custom8(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
}

void custom9(int x)
{
  lcd.setCursor(x,0);
  lcd.write(0);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 1);
  lcd.print(" ");
  lcd.print(" ");
  lcd.write(7);

}

void digitalClockDisplay(){
  // digital clock display of the time
  printDigits(rtc[2]/10,0); 
  printDigits(rtc[2]%10,4); 
  printDigits(rtc[1]/10,9);
  printDigits(rtc[1]%10,13);

   
  if (rtc[0]%10%2==0){
    lcd.setCursor(7, 0);
    lcd.print("+ ");
    lcd.setCursor(7, 1);
    lcd.print(" +");
    lcd.setCursor(3, 1);
    lcd.print("+");
    lcd.setCursor(12, 0);
    lcd.print("+");
    lcd.setCursor(3, 0);
    lcd.print(" ");
    lcd.setCursor(12, 1);
    lcd.print(" ");
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print(" +");
    lcd.setCursor(7, 1);
    lcd.print("+ ");
    lcd.setCursor(3, 0);
    lcd.print("+");
    lcd.setCursor(12, 1);
    lcd.print("+");
    lcd.setCursor(3, 1);
    lcd.print(" ");
    lcd.setCursor(12, 0);
    lcd.print(" ");
  }
  //нарисовали двоеточие

}



void printDigits(int digits, int x){
  // utility function for digital clock display: prints preceding colon and leading 0

  switch (digits) {
  case 0:  
    custom0(x);
    break;
  case 1:  
    custom1(x);
    break;
  case 2:  
    custom2(x);
    break;
  case 3:  
    custom3(x);
    break;
  case 4:  
    custom4(x);
    break;
  case 5:  
    custom5(x);
    break;
  case 6:  
    custom6(x);
    break;
  case 7:  
    custom7(x);
    break;
  case 8:  
    custom8(x);
    break;
  case 9:  
    custom9(x);
    break;

  }


}


void loop () {
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  int minVent= EEPROM.read(114); //время работы вентилятора
  int Vent= EEPROM.read(122);
  int rejimDay=0;
  int Day = rtc[3]; //присваиваем день
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1]; //присваиваем минуты
  int Second = rtc[0]; // присваиваем секунды
  int FullMinutes = Hour * 60 + Minute; //приводим значение текущего времени к минутам
  int FullMinutesTimerOn= hourOn*60+minOn; //приводим значение ВКЛЮЧЕНИЯ таймера к минутам
  int FullMinutesTimerOff= hourOff*60+minOff; //приводим значение ВЫКЛЮЧЕНИЯ таймера к минутам
  int sutki=0; // по умолчанию, таймер работает в одних сутках
  
  //if (digitalRead (vanna) == HIGH) // пока свет в ванной или в туалете горит включается вентилятор
  //{
  // digitalWrite(RELE_2, LOW);
   //digitalWrite(ledPin, HIGH);
  // vannatimer=1;
  //}
  
  lcd.setCursor(16, 0);
  lcd.print(Day); 
  
  if (hourOff-hourOn < 0) sutki=1; //если время выключения на следующие сутки, то включаем триггер сутки=1
  else sutki=0;
  
  if (EEPROM.read(114+Day)==1 && sutki==1)
  {
    rejimDay=1;
    lcd.print("+");
  }
  else
  {
    rejimDay=0;
    lcd.print("-");
  }
  
    
  if ((sutki==1 && rejimDay==1) && (FullMinutes >= FullMinutesTimerOn || FullMinutes <= FullMinutesTimerOff) ) 
{
lcd.setCursor(16, 2);
lcd.print("VKL"); 
digitalWrite(RELE_1, LOW);
 // если сутки перескакивают, то проверяем время оператором ИЛИ
}
else if (sutki==1)
{
  lcd.print("   ");
    digitalWrite(RELE_1, HIGH);
}
  
  if (sutki == 0 && (FullMinutes >= FullMinutesTimerOn && FullMinutes <= FullMinutesTimerOff )) 
  {
    lcd.setCursor(16, 2);
    lcd.print("VKL");
   digitalWrite(RELE_1, LOW);
  } // если сутки НЕ перескакивают, то проверяем время оператором И
  else if (sutki == 0) 
  {
    lcd.print("   ");
    digitalWrite(RELE_1, HIGH);
  }
  
  if (((Minute >= 0 && Minute <= minVent) && (Hour >= 9 && Hour <= 21))||(digitalRead (vanna) == HIGH))
  {
    if (Vent==1)
    {
    lcd.setCursor(17, 1);
    lcd.print("VEN");
    digitalWrite(RELE_2, LOW);
    }
  }
  if ((Minute >= 0 && Minute > minVent || digitalRead (vanna) == LOW)&&(Vent==0))
    {
    lcd.setCursor(17, 1);
    lcd.print("   ");
    digitalWrite(RELE_2, HIGH);
  }
  if (digitalRead (vanna) == HIGH) digitalWrite(RELE_2, LOW); //по включению света в ванной и таулете вытяжка включается всегда
  
  RTC.get(rtc,true);

           
  if (rejim==0) 
    {
    lcd.setCursor(0, 2);
    lcd.print ("ojidayu komandi");
    strokatimera();
    digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
    }
   if (rejim==1) 
   {
    setTimerOn();
   }
  if (rejim==2) 
  {
      setTimerOff();
  }
  if (rejim==3) 
  {
      setTime();
    }
  if (rejim==4)
  {
  setVent();
  }  
  
  if (rejim==5)
  {
  setVentOnOff();
  } 
  if (rejim==6)
  {
  setTimerDay1();
  } 
  

if (digitalRead (button5) == HIGH) 
{ 
  rejim++;
  delay(100);
  if (rejim>6) rejim=0;
  lcd.clear();
  //}
}
  
}

void setTimerOn() 
{
  int hourOn= EEPROM.read(110); 
  int minOn= EEPROM.read(111); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VKL");
  
      if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     hourOn++;
    if (hourOn >=24) hourOn=0;
        }
   
  if (digitalRead(button2)==HIGH) //нажимая нижнюю кнопку меняем минуты
  {
    minOn++;
    if (minOn >=60) 
    {
      minOn=0;
      hourOn++;
      if (hourOn >=24) hourOn=0;
    }
    }
 strokatimera();
 digitalClockDisplay();
    if ((digitalRead(button2) == HIGH)||(digitalRead(button3) == HIGH))
    {
      EEPROM.write(110, hourOn);
      EEPROM.write(111, minOn);
      lcd.clear();
      }
    }

void setTimerOff() 
{
  int hourOff= EEPROM.read(112); 
  int minOff= EEPROM.read(113); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika VIKL");
  
    if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     hourOff++;
    if (hourOff >=24) hourOff=0;
        }
   
  if (digitalRead(button2)==HIGH) //нажимая нижнюю кнопку меняем минуты
  {
    minOff++;
    if (minOff >=60) 
    {
      minOff=0;
      hourOff++;
      if (hourOff >=24) hourOff=0;
    }
    }
 strokatimera();
 digitalClockDisplay();
    if ((digitalRead(button2) == HIGH)||(digitalRead(button3) == HIGH))
    {
      EEPROM.write(112, hourOff);
      EEPROM.write(113, minOff);
      lcd.clear();
      }
}

void setTime()
{
  int Hour = rtc[2]; //присваиваем часы
  int Minute = rtc[1];
  //lcd.clear();
  lcd.setCursor(0, 2);
  lcd.print ("nastroika time");
  RTC.get(rtc,true);
  digitalClockDisplay();
  //lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
   // lcd.print(Hour);
   // lcd.print(":");
   // lcd.print(Minute);
  
  if (digitalRead(button2)==HIGH) //нажимая нижнюю кнопку меняем минуты
  {
    Minute++;
    if (Minute >=60) 
    {
      Minute=0;
      Hour++;
      if (Hour >=24) Hour=0;
    }
    }
    if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
     {  
     Hour++;
    if (Hour >=24) Hour=0;
        }
 
    if ((digitalRead(button2) == HIGH)||(digitalRead(button3) == HIGH))
    {
      RTC.set(DS1307_MIN,Minute);
      RTC.set(DS1307_HR,Hour);
      lcd.clear();
      }
  
   
}

void strokatimera()
{
  int hourOn=EEPROM.read(110); //EEPROM.read(110)
  int hourOff= EEPROM.read(112);; //EEPROM.read(112)
  int minOn= EEPROM.read(111);; //EEPROM.read(111)
  int minOff= EEPROM.read(113);; //EEPROM.read(113)
  int minVent= EEPROM.read(114);
  lcd.setCursor(0, 3); // вывод в последней строчке времени работы таймера
    lcd.print(hourOn);
    lcd.print(":");
    lcd.print(minOn); 
    lcd.print("-");
    lcd.print(hourOff);
    lcd.print(":");
    lcd.print(minOff);
    lcd.print("  ");
    lcd.print("VENT:");
    lcd.print(minVent);
    
  }
    
void setVent()
{
  strokatimera();
  int minVent= EEPROM.read(114); 
  lcd.setCursor(0, 2);
  lcd.print("nastroika Vent");
  
      if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем минуты
  {
    minVent++;
    if (minVent >=60) 
    {
      minVent=0;
     }
   }
 
 digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(114, minVent);
      lcd.clear();
    }
}




void setVentOnOff()
{
  strokatimera();
  int Vent= EEPROM.read(122); 
  lcd.setCursor(0, 2);
  lcd.print("Vent");
  if (Vent==0) lcd.print(" Off");
  if (Vent==1) lcd.print(" On");
      if (!digitalRead(button4)) //нажимая нижнюю кнопку меняем статус вкл откл
  {
    delay(100);
    Vent++;
    if (Vent >=2) 
    {
      Vent=0;
     }
   }
 
 digitalClockDisplay(); //выводим красивенькие часики на 2 строки 
    if (digitalRead(button2) == HIGH)
    {
      EEPROM.write(122, Vent);
      lcd.clear();
    }
    

}



void setTimerDay1()
{
  
  int z;
  //lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print ("nastroika dney");
  //lcd.setCursor(0, 1);
    
   lcd.setCursor (0,3);
   lcd.print (EEPROM.read(115));
   lcd.print (" ");
   lcd.print (EEPROM.read(116));
   lcd.print (" ");
   lcd.print (EEPROM.read(117));
   lcd.print (" ");
   lcd.print (EEPROM.read(118));
   lcd.print (" ");
   lcd.print (EEPROM.read(119));
   lcd.print (" ");
   lcd.print (EEPROM.read(120));
   lcd.print (" ");
   lcd.print (EEPROM.read(121));
   lcd.print (" "); 
  
  //lcd.clear();
      
    z=EEPROM.read(114+i); // присваеваем текущему дню состояние вкл/выкл из памяти
    if (i==1)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("ponedelnik");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==2)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("vtornik");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==3)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("sreda");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==4)
    {
      lcd.setCursor(1, 2); 
      lcd.print ("chetverg");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==5)
    {
      lcd.setCursor(1, 2);
      lcd.print ("pyatnica");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==6)
    {
      lcd.setCursor(1, 2);
      lcd.print ("subbota");
      lcd.print (" ");
      lcd.print (z);
    }
    if (i==7)
    {
      lcd.setCursor(1, 2);
      lcd.print ("voskresen'e");
      lcd.print (" ");
      lcd.print (z);
    }
   
    if (digitalRead(button3)==HIGH) //переключаем дни недели
    {
      lcd.clear();
     i++;
     delay(150);
     if  (i>7) {i=1;}
    }
    
    if (digitalRead(button2)==HIGH)
    {
     delay(150);
     z=!z;
     if (z!= EEPROM.read(114+i))
      {
        EEPROM.write(114+i,z);
      }
    }
    
    
    //EEPROM.write(115, 0);//понедельник
    //EEPROM.write(116, 0);//вторник
    //EEPROM.write(117, 1);//среда
    //EEPROM.write(118, 0);//четверг
    //EEPROM.write(119, 1);//пятница
    //EEPROM.write(120, 1);//суббота
    //EEPROM.write(121, 0);//воскресенье
      
     //z=EEPROM.read(114+i);
}     

Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 76
  • 0
    Делал чуть менее года назад четырехканальный таймер для управления освещением птичьих клеток. Логика немного другая — просто у каждого канала есть один интервал включения в сутки. Возможно, автору будет интересно ознакомиться, т.к. использованное железо похоже. github.com/eta4ever/arduino_timer
    • 0
      Насколько я понял, Вы с «дребезгом» кнопок справились засекая время удержания от 10 до 300 мс?
      А есть видео управления контроллером? Хотелось бы на будущее использовать Вашу наработку для автоматизации полива в теплице.
      • +1
        Про дребезг — верно. Видео, к сожалению, нет. Попробую пантомимой.

        Из дежурного экрана: нажатие кнопки Set (самая левая) отправляет в настройку даты-времени. Кнопки влево-вправо-вверх-вниз — в настройку интервала включения каждого из четырех каналов.
        image

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

        Интервалы включения пишутся в EEPROM. RTC с батарейкой.

        По результатам эксплуатации (скоро год) отмечу: пару раз система зависала при непосредственной близости слабого электрического разряда (втыкание зарядки ноутбучной в розетку). Модуль RTC уныл, убегает вперед на несколько минут в месяц. В остальном — я доволен.

        • 0
          Модуль RTC аналогичен моему? Я сделал запас в 5 минут, чтобы не перепрыгнуть в другой тариф.
          Также возникала мысль по поводу количества операций чтения\записи в EEPROM, которое указано в даташите в количество 100 000 операций. ТУТ провели тест и получили больше 6 млн операций чтения\записи в одну ячейку.
          Я задумался: у меня ведь каждый цикл происходит чтение ячеек, в которых хранится запись. А как узнать время одного цикла, чтобы примерно рассчитать, через сколько ячейки занятые дадут сбой?
          • 0
            RTC очень похож. Какой-то очень китайский TinyRTC. Время подвожу примерно раз в месяц-два.
            Если я правильно помню, чтение флеш-ячейки практически не влияет на ее ресурс, только запись.
            Ну и, у меня какая-то работа с флешем происходит только при загрузке контроллера и установке параметров. Так что, не парюсь.

            • +1
              Есть просто отличная идея, так сказать костыльная. Повесь на таймер еще одну задачу — каждые сутки в заданное время(когда удобней всего) откручивай время на нужную величину, чтобы скорость ухода за месяц компенсировать.
              • 0
                А вот это, я бы сказал, гениально. Костыльно, но гениально. А я не догадался. Надо запилить.
                • 0
                  Чего только люди не делают, лишь бы не ставить нормальный кварц за 25 рублей :-D

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

                    Один умный сисадмин сказал: Работает? Не трогай! (с) народное
                    • 0
                      Добавлять костыльный код в прошивку — это как-то более стрёмно (в программной коррекции огромное раздолье для ошибок), чем выпаять один кварц и впаять другой.
                      • 0
                        А другой тоже надо будет корректировать, их точность изначально достаточна только для +-30 сек в месяц, при условии термостатирования. Для лучшей точности надо корректировать либо программно либо подбором опорной емкости(тут уже частотомер нужен с 7-8 значащими разрядами).
                        • 0
                          Ну если нужна суперточность, то проще поставить GPS модуль с хорошей антенной и не париться с калибровкой :-)

                          • 0
                            Вот только намаятся с подключением GPS модуля придется еще больше, и в особо пасмурные дни даже хорошая антенна не поможет — это всё факторы риска.

                            Лучше уж локальные часы с термостатированым кварцем, прописать коррекцию времени в коде куда проще чем приклеить к схеме GPS-модуль. Надо будет только раз в год предупреждать старение кварца.
                    • 0
                      Проблема же в том что при этом переделывать все надо. И не факт что саму микросхему не уронят в процессе транспортировки или эксплуатации и у неё тоже точность поплывёт.
                    • 0
                      Вот жеж блин!
                  • +2
                    100 000 операций записи/стирания. Читать можно сколько угодно.
                    arduino.cc/en/Reference/EEPROMWrite

                    • 0
                      Спасибо. Значит, в моем случае, контроллер окажется почти вечным. Думаю, что раньше релюшки помрут. Хотя, если у них ресурс 10 000, то выходит, что при моем режиме работы они проработают 10000 \8(раз в день) = 1250 дней или почти 3.5 года, а если учесть увеличение частоты включения, когда пойдет привязка к включению света в ванной, то и того раньше. Хорошо, что я подключил реле без пайки.
                      • 0
                        Если что — можно добавить карту памяти и писать на неё.
                        • 0
                          Была такая мысль, но хранить надо всего 7 значений, а ради этого громоздить дополнительный шилд и накладно и громоздко. В этом случае можно воспользоваться памятью, встроенной в модуль RTC. Если не ошибаюсь, там 54 байта энергонезависимой памяти.
                          • 0
                            А зачем шилд? Вот такая фигня стоит всего полтора доллара с доставкой. Там же практически напрямую припаяться к карте можно.
                            image
                            • +2
                              Я не жадный, я экономный :) Полтора бакса модуль, плюс столько же за флешку ради 7 байт данных. Если бы они записывались с частотой каждую минуту или секунду-имело бы смысл. Такой модуль у меня есть и я думаю его использовать в дальнейшем, когда буду вести учет добытой от солнечных батарей энергии.
                        • 0
                          давным давно, если видели, по стране стояли т.н. столбы — 5-ти рублевые игровые автоматы. В них использовалась eeprom для рассчёта вероятности и хранения ключевой информации. так вот. чтобы повысить ресурс записи — в eeprom писалось по кругу во все ячейки. т.е. номер записи, сама запись. следующая запись — в следующую ячейку.
                          • 0
                            Такое циклирования действительно продлит время службы, но код становится более запутанным и увидеть в самом коде, куда пишется то или иное значение в данный момент времени затруднительно.
                            Как выяснилось, при текущем коде, время жизни ячеек практически бесконечно.
                            • 0
                              согласен. если нет задачи частой записи, то нет и смысла усложнять код
                    • +1
                      Скорей всего батарейка села. Если на свежей уходит, значит менять кварц на 32768Гц который, скорей всего он «удареный», они очень сильно бояться ударов.
                      • 0
                        Батарейка живая. Может быть, дело действительно в кварце. Перепаивать не полезу, лучше действительно замеряю уход и сделаю программную коррекцию.
                        • 0
                          И если энергетические ресурсы позволяют, неплохо было бы термостатировать кварц, так можно выиграть еще один порядок точности, дальше уже от старения зависеть будет. И в виду повышения точности ввести еще дополнительную ежемесячную коррекцию, может тогда за время жизни батарейки вообще подстраивать не придется, правда проблемы будут с быстрым определением этого коэффициента — нужен будет источник точного времени уровня STRATUM-2 или 3. Подстраиваться по другим источникам точного времени может выйти боком, они хоть и точные в пределах года но в разное время показания могут отличаться довольно серьёзно. По телевизору и радио — так там вообще точность +-30 секунд, как и у стандартного сервиса синхронизации времени в виндовс.
                          Лучше всего брать за источник времени GPS, когда он надёжно видит не менее 6 спутников.
                          • 0
                            Мне плюс-минус 10 минут погоды не делают, так что ни NTP, ни GPS тут не надо :) В критичных приложениях, понятно, все будет по-другому.
                            • 0
                              Это нужно будет для того чтобы максимально точно определить необходимую коррекцию, иначе смысл? все равно будут со временем расходится.
                              • 0
                                Коэффициент точности времени применим в тех случаях, когда от этого зависит надежность работы самой системы или есть жесткая необходимость в соблюдении времени. К примеру, у меня в проекте необходимо выдерживать время, чтобы не перескочить в дорогой тариф на электроэнергию. Но я обеспечил это простым выставлением таймера на 10 минут от времени включения тарифа. Если это будет теплица или подсветка клеток, то критичность к точности времени снижается. Ведь какая раница, будут политы помидоры в 16.00 или в 16.20. Понятно, что их надо поливать в 16 часов, а не в 20, но порядок точности не обязывает обеспечить привязку к атомным часам.
                                • 0
                                  Не привязку к атомным часам, а измерить ТОЧНО скорость хода твоих часов, чтобы правильно их компенсировать за небольшой промежуток времени(неделю) не прибегая к спецоборудованию. Если будешь измерять точность хода по сигналам точного времени по радио, к примеру, или телевизору — можешь сильно ошибиться в расчете коррекции.
                                  Конечно, можно измерить скорость хода и с помощью сигналов по радио/телевизору, но для достижения приемлемой точности в +-1 минуту в год нужно измерять уход времени за пол года.
                                  После этого никакая привязка к источникам точного времени не нужна.
                                  • 0
                                    Теперь понял. Я подумал, что речь о непосредственном припиливании к таймеру источника сигнала точного времени (что тоже не самая плохая идея).
                      • 0
                        Против зависания надо обязательно применять watchdog. Только проблема в том, что в Arduino Nano надо прошивать альтернативный загрузчик, чтобы watchdog корректно работал…
                  • +1
                    Есть два типа реле — твердотельные и обычные. Первые от большого тока перегреваются, а у вторых пригорают контакты. Я сам сталкивался с такой проблемой, особенно если потребитель какой-то мотор (с большим пусковым током) или тен. Реле вроде по номиналу и проходит (даже с запасом) но месяца через 3-4 умирает. Может и есть какие-то «золотые» реле с неубиваемыми контактами.

                    Решения я так и не нашел. Мысль: комбинировать два реле одновременно не покидает меня. Сначала включать твердотельное, потом замыкать контакты. И обратной в той же последовательности.
                    Свою же задачу решил, нагромоздив каскад из двух реле: первое замыкает цепь питания второго доброго «бабушкиного» советского реле с рынка, у которого 15 кратный запас мощности — работает.

                    А вы столкнулись уже с этим эффектом?
                    • 0
                      Наверное Вы хотели сказать, что замыкаем сначала обычные контакты, потом твердотельное реле. И размыкаем сначала твердотельное, а потом обычное, чтобы не возникала дуга?
                      В моем случае, первое реле замыкает слаботочную цепь с напряжением до 10 В и током в миллиамперы, а второе реле замыкает цепь 220В и запускает двигатель мощностью 550 Вт, то есть ток протекает порядка 2.5 А. Стартовый ток движка наверняка в районе 5х, так что 10-12 А замыкается реле. На размыкание ток уже гораздо меньше и дуги возникнуть не должно. Месяц испытания прошел успешно и система доказала жизнеспособность. Вскоре будет щелкать чаще и будет ясно, насколько это надежное решение.
                      • 0
                        Нет, как раз твердотельные работают без искры, но они сильно греются, по-этому оно и включается первым, а потом уже при замкнутой цепи дополнительно (параллельно) замыкается цепь через обычное реле. Потом твердотельное можно отключить или оставить — разницы особой нет. (Твердотельное реле все равно обладает каким-то малым сопротивлением, а у обычного реле — сопротивление близко к нулю). Выключается все в обратной последовательности: сначала размыкаем контакты, потом отключаем твердотельник.

                        Кстати, у меня реле «пригорело» в замкнутом состоянии — было неприятно :))
                        • 0
                          Ну раз реле установлены параллельно, то идея понятна. Только дублирование все-равно накладно. Проще обеспечить твердотельники охлаждением — взять радиатор от старого процессорного кулера.
                          У меня если и пригорит, то просто будет работать вытяжка без остановки-не критично.
                          А Вы какие токи коммутируете?
                          • 0
                            У меня было реле на 25А, то есть это 5500 Вт мощность, на нем было освещение около 3 кВт. Выходит, что у меня был почти двукратный запас по мощности. Когда первый раз сломалось, я подумал — брак и заменил реле. Новое, проработав все те же месяца 3-4, перестало работать. (Это я все про механические реле пишу)
                            • 0
                              Они как правило рассчитаны на номинальный ток во время работы, но не предназначены для коммутации цепей под нагрузкой. т.е. конструкция держит номинальный ток, а непосредственно контактные площадки — нет.
                              В таком случае, нужны более серьезные реле с нормальными контактами.
                      • 0
                        Что касается твердотельных реле, то на форуме по солнечной энергетике уже сталкивался с этим вопросом: твердотельные реле куда более надежные на замыкание нагруженных цепей, позволяют избежать искры при коммутации, но греются — необходим хотя бы пассивный радиатор, чтобы продлить срок службы устройства.
                        • 0
                          У вас, судя по всему, индуктивная нагрузка (электродвигатель). При размыкании такой нагрузки индуктивность стремится поддерживать ток, в результате возникает бросок напряжения, величина которого может достигать нескольких киловольт и даже десятков киловольт. Возможен пробой реле — как механического, так и твердотельного. Я думаю, нужно принимать меры к «облагораживанию» индуктивной нагрузки, например — еще одно реле, которое точно в момент размыкания цепи питания замыкало бы нагрузку накоротко. Или какой-нибудь разрядник, или полупроводниковый саппрессор.
                          • 0
                            Если взять первый способ, когда будет замыкаться нагрузка накоротко, после размыкания цепи питания, то можно столкнуться с КЗ: то есть реле питания залипло, а контакты сплавились и тут второе реле замыкает нагрузку, которая находится под напряжением. У меня этот двигатель на отдельном автомате, но не хотелось бы так рисковать.
                            В таком случае, надежнее будет использовать твердотельное реле или контактор. Любопытно хотя бы 3-4 месяца реле погонять, а потом разобрать его и посмотреть состояние контактных площадок.
                            • 0
                              Разумеется, должна быть защита от КЗ. И еще один момент: если вторым реле замыкать нагрузку накоротко — то второе реле может замкнуться слишком рано (и устроить КЗ сети), или слишком поздно (уже после того, как произойдет бросок напряжения). Поэтому лучше применять быстродействующие устройства борьбы с бросками напряжения: разрядники, сапрессоры (Transient Suppressor).
                              • 0
                                Тут только опытная эксплуатация покажет. Я выждал месяц после запуска контроллера, прежде чем написать. На данный момент работает. К концу весны можно разобрать реле и посмотреть состояние контактов. Правда, как я понимаю, после разбора реле можно выбросить.
                            • 0
                              Варистор же, не?
                            • 0
                              Магнитный пускатель вам в помощь wiki
                              • 0
                                Коммутировать большие токи можно симисторами с радиаторами. Если электричество всё равно идёт на обогрев, то снижение КПД из-за падения напряжения можно не учитывать.
                              • +1
                                del: не туда опубликовал
                                • 0
                                  Обсуждение реле и коммутации электродвигателей просто обязано докатиться до использования частотного преобразователя и тормозного резистора, не?
                                  • 0
                                    Я приверженец простых и экономичных решений, но не в угоду надежности. Каждый элемент, усложняющий конструкцию вносит снижение этой самой надежности. В данном случае, самый «слабый» элемент — механическое реле. Если соотнести ущерб от его внезапной смерти со стоимостью альтернативного метода подключения (твердотельное реле, контактор, устройство плавного запуска), то становится ясно, что максимум, что можно потерять — это энергию на безостановочную работу вытяжки, пока я не замечу сбой. Ну или еще сколько-нибудь денег на создание системы коммутации, надежность которой будет стремиться к 99,9%.
                                    • 0
                                      С другой стороны, можно подумать об обратной связи. Например, датчик тока. Можно три нештатных ситуации отследить: незапуск двигателя при подаче команды, продолжение работы при подаче команды на остановку, превышение номинального тока (заклинивание).
                                      • 0
                                        На последний вариант стоит отдельный автомат тип С на 10А, который обслуживает только линию с вытяжкой. Первые два варианта бeдут интересны в промышленных системах, где необходимо удаленно отслеживать работу нескольких аналогичных систем. Идея отличная! А что за тормозной резистор?
                                        • 0
                                          Используется как раз в промышленных установках. Двигатель управляется преобразователем частоты. Когда с двигателя снимается напряжение, он, продолжая вращаться, работает в генераторном режиме. Вот для рассеивания этого дела и ставится мощный резистор, который частотник коммутирует на двигатель при снятии питания. Но это уже, скорее, схема для мощных (несколько кВт и далее) двигателей с инерционной нагрузкой (не легкая крыльчатка, а какая-нибудь тяжелая байда типа шпинделя).

                                          Например www.owen.ru/catalog/36482892
                                          • 0
                                            Ну а так, отслеживание тока, плавный пуск и прочие плюшки в промышленных установках — это преобразователь частоты. Для самопала вообще ни разу не бюджетно, конечно.
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                    • +2
                                      У меня по дому теплые водяные полы. Стяжка играет роль огромного теплоаккумулятора. С 7 до 23 часов, когда действует дневной тариф, котел не работает и дом охлаждается на 3-4 градуса. Таким образом, ко времени сна, дом становится как раз холоднее, чем утром. А большая инертность бетонной подушки не дает сразу поднять температуру в комнатах.
                                      Для квартирных стиралок есть вариант уменьшения звука — резиновый коврик: image
                                    • +1
                                      Один хороший программист как-то сформулировал правило: неправильно выравненный код работать не может. Проект у вас хороший, а вот код оформлен ужасно. По аналогии это как будто вы вместо красивой коробочки, кнопок и дисплея смонтировали все на макетной доске и прибили гвоздями к стене. Как еще говорят, качество одежды определяют по внутренним швам!
                                      • 0
                                        Благодарю за замечание. Мне в личку подсказали нужный тег- поправил. Учиться всегда приятно, спасибо за это хабр-сообществу.
                                        • 0
                                          Ну вот сейчас, например:

                                                if (digitalRead(button3)==HIGH) //нажимая верхнюю кнопку меняем часы
                                               {  
                                               hourOn++;
                                              if (hourOn >=24) hourOn=0;
                                                  }
                                          
                                      • 0
                                        Подскажите, что за кнопки использовали? Насколько могу судить по видео, монтаж на корпус, достаточно мягкое нажатие, но, клик как у тактовых кнопок с монтажом на плату. Или это щёлкает кнопка об свой ободок?
                                        • 0
                                          Кнопки нашел в одном павильоне на Митинском радиорынке в цокольном помещении- там огромный лоток с кучей кнопок. Понравилось отсутствие щелчков и мягкость хода. Правда одна из кнопок просто развалилась во время монтажа. Как говорит мой знакомый — совсем китайский китай…
                                          Думаю, что аналогичные кнопки можно найти в автозапчастях.
                                          • 0
                                            Или разобрать древние клавиатуры, те что еще не пленочные. там кнопки индивидуальные.
                                        • 0
                                          Для реализации реле времени в одном проекте я взял «голую» ATMega и тактировал её от часового кварца (32768Гц). Дёшево и сердито.
                                          • 0
                                            В планах расширять функционал, а Arduino Nano уже имеет всю необходимую обвязку и работать с ней комфортно. Не было цели сделать супер-дешево, надо было сделать удобно и недорого. Как аппетит приходит во время еды, так и расширение функционала идет после монтажа первой систему — так я поставил систему вентиляции через неделю после запуска управления котлом.
                                            • 0
                                              А какая там обвязка? Атмегам кроме конденсатора по питанию и не надо ничего. От слова «вообще».
                                              Потом, насколько я слышал, модульность *duino накладывает серьёзные ограничения по использованию выводов микроконтроллера. Я понимаю, что это всё для лучшей модульности и совместимости, но на «голый» контроллер вешать можно куда и что угодно — хватило бы ног да набора бортовой периферии. Это к вопросу о расширении.
                                              Комфорт — понятие относительное. Меня вполне устраивает такая связка: avra для компиляции, avrdude для заливки прошивок и встроенный редактор mc для написания кода. Ну да, я пишу на ассемблере. И ISP-программатор самодельный (на FT232).
                                              Дело вкуса, конечно, но я лично не очень люблю для решения конкретных задач использовать платформы, которые в данной задаче будут использоваться на 1%. Только если изучить/поиграться…
                                              Впрочем, *duino, как я понимаю, и есть учебная платформа для начинающих. Делать на ней что-то серьёзное и не предполагалось вроде бы.
                                          • +1
                                            А что по затратам? За сколько это окупится?
                                            • +2
                                              Считаем:
                                              Arduino Nano — 182 руб
                                              2х реле — 297 руб
                                              модуль часов — 62 руб
                                              DC-DC 12v-5v преобразователь — 62 руб
                                              Дисплей 20х4 с шиной i2c — 506 руб
                                              Коробка, кнопки, провода, плата — порядка 600 руб

                                              Итого: 1709 руб
                                              Затраты на отопление снизились на 30% и составили порядка 4500 руб, то есть экономия составила около 1500 руб за первый месяц. Окупаемость чуть больше месяца. Опыт-бесценно!
                                              • 0
                                                Так нормальный контроллер так и работает. Поставь ему желаемое время подогрева, внутренний/внешний температурный датчик, отдельное управление насосом и пр. А потом настрой оптимальную отопительную кривую. Как кейс — обрати внимание — что если не запускать котел, а просто включить на несколько минут насос на циркуляцию — возможна передача тепла от более теплого бетонного «термоса» к более холодному. Еще лучше будет поставить тепловой бак на воде — залить 1 м3 в бак, который снаружи обложен полистиролом. (у воды удельная теплоемость почти в 2 раза выше чем бетон ) А вот совсем выключать систему, не учитывая температуру в помещении — я бы не советовал — жить будет крайне некомфортно

                                                • 0
                                                  Если речь про форумхаус и пользователя, который запилил огромный бак и засунул туда батарею, то я изучал его топик. Мне не подойдет по нескольким причинам. Циркуляционны насосы у меня работают без остановки, выравнивая температуру между комнатами. Их суммарная мощность 70 Вт*ч, что в рублях эквивалентно ~100 рублей в месяц. Я думал их включать перманентно, но оно не стоит того.
                                                • 0
                                                  > а с ростом температуры стала повышаться влажность.
                                                  Может наоборот? Зимой в теплом помещении влажность понижается до 20-30 процентов при комнатной температуре (17 л воды в 1м3). Это очень мало и сухо. А при нагревании или проветривании через окна холодным воздухом будет еще суше (на улице при -20 — менее 100 г в 1м3)
                                                  • 0
                                                    У меня идет отделка в доме, шпклевка, покраска и поклейка обоев связаны с выделением большого количества влаги. И если раньше, когда не было принудительной вытяжки, часто приходилось открывать окна и устраивать сквозняк, потому что по окнам буквально тек конденсат, то теперь в комнатах нормальная влажность.
                                              • 0
                                                На Alibaba купил вот такую штуку ru.aliexpress.com/item/USR-WP3-Home-Smart-Remote-Control-Switch-Wifi-Wireless-Socket-Wifi-Outlet/2039276083.html?recommendVersion=2
                                                -удивительно качественная штука — 3 независимых канала с неограниченным числом таймеров на вкл/выкл, и через WiFi (ну очень правильный передатчик — мощный и многостандартный) цепляется к облаку — туда же смотрит и мобильное приложение на iOS, Android и даже Windows XP (с исходниками !)
                                                Доволен как слон.
                                                До кучи еще взял ru.aliexpress.com/item/USR-HTW-Wifi-Wireless-Temperature-and-Humidity-Transmitter-Remote-Control/2046096745.html?recommendVersion=2
                                                • 0
                                                  По первому девайсу: мне никак не подходило, поскольку надо было запускать котел. Котел трехфазный, то есть имеем 380 Вольт и 4 провода(5й заземление и не коммутируется), да и нагрузка в 12 киловатт такие провода просто расплавит. Решение нашлось после переписки с официальным производителем котлов Valliant. Они мне подсказали, что котел может быть оснащен выносным датчиком температуры в помещении, который имеет нормальнозамкнутый контакт. При превышении установки, контакт размыкается и котел перестает греть. Таким образом, я свой контроллер подключил в цепь «датчика температуры» и руковожу работой котла. Маленькие токи — никаких искр и электрических дуг при размыкании, все решается копеечным реле.
                                                  Что касается второго девайса, то есть ответ для уже установленной ардуины в виде датчика температуры и влажности — 80 рублей и все работает.
                                                  Я тоже ратую за конечные устройства и изначально искал вариант попроще. Пересмотрел различные реле времени, но решил, что контроллер с кучей выходов, который можно гибко программировать под собственные нужды и наращивать его возможности с помощью модулей — это и интересно и доступно.
                                                • +2
                                                  А Ваш контроллер позволяет управлять котлом не только по часам, но и по дням недели?
                                                  • +1
                                                    В принципе, можно запрограммировать, так как модуль DS1307 выдает не только время, но и дату, а самое главное — день недели в числе. То есть понедельник -1, вторник -2 и т.д. Так что, в принципе, реализуемо. Попробую сделать и дополнить статью.
                                                    • 0
                                                      Добавил эту функцию, новая версия кода добавлена в статью

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