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

в 0:48, , рубрики: Песочница, метки: , , , , , , , , ,

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

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

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

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 листает минуты. Может кто подскажет, если сталкивался с подобным?

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js