Суточный термостат для газового котла

Slaiterx
Offline
Зарегистрирован: 05.05.2016

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

Slaiterx
Offline
Зарегистрирован: 05.05.2016
/*
Суточный термостат для котла Baxi Mainfour 240F
 >Encoder control
 >DS18B20 thermal sensor
 >LCD1602 parallel
 >DS1307 RTC
 */
 
//#include <avr\eeprom.h>
#include <Wire.h> // i2c (для RTC)
#include <RealTimeClockDS1307.h> // RTC
#include <EEPROMex.h> // EE
#include <LiquidCrystal_I2C.h>
#include <TimerOne.h> // прерывания по таймеру1
#include "DHT.h"
#include <OneWire.h> // 1wire для DS18B20
#include <DallasTemperature.h> // DS18B20
 
#define ONE_WIRE_BUS A0
#define DHTPIN A1 // к какому пину будет подключен сигнальный выход датчика 
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
DeviceAddress DS18B20Address;
 
#define encoderA    2 // энкодер - поворот вправо (об землю)
#define encoderB    3 // энкодер - поворот влево (об землю)
#define encoderK    4 // энкодер - кнопка (об землю)
#define BeepPin     11 // пищалка
#define BeepToneNo  2000 // тон звука "No", герц
#define BeepToneYes 4000 // тон звука "Yes", герц
#define BeepToneNoDuration 200 // длительность звука "No", мс
#define BeepToneYesDuration 200 // длительность звука "Yes", мс
#define Relay  12 // нога, к которой подключено реле
#define RelayOn LOW // полярность сигнала включения реде (HIGH/LOW)
//выбор используемого датчика
#define DHTTYPE DHT11 // DHT 11
//#define DHTTYPE DHT22 // DHT 22 (AM2302)
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
//инициализация датчика
DHT dht(DHTPIN, DHTTYPE);
// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x3f, 20, 4);
 
byte block1[8] = {
  0x06,0x09,0x09,0x06,0x00,0x04,0x0E,0x1F }; // значок градуса с пламенем снизу
byte block2[8] = {
  0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00 }; // значок градуса
 
//#define serialenabled // раскомментировать для выдачи в порт отладочной инфы
 
#define TstatTimerMax 180 //минимальная пауза между включениями горелки, сек
unsigned int TstatTimer = 20; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса
 
float DS18B20Temperature = 0; //сырая температура от датчика
float Temperature = 0; //вычисленная температура с коррекцией
float DS18B20TempTmp; //времянка
byte DS18B20iteration = 0; //счётчик измерений температуры для усреднения
 
float TstatTemp = 23; //температура термостатирования, может изменяться настройками
float TemperatureCorr = 0; //коррекция температуры, может изменяться настройками
float Hysteresis = 0.1; // гистерезис термостата, может изменяться настройками
float HysteresisOld;
 
int Hours = 0; // времянка часов RTC для отображения и установки
int Minutes = 0; // времянка минут RTC для отображения и установки
int Seconds;
boolean PrintYesNo = false; // показывать ли после времени Yes/No (косвенно - указание на режим установка/отображение)
boolean SetH = false; // выделение часов при отображении
boolean SetM = false; // выделение минут при отображении
boolean SetYesNo = false; // выделение Yes/No при установке часов
 
boolean blink500ms = false; // мигающий бит, инвертируется каждые 500мс
boolean plus1sec = false; // ежесекундно взводится
 
boolean BeepEnabled = true;
 
byte MenuTimeoutTimer;
 
// структура для суточных таймеров (8 байт)
struct buffer_template {
  byte Hours;
  byte Minutes;
  float Temperature;
  boolean Enabled;
  boolean Activated;
};
buffer_template Timer[4] = {
  0, 0, 23.0, false, false}; //объявление 4-х суточных таймеров и их начальные значения
 
float AlarmTemp = 10; // температура для замерзательного орала
 
// encoder vars
static boolean rotating=false;      // debounce management
boolean A_set = false;             
boolean B_set = false;
boolean encoderR = false;
boolean encoderL = false;
 
// EEPROM
EEMEM float TstatTempEE; //EE температура термостатирования
EEMEM float TemperatureCorrEE; // EE коррекция температуры
EEMEM float HysteresisEE; // EE гистерезис
EEMEM boolean BeepEnabledEE; // EE признак разрешения звука
EEMEM float AlarmTempEE; // EE значение недопустимого снижения температуры
EEMEM buffer_template TimerEE[4]; // EE структуры для 4 суточных таймеров
 
// ===== SETUP ========================================================================
void setup() {
 dht.begin();
#ifdef serialenabled
  Serial.begin(9600);
#endif
  pinMode(Relay, OUTPUT);
  digitalWrite(Relay, HIGH);
  // initialize the LCD
 
  // Turn on the blacklight and print a message.
  lcd.backlight();
  lcd.begin(20,4);
  lcd.createChar(1, block1);
  lcd.createChar(2, block2);
  pinMode(encoderA, INPUT);
  digitalWrite(encoderA, HIGH);
  pinMode(encoderB, INPUT);
  digitalWrite(encoderB, HIGH);
  pinMode(encoderK, INPUT);
  digitalWrite(encoderK, HIGH);
  attachInterrupt(0, doEncoderA, CHANGE);   // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 (pin 3)
  Timer1.initialize(500000); // Timer0 interrupt - set a timer of length 500000 microseconds
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
  EEPROM.setMaxAllowedWrites(32767);
  if ((digitalRead(encoderK)) == 0)
  { // если первая запись однокристалки (зажата кнопка при включении питания)- записать начальные значения в EE
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("Cold start..."));
    for (int i=0; i<4; i++)
    {
      //      Timer[i].Hours = Timer[i].Minutes = 0;
      //      Timer[i].Temperature = 23.0;
      //      Timer[i].Enabled = Timer[i].Activated = false;
      EEPROM.updateBlock(int(&TimerEE[i]), Timer[i]);
    }
    EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);
    EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
    EEPROM.updateFloat(int(&TemperatureCorrEE), TemperatureCorr);
    EEPROM.updateFloat(int(&HysteresisEE), Hysteresis);
    EEPROM.updateFloat(int(&AlarmTempEE), AlarmTemp);
    tone(BeepPin,2000,50);
    delay(50);
    tone(BeepPin,3000,50);
    delay(50);
    tone(BeepPin,4000,50);
    delay(1000); 
  }
  lcd.clear();
  lcd.setCursor(0, 0); //инфо на LCD
  lcd.print(F("Read settings..."));
  BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
  TstatTemp = EEPROM.readFloat(int(&TstatTempEE));
  TemperatureCorr = EEPROM.readFloat(int(&TemperatureCorrEE));
  Hysteresis = EEPROM.readFloat(int(&HysteresisEE));
  AlarmTemp = EEPROM.readFloat(int(&AlarmTempEE));
  for (int i=0; i<4; i++)
  {
    EEPROM.readBlock(int(&TimerEE[i]), Timer[i]);
  }
 
  DS18B20.begin();
  DS18B20.getAddress(DS18B20Address, 0);
  DS18B20.setResolution(DS18B20Address, 12);
  DS18B20.setWaitForConversion(false);
  DS18B20.requestTemperatures();
 
  tone(BeepPin,4000,50);
  delay(100); 
  tone(BeepPin,4000,50);
  delay(1000); 
  lcd.clear();
  RTC.start();
 
}
 
// ===== MAIN CYCLE ===================================================================
void loop() {
  float hdht = dht.readHumidity();
  // Read temperature as Celsius
  float tdht = dht.readTemperature();
  lcd.setCursor(10, 0); //инфо на LCD
  if ((Temperature < AlarmTemp)&(blink500ms)) {
    lcd.print(F("*"));
  }
  else {
    lcd.print(F(" "));
  }
  lcd.print(F("t="));
  if (Temperature < 10) {
    lcd.print(F(" "));
  }
  lcd.print(Temperature,1);
  lcd.write(0x02); // значок градуса
  
  lcd.setCursor(4, 2); // Устанавливаем курсор в начало 1 строки
  lcd.print("Humi ="); // Выводим текст
  lcd.setCursor(11, 2); // Устанавливаем курсор на 11 символ
  lcd.print(hdht,1); // Выводим на экран значение влажности
  lcd.setCursor(15, 2); // Устанавливаем курсор на 11 символ
  lcd.print("%"); 
  lcd.setCursor(4, 3); // Устанавливаем курсор в начало 2 строки
  lcd.print("Temp ="); // Выводим текст, \1 - значок градуса
  lcd.setCursor(11, 3); // Устанавливаем курсор на 7 символ
  lcd.print(tdht,1); // Выводим значение температуры
  lcd.write(0x02);
  // если таймер включен - надпись светится, если сработал - мигает, обрабатываем все 4 таймера
  lcd.setCursor(1, 1); //инфо на LCD
  for (int i=0;i<4;i++){
    if ((Timer[i].Enabled)&!((Timer[i].Activated)&(blink500ms))) {
      lcd.print(F("T"));
      lcd.print(i+1);
    }
    else {
      lcd.print(F("  "));
    }
  }
 
  lcd.setCursor(11, 1); //инфо на LCD
  lcd.print(F("s="));
  lcd.print(TstatTemp,1);
  if ( digitalRead(Relay) == RelayOn ) {
    lcd.write(0x01); // значок градуса с пламенем
  }
  else {
    lcd.write(0x02); // значок градуса
  }
 
  // печатаем текущее время
  PrintYesNo = false;
  PrintRTC(2,0);
 
  // термостатирование
  if ( TstatTimer == 0 )
  {
    if ( Temperature > ( TstatTemp + Hysteresis ) ) // гистерезис
    {
      if ( digitalRead(Relay) == RelayOn ) // если горелка включена -
      {
        digitalWrite(Relay, !RelayOn); // выключить горелку
        TstatTimer = TstatTimerMax; // горелку держать выключённой не менее заданного в TstatTimerMax времени
      }
    }
    if (Temperature < TstatTemp)
    {
      if ( digitalRead(Relay) == !RelayOn ) // если горелка выключена -
      {
        digitalWrite(Relay, RelayOn); // включить горелку
        TstatTimer = TstatTimerMax; // горелку держать включённой не менее заданного в TstatTimerMax времени
      }
    }
  }
 
  // если прошла 1 секунда - делаем ежесекундные дела
  if (plus1sec) {
    plus1sec = false; // сбрасываем до следующей секунды
    // обновляем часы
    RTC.readClock();
    Hours=RTC.getHours();
    Minutes=RTC.getMinutes();
    Seconds=RTC.getSeconds();
 
    // измеряем температуру воздуха
    DS18B20TempTmp = DS18B20.getTempCByIndex(0); // получить температуру от датчика
    //Serial.println(DS18B20TempTmp);
    DS18B20.requestTemperatures();  // запустить новое измерение
    if (DS18B20TempTmp != -127)
    {
      DS18B20Temperature += DS18B20TempTmp; // суммируем для усреднения
      DS18B20iteration ++;
      if (DS18B20iteration == 10)
      {
        DS18B20iteration = 0;
        Temperature = (DS18B20Temperature / 10) + TemperatureCorr; //усреднённая + коррекция
        DS18B20Temperature = 0;
      }
    }
 
    // если уставку термостата поменяли вручную - запись её в EE, не чаще 1 раза в минуту
    //(экономия ресурса EE)
    if ((EEPROM.readFloat(int(&TstatTempEE)) != TstatTemp)&(Seconds == 0)) {
      EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);
    }
 
    // проверка таймеров и изменение уставки термостата при совпадении 
    for (int i=0;i<4;i++)
    {
      if ((Hours == Timer[i].Hours)&(Minutes == Timer[i].Minutes)&(Timer[i].Enabled)&(Seconds == 0)) { // время T совпадает с RTC
      Timer[0].Activated = Timer[1].Activated = Timer[2].Activated = Timer[3].Activated = false;
        Timer[i].Activated = true;
        TstatTemp = Timer[i].Temperature;
        EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);
        if (BeepEnabled) {
          tone(BeepPin,4000,5);
        }
        break; // это чтобы статус Activated остался взведённым
      }
    }
 
    // дебаг-инфо - в терминал
#ifdef serialenabled
    Serial.print(F("Temp="));
    Serial.print(Temperature, 1);
    Serial.print(F("("));
    Serial.print(DS18B20Temperature, 4);
    Serial.print(F(",corr "));
    Serial.print(TemperatureCorr, 1);
    Serial.print(F("),TstatTimer="));
    Serial.println(TstatTimer);
#endif
 
    if (Temperature < AlarmTemp) {
      tone(BeepPin,4000,5);
    }
  }
 
  // обработка поворота энкодера на лету (ручное изменение уставки температуры))
  rotating = true;  // reset the debouncer
  if ((encoderR)|(encoderL)) {
    if (encoderR) {
      TstatTemp += 0.1;
    }
    else
    {
      TstatTemp -= 0.1;
    }
    TstatTemp = constrain(TstatTemp, 10, 35);
    encoderR = false;
    encoderL = false;
    Timer[0].Activated = Timer[1].Activated = Timer[2].Activated = Timer[3].Activated = false;
  }
 
  // ================ по нажатию кнопки энкодера - меню настроек ====================
  if(digitalRead(encoderK) == 0) {
    MenuTimeoutTimer = 10; //таймер таймаута, секунд
    lcd.clear();
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("< SETUP >")); 
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    delay(200);
    int menuitem = 0;
 
    do {
      rotating = true;  // reset the debouncer
      if ((encoderR)|(encoderL)) {
        MenuTimeoutTimer = 10; //таймер таймаута, секунд
        if (encoderR) {
          menuitem += 1;
        }
        else  {
          menuitem -= 1;
        }
        if ( menuitem > 9 ) {
          menuitem = 0;
        } // границы пунктов меню
        if ( menuitem < 0 ) {
          menuitem = 9;
        }
        encoderR = false;
        encoderL = false;
      }
 
      // индикация пункта меню (номер пункта - в menuitem)
      lcd.setCursor(0, 1); //инфо на LCD
      switch(menuitem)
      {
      case 0:
        lcd.print(F("0.BACK          ")); 
        break;
      case 1:
        lcd.print(F("1.TIMER1 SET    ")); 
        break;
      case 2:
        lcd.print(F("2.TIMER2 SET    ")); 
        break;
      case 3:
        lcd.print(F("3.TIMER3 SET    ")); 
        break;     
      case 4:
        lcd.print(F("4.TIMER4 SET    ")); 
        break;     
      case 5:
        lcd.print(F("5.CLOCK SET     ")); 
        break;
      case 6:
        lcd.print(F("6.HYSTERESIS SET")); 
        break;
      case 7:
        lcd.print(F("7.T-CORRECT SET ")); 
        break;
      case 8:
        lcd.print(F("8.SOUND SET     ")); 
        break;
      case 9:
        lcd.print(F("9.T-ALARM SET   ")); 
        break;     
      }
      if (MenuTimeoutTimer == 0) {
        menuitem = 0;
      }
 
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    // если нажата кнопка энкодера или таймаут - обработка пункта меню (номер пункта - в menuitem)
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    switch(menuitem)
    {
      // ====== пункт 0 - выход
    case 0:
      if (BeepEnabled) {
        tone(BeepPin,BeepToneNo,BeepToneNoDuration);
      } //звук "NO"
      break; // case 0 out
 
      // ====== пункт 1 - установка Timer1
    case 1:
      TimerXSetup(0);
      break; // case 1 out
 
      // ====== пункт 2 - установка Timer2
    case 2:
      TimerXSetup(1);
      break; // case 2 out
 
      // ====== пункт 3 - установка Timer3
    case 3:
      TimerXSetup(2);
      break; // case 3 out
 
      // ====== пункт 4 - установка Timer4
    case 4:
      TimerXSetup(3);
      break; // case 4 out
 
      // ====== пункт 5 - установка RTC
    case 5:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP CLOCK")); 
      delay(200);
      RTC.readClock();
      Hours=RTC.getHours();
      Minutes=RTC.getMinutes();
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo)
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          RTC.setHours(Hours);
          RTC.setMinutes(Minutes);
          RTC.setSeconds(0);
          RTC.setClock();
          RTC.start();
        }
        else
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 5 out
 
      // ====== пункт 6 - установка гистерезиса
    case 6:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      HysteresisOld = Hysteresis;
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP HYSTERESIS")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   "); 
        }
        else {
          lcd.print(Hysteresis, 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          Hysteresis += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          Hysteresis -= 0.1;
          encoderL = false;
        }
        Hysteresis = constrain(Hysteresis, 0.1, 1); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&HysteresisEE), Hysteresis); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        Hysteresis = HysteresisOld;
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 6 out
 
      // ====== пункт 7 - установка коррекции температуры
    case 7:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-CORRECT ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (TemperatureCorr >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(TemperatureCorr, 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          TemperatureCorr += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          TemperatureCorr -= 0.1;
          encoderL = false;
        }
        TemperatureCorr = constrain(TemperatureCorr, -8, 8); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&TemperatureCorrEE), TemperatureCorr); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        TemperatureCorr = EEPROM.readFloat(int(&TemperatureCorrEE));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 7 out     
 
      // ====== пункт 8 - вкл/выкл звука
    case 8:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SOUND SET       ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (BeepEnabled) {
          lcd.print(F("BEEP ON         ")); 
        }     
        else {
          lcd.print(F("BEEP OFF        ")); 
        }
 
        rotating = true;  // reset the debouncer
        if ((encoderR)|(encoderL)) {
          BeepEnabled = !BeepEnabled;
          encoderR = false;
          encoderL = false;
        }
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
        EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
      }
      if (MenuTimeoutTimer == 0) {
        BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
      }
      break; // case 8 out
 
      // ====== пункт 9 - установка предупреждалки о холоде
    case 9:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("ALARM-TEMP SET  ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (AlarmTemp >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(AlarmTemp, 0); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          AlarmTemp += 1;
          encoderR = false;
        }
        if (encoderL) {
          AlarmTemp -= 1;
          encoderL = false;
        }
        AlarmTemp = constrain(AlarmTemp, 15, 30); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&AlarmTempEE), AlarmTemp); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        AlarmTemp = EEPROM.readFloat(int(&AlarmTempEE));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 9 out
 
    }
 
    delay(200);
    lcd.clear();
  }
}
 
// ===== SUBROUTINES ==================================================================
 
// ========================================
void SetTime(char x, char y)
{
  // ========= set hours
  SetH = true;
  do {
    PrintRTC(x,y);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Hours += 1;
      if(Hours > 23) {
        Hours = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Hours -= 1;
      if(Hours < 0) {
        Hours = 23;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  SetH = false;
  delay(200);
  // ========= set minutes
  SetM = true;
  do {
    PrintRTC(0,1);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Minutes += 1;
      if(Minutes > 59) {
        Minutes = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Minutes -= 1;
      if(Minutes < 0) {
        Minutes = 59;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  if (PrintYesNo) {
    SetM = false;
    delay(200);
    // ========= set yes-no
    SetYesNo = false;
    do {
      PrintRTC(0,1);
      rotating = true;  // reset the debouncer
      if ((encoderR)||(encoderL)) {
        SetYesNo = !SetYesNo;
        encoderR = false;
        encoderL = false;
      }
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    delay(200);
  }
 
}
 
// ========================================
void PrintRTC(char x, char y)
{
  lcd.setCursor(x,y);
  if (SetH&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Hours < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Hours);
  }
 
  // мигающее двоеточие, если не в режиме установки времени
  if (!(SetH||SetM||PrintYesNo||blink500ms))
  {
    lcd.print(F(" "));
  }
  else {
    lcd.print(F(":"));
  }
 
  if (SetM&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Minutes < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Minutes);
  }
  lcd.print(F(" "));
 
  if (PrintYesNo) {
    lcd.print(F("["));
    if (!(SetH||SetM||blink500ms))
    {
      lcd.print(F("   "));
    }
    else {
      if (SetYesNo)
      {
        lcd.print(F("YES"));
      }
      else {
        lcd.print(F("NO "));
      }
    }
    lcd.print(F("]"));
  }
 
}
 
// ============================ Encoder interrupts =============================
// Interrupt on A changing state
void doEncoderA(){
  if ( rotating ) {
    delay (1) ;  // wait a little until the bouncing is done
  }
  // Test transition, did things really change?
  if( digitalRead(encoderA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set )
    {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderR = true;
      rotating = false;  // no more debouncing until loop() hits again
    }
  }
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) {
    delay (1);
  }
  if( digitalRead(encoderB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderL = true;
      rotating = false;
    }
  }
}
// ============================ Timer0 interrupt =============================
// run every 500ms
void timerIsr()
{
  blink500ms = !blink500ms; // инверсия мерцающего бита
  if(blink500ms) {
    plus1sec = true; // ежесекундно взводится
    if (TstatTimer != 0) {
      TstatTimer --; // ежесекундный декремент этого таймера
    }
    if (MenuTimeoutTimer != 0) {
      MenuTimeoutTimer --; // ежесекундный декремент этого таймера
    }
  }
}
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TimerXSetup(int X) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER")); 
      lcd.print(X+1);  // выводим номер таймера на LCD
      delay(200);
      Hours=Timer[X].Hours;
      Minutes=Timer[X].Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer[X].Hours = Hours;
          Timer[X].Minutes = Minutes;
          Timer[X].Enabled = true;
          EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);
 
          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer"));
          lcd.print(X+1);
          lcd.print(F(" Temp. Set")); 
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     ")); 
            }
            else {
              lcd.print(Timer[X].Temperature, 1); 
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer[X].Temperature += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer[X].Temperature -= 0.1;
              encoderL = false;
            }
            Timer[X].Temperature = constrain(Timer[X].Temperature, 10, 35); // крайние значения
          }
          while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
          EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
          EEPROM.readBlock(int(&TimerEE[X]), Timer[X]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }      
 
        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          EEPROM.readBlock(int(&TimerEE[X]), Timer[X]);
          Timer[X].Enabled = false;
          EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~````

 

Slaiterx
Offline
Зарегистрирован: 05.05.2016

i2c дисплей 2004 и дополнительный датчик для измерения температуры и влажности на улице пока dht11 в дальнейшем dht22

idtmark
idtmark аватар
Offline
Зарегистрирован: 04.06.2016

bagotu пишет:

по I2C часы и LCD работают. 

добавил 4-й таймер. типа: утро, день, вечер, ночь.

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

петрович33
Offline
Зарегистрирован: 04.11.2016

[quote=olezhan36]

Вот плод моих ночных бдений в свободное от работы и семьи время.

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

За основу были взяты 3 скетча:

1.  скетч автора этой темы

2.https://www.youtube.com/watch?v=mFtgO3kWA4g

3.http://cxem.net/arduino/arduino166.php для работы со сдвиговым регистром.

vlad219i написал отличный скетч, но бывает так (как у меня), что система отопления состоит не только из одного котла. Например, у меня есть котел, к которому через гидрострелку подключены радиаторы, теплый пол, бойлер косвенного нагрева. Есть 3 насоса+насос рециркуляции ГВС. И всем этим хозяйством нужно управлять. Вот я и решил немного допилить скетч vlad219i.

Изменения:

-изменение температуры термостатирования перенесено в меню

-вращение энкодера до входа в меню теперь переключает  каналы термостатирования

-реле основное вкл/выкл котел, замыкает  и размыкает контакты термостата котла на плате.

-3 свободных аналоговых входа использованы для подключения сдвигового регистра 74HC595. Т.о. имеем 8 цифровых выходов, к которым подключены реле для управления  8 каналами. По каждому из каналов скетч работает как у vlad219i-задается температура и поддерживается, сохранено время миниманальной паузы между включениями/выключениями. Таймеры сохранены.

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

Таким образом можно регулировать температуру в 8-ми комнатах отдельно, да ещё и по расписанию. Если конечно есть исполнительные устройсва в этих комнатах.

Я планирую управлять температурой в комнатах (один датчик на все комнаты) с помощью вкл/выкл насоса радиаторов. Аналогично для теплого пола и бойлера косвенного нагрева.

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

Будет ли экономия существенной я узнаю только следующей зимой. А пока это только мечты...

Очень интересный проект, только не компелируется пишит 

sketch_may10a:88: error: function definition does not declare parameters
sketch_may10a.ino: In function 'void getTemp()':
sketch_may10a:774: error: 'sensor' was not declared in this scope
 В чем косяк ?
петрович33
Offline
Зарегистрирован: 04.11.2016

Подскажите пожалуста почему ошибки при компилирование скетча с 5 страницы

sketch_may10a:88: error: function definition does not declare parameters
sketch_may10a.ino: In function 'void getTemp()':
sketch_may10a:774: error: 'sensor' was not declared in this scope
bagotu
Offline
Зарегистрирован: 15.10.2016

Нет там ошибок. Ради интереса проверил. Все там компилируется.  "Скетч использует 20 668 байт (64%) памяти устройства. Всего доступно 32 256 байт. Глобальные переменные используют 1 283 байт (62%) динамической памяти, оставляя 765 байт для локальных переменных. Максимум: 2 048 байт."

Ищите ошибку у себя

петрович33
Offline
Зарегистрирован: 04.11.2016

Спасибо что откликнулись !!! Я не спорю что в коде нет ошибок , я спрашиваю почему у меня не проходит  компиляция ?

Код (термостат )автора темы  у меня работает Отлично За что ему отдельное спасибо ,стоит на водогрейке   Но хотелось несколько каналов  А  тут печалька  получается 

bagotu
Offline
Зарегистрирован: 15.10.2016

Значит скопировали не верно... Телепатов тут нет.

петрович33
Offline
Зарегистрирован: 04.11.2016

Нет не в копирование дела , не один раз копиравал А по поводу Телепатов ,плохо что нет 

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

петрович33 пишет:

Нет не в копирование дела , не один раз копиравал А по поводу Телепатов ,плохо что нет 

В какой версии Arduino IDE компилируете?

петрович33
Offline
Зарегистрирован: 04.11.2016

1.0.6

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

А у автора какая IDE?

У меня не компилится в виду отсустствия библиотек. 

#include <RealTimeClockDS1307.h> // RTC
#include <EEPROMex.h> // EE
#include <TimerOne.h> // прерывания по таймеру1
 
Ставить честно говоря лень
петрович33
Offline
Зарегистрирован: 04.11.2016

У вас какая версия IDE ? Ошибок как у меня я понял у вас нет 

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

У меня IDE 1.8.2

Из ошибок 
 

#include <RealTimeClockDS1307.h> // RTC

                                        ^

compilation terminated.

Но это из за того что библиотек нет. Может если скачать и установить библиотеки будут еще ошибки, а может и нет. Но вроде выше писали что все ок. 

петрович33
Offline
Зарегистрирован: 04.11.2016

Нада значит IDE 1.8.2 поставить  и еще не могу понять в коде ,куда цыплять  74HC595.

bagotu
Offline
Зарегистрирован: 15.10.2016

Кто-нибудь прикрутил к термостату NRF24L01?

yuraanapa
Offline
Зарегистрирован: 31.05.2017

Подскажите как правильн скетч с поста скопировать,сначала в редактор потом сохранить в ино ?Просто циферки строк смущают )

 

evgta
Offline
Зарегистрирован: 02.09.2016

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

yuraanapa
Offline
Зарегистрирован: 31.05.2017

Спасибо,если красным посвечивает это занчит библиотек не хватает ?

yuraanapa
Offline
Зарегистрирован: 31.05.2017

Сам себе отвечу,нет не значит,просто подсветка в каких то конф файлах включена.У меня вопрос к Slaiterx и другим кто подключает дисплей по i2c на A4 A5 висят часы а дисплей тогда куда вешать ? Или подскажите где почитать про это,никак не могу найти

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

yuraanapa пишет:

Сам себе отвечу,нет не значит,просто подсветка в каких то конф файлах включена.У меня вопрос к Slaiterx и другим кто подключает дисплей по i2c на A4 A5 висят часы а дисплей тогда куда вешать ? Или подскажите где почитать про это,никак не могу найти

I2C является адресной шиной и позволяет подключать до 127 (тут не уверен, возможно больше) различных устройств. Каждое устройство имеет уникальный адрес, по которому и происходит обращение. Вот подключить двое часов или барометрических датчиков так просто не выйдет, у них он не меняется. Для часов - 0х68. 
Вешаете свои устройства, запускаете i2C сканер (погуглите), находите адрес своего дисплея и указываете его в строке инициализации (в примерах обычно стоит 0х27). Если количество адресов меньше, чем подключено устройств, значит где то косяк в подключении.

bagotu
Offline
Зарегистрирован: 15.10.2016

demonik-13 пишет:

Отличный термостатик получился. Автору премного благодарностей! Если кому интересно есть код на два модуля с радиосвязью по RF24.

Интересно! Где смотреть?

bagotu
Offline
Зарегистрирован: 15.10.2016

idtmark пишет:

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

пора наверное на OLED перейти.

LCD реально как ночник работает.

yuraanapa
Offline
Зарегистрирован: 31.05.2017

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

Alex_ss
Offline
Зарегистрирован: 26.09.2017

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

yuraanapa
Offline
Зарегистрирован: 31.05.2017

LC фильтр по питанию поставить или просто дросселек один,возможно поможет

MaKo
Offline
Зарегистрирован: 25.10.2017

yuraanapa пишет:

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

Вот тут есть интересный проект http://arduino.ru/forum/proekty/moi-termostat-dlya-kotla-baxi

сам хочу что-то подобное сделать.

ssbon
Offline
Зарегистрирован: 17.02.2015

Коллеги, недельный таймер, аналогичный этому, никто не делал?

По идее недельный даже не нужен, достаточно пнд-чтв, птн, сбт-вск или будни/выходные.

ssbon
Offline
Зарегистрирован: 17.02.2015

Подскажите, AT24C32A для EEPROM достаточно?

vlad219i
Offline
Зарегистрирован: 05.03.2014

ssbon пишет:

Подскажите, AT24C32A для EEPROM достаточно?

Если речь идёт про этот проект, то здесь используется EEPROM, встроенный в микроконтроллер.

А если нет - то ДА, конечно, достаточно. :-)

 

ssbon
Offline
Зарегистрирован: 17.02.2015

Ну это все в принципе меняет :), а я собрался AT24C32A прикручивать

 

 

MaKo
Offline
Зарегистрирован: 25.10.2017

demonik-13 пишет:

Отличный термостатик получился. Автору премного благодарностей! Если кому интересно есть код на два модуля с радиосвязью по RF24.

Плохо что здесь в личку писать нельзя, как общаться, не понимаю!?

Если можно demonik-13 киньте пожалуйста свой код и схему желательно, если есть.

lkostyan@mail.ru

Vladis_a
Offline
Зарегистрирован: 13.12.2015

Вот уже 2 года бесперебойно трудится такой термостат! Хочу выразить ещё раз огромную благодарность автору! Сэкономлено несколько кубов газа и, соответственно, денег. :)

Читал, что кто-то собирался разделить датчик и управляющую часть и соединить их с помощью NRF24. Очень прошу поделиться информацией! Заранее благодарен!

 

olezhan36
Offline
Зарегистрирован: 29.02.2016

Всем привет. Я снова в деле:-))

Ранее опубликованая мной многоканальная версия термостата доработана.

Использован LCD 2004 по I2C

Как и ранее для сбережения выходов ардуины использован  сдвиговый регистр 74HC595.

К каждому из 8 выходов сдвигового регистра подключаем  реле, которые управляют исполнительными устройствами (насосами, тенами, вентилями и т.д) поддерживая заданную температуру.

Есть главное реле (замыкает/размыкает контакты для комнатного термостата на плате котла, т.е. вкл/выкл котел), подключенное к отдельному пину ардуино. Если все 8 каналов, подключенных к сдвиговому регистру, выключены, то выключается и главное реле. 

Адреса датчиков температуры прописывам в скетче. Все 8 датчиков подключать не обязательно!!!

/*
Суточный термостат для газового котла многоканальный
 >Encoder control
 >DS18B20 thermal sensor
 >LCD1602 parallel
 >DS1307 RTC
 */
 
#include <Wire.h> // i2c (для RTC)
#include <RealTimeClockDS1307.h> // RTC
#include <EEPROMex.h> // EE
#include <LiquidCrystal_I2C.h> // LCD 20*4
#include <TimerOne.h> // прерывания по таймеру1
#include <OneWire.h> // 1wire для DS18B20
#include <DallasTemperature.h> // DS18B20

#define ONE_WIRE_BUS A1   //номер пина с подключенным датчиком температуры 
OneWire oneWire(ONE_WIRE_BUS);    // Создаем объект OneWire
DallasTemperature DS18B20(&oneWire);  // Создаем объект DallasTemperature для работы с сенсорами, передавая ему ссылку на объект для работы с 1-Wire.
DeviceAddress DS18B20Address;     // Специальный объект для хранения адреса устройства

 
#define encoderA    2 // энкодер - поворот вправо (об землю)
#define encoderB    3 // энкодер - поворот влево (об землю)
#define encoderK    13 // энкодер - кнопка (об землю)
#define BeepPin     12 // пищалка
#define BeepToneNo  2000 // тон звука "No", герц
#define BeepToneYes 4000 // тон звука "Yes", герц
#define BeepToneNoDuration 200 // длительность звука "No", мс
#define BeepToneYesDuration 200 // длительность звука "Yes", мс
#define RelayMain  11  // нога, к которой подключено реле, которое вык/вкл котел
#define RelayOn LOW // полярность сигнала включения реле (HIGH/LOW)
#define qtyChannal 8  //количество каналов/датчиков
int channalOn=0;  //количество включенных реле (каналов, температура которых ниже заданной).
LiquidCrystal_I2C lcd(0x3f, 20, 4);

//для регистра 74HС595
#define clock 16
#define data 17
#define latch 14
boolean Relay[8];

byte block3[8] = {
  0b00110,0b01111,0b11111,0b11111,0b11111,0b11111,0b11110,0b01100};   //значок пламени (индикация работы котла
byte block1[8] = {
  0x06,0x09,0x09,0x06,0x00,0x04,0x0E,0x1F }; // значок градуса с пламенем снизу
byte block2[8] = {
  0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00 }; // значок градуса
//#define serialenabled // раскомментировать для выдачи в порт отладочной инфы
 
#define TstatTimerMax 180 //минимальная пауза между включениями горелки, сек
unsigned int TstatTimer[qtyChannal]  ; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса

float DS18B20Temperature[qtyChannal] ; //времянка
float Temperature[qtyChannal] ; //вычисленная температура с коррекцией
float DS18B20TempTmp[qtyChannal]; //сырая температура от датчиков
byte DS18B20iteration = 0; //счётчик измерений температуры для усреднения
 
float TstatTemp[qtyChannal] ; //температура термостатирования, может изменяться настройками
float TstatTempOld[qtyChannal];
float TemperatureCorr[qtyChannal]; //коррекция температуры, может изменяться настройками
float Hysteresis[qtyChannal] ; // гистерезис термостата, может изменяться настройками
float HysteresisOld[qtyChannal];
float AlarmTemp[qtyChannal] ; // температура для замерзательного орала
 

int Hours = 0; // времянка часов RTC для отображения и установки
int Minutes = 0; // времянка минут RTC для отображения и установки
int Seconds;
boolean PrintYesNo = false; // показывать ли после времени Yes/No (косвенно - указание на режим установка/отображение)
boolean SetH = false; // выделение часов при отображении
boolean SetM = false; // выделение минут при отображении
boolean SetYesNo = false; // выделение Yes/No при установке часов
 
boolean blink500ms = false; // мигающий бит, инвертируется каждые 500мс
boolean plus1sec = false; // ежесекундно взводится
 
boolean BeepEnabled = true;
int channel=0;   //номер канала
 
byte MenuTimeoutTimer;
 
 // адреса градусников.
DeviceAddress sensor []{ 
 {0x28, 0xFF, 0x08, 0x26, 0x34, 0x16, 0x04, 0x68},  //
 {0x28, 0xFF, 0x01, 0x4D, 0x34, 0x16, 0x04, 0x9F},
 {0x28, 0xFF, 0x75, 0x2D, 0x34, 0x16, 0x03, 0xFE},
 {0x28, 0xFF, 0x84, 0x4D, 0x34, 0x16, 0x04, 0x86},
 {0x28, 0xFF, 0x4D, 0x28, 0x34, 0x16, 0x03, 0xC5},
 {0x28, 0xFF, 0xF7, 0x4D, 0x34, 0x16, 0x04, 0xA5},
 {0x28, 0xFF, 0x8C, 0x30, 0x34, 0x16, 0x04, 0x8D},
 {0x28, 0xFF, 0xFC, 0x49, 0x34, 0x16, 0x04, 0xDB},
 {0x28, 0xFF, 0xAB, 0x30, 0x34, 0x16, 0x03, 0xA7},
 {0x28, 0xFF, 0x67, 0x40, 0x34, 0x16, 0x04, 0x8F},
};

// структура для суточных таймеров (8 байт)
struct buffer_template {
  byte Hours;
  byte Minutes;
  float Temperature;
  boolean Enabled;
  boolean Activated;
};
buffer_template Timer[4][qtyChannal] = {0, 0, 23.0, false, false}; //объявление 4-х суточных таймеров для каждого из  каналов и их начальные значения
 

// encoder vars
static boolean rotating=false;      // debounce management
boolean A_set = false;             
boolean B_set = false;
boolean encoderR = false;
boolean encoderL = false;
 
// EEPROM
EEMEM float TstatTempEE[qtyChannal]; //EE температура термостатирования
EEMEM float TemperatureCorrEE[qtyChannal]; // EE коррекция температуры
EEMEM float HysteresisEE[qtyChannal]; // EE гистерезис
EEMEM boolean BeepEnabledEE; // EE признак разрешения звука
EEMEM float AlarmTempEE[qtyChannal]; // EE значение недопустимого снижения температуры
EEMEM buffer_template TimerEE[4][qtyChannal]; // EE структуры для 4 суточных таймеров
 
// ===== SETUP ========================================================================
void setup() {
  
#ifdef serialenabled
  Serial.begin(9600);
#endif
// задаем начальные значения
for (int j=0; j<qtyChannal; j++){
  TstatTimer[j]=20  ; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса
  DS18B20Temperature[j]=0 ; //времянка
  Temperature[j]=0 ; //вычисленная температура с коррекцией
}
  pinMode(RelayMain, OUTPUT);
  digitalWrite(RelayMain, HIGH);
  lcd.begin();
  lcd.createChar(1, block1);
  lcd.createChar(2, block2);
  lcd.createChar(3, block3);
  pinMode(encoderA, INPUT);
  digitalWrite(encoderA, HIGH);
  pinMode(encoderB, INPUT);
  digitalWrite(encoderB, HIGH);
  pinMode(encoderK, INPUT);
  digitalWrite(encoderK, HIGH);
  attachInterrupt(0, doEncoderA, CHANGE);   // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 (pin 3)
  Timer1.initialize(500000); // Timer0 interrupt - set a timer of length 500000 microseconds
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
  EEPROM.setMaxAllowedWrites(32767);
  
//для сдвигового регистра
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  digitalWrite(latch, HIGH);
 cleanreg(); 
   
//холодный старт
  if ((digitalRead(encoderK)) == 0){ // если первая запись однокристалки (зажата кнопка при включении питания)- записать начальные значения в EE
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("Cold start..."));
    for (int j=0; j<qtyChannal; j++){
      for (int i=0; i<4; i++)
      {
        Timer[i][j].Hours = Timer[i][j].Minutes = 0;
        Timer[i][j].Temperature = 23.0;
        Timer[i][j].Enabled = Timer[i][j].Activated = false;
        EEPROM.updateBlock(int(&TimerEE[i][j]), Timer[i][j]);
      }
      //присвоение начальных значений  
      TstatTemp[j]=25 ; //температура термостатирования, может изменяться настройками
      TemperatureCorr[j]=0; //коррекция температуры, может изменяться настройками
      Hysteresis[j]=0.5 ; // гистерезис термостата, может изменяться настройками
      AlarmTemp[j]=20 ; // температура для замерзательного орала  
      //запись начальных значений в еепром
      EEPROM.updateFloat(int(&TstatTempEE[j]), TstatTemp[j]);
      EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
      EEPROM.updateFloat(int(&TemperatureCorrEE[j]), TemperatureCorr[j]);
      EEPROM.updateFloat(int(&HysteresisEE[j]), Hysteresis[j]);
      EEPROM.updateFloat(int(&AlarmTempEE[j]), AlarmTemp[j]);
     }
    tone(BeepPin,2000,50);
    delay(50);
    tone(BeepPin,3000,50);
    delay(50);
    tone(BeepPin,4000,50);
    delay(1000); 
  }
  lcd.clear();
  lcd.setCursor(0, 0); //инфо на LCD
  lcd.print(F("Read settings..."));
 
 //чтение еепром
  BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
  for (int j=0; j<qtyChannal; j++) 
  {
  TstatTemp[j] = EEPROM.readFloat(int(&TstatTempEE[j]));
  TemperatureCorr[j] = EEPROM.readFloat(int(&TemperatureCorrEE[j]));
  Hysteresis[j] = EEPROM.readFloat(int(&HysteresisEE[j]));
  AlarmTemp[j] = EEPROM.readFloat(int(&AlarmTempEE[j]));
 
  for (int i=0; i<4; i++)
   {
    EEPROM.readBlock(int(&TimerEE[i][j]), Timer[i][j]);
   }
  }
  DS18B20.begin();
  DS18B20.setResolution(DS18B20Address, 12);   //устанавливаем разрешения датчика 12 бит
  DS18B20.setWaitForConversion(false);
  DS18B20.requestTemperatures();  //запрос на измерение температуры
 
  tone(BeepPin,4000,50);
  delay(100); 
  tone(BeepPin,4000,50);
  delay(1000); 
  lcd.clear();
  RTC.start();
} 
// ===== MAIN CYCLE ===================================================================
void loop() {
//инфо на LCD 1 строка
  lcd.setCursor(6, 0);          
  lcd.print(F("channel "));
  lcd.print(channel+1);           //чтобы не было нулевого
  if (Temperature[channel]!=-127) {     //если есть датчик температуры 
    lcd.setCursor(17, 0); 
     if (TstatTimer[channel] < 10)      //чтобы надпись не скакала вправо/влево при переходе значения температуры с двух значного на однозначное
      lcd.print(F("  "));
      if (TstatTimer[channel] > 10&TstatTimer[channel] < 10)      //чтобы надпись не скакала вправо/влево при переходе значения температуры с двух значного на однозначное
      lcd.print(F(" "));
     if (TstatTimer[channel] !=0) {       //выводим остаток времени, по истечению которого возможно изменение статуса выхода
      lcd.print(TstatTimer[channel]);
     }
     else {
      lcd.print(F("   "));
     }
//инфо на LCD 2 строка  
  lcd.setCursor(0, 1);    
  lcd.print(F(" "));
   if (Temperature[channel] < 10) {     //чтобы надпись не скакала вправо/влево при переходе значения температуры с двух значного на однозначное
    lcd.print(F(" "));
   }
   lcd.print(Temperature[channel],1);      //текущая температура
   lcd.write(0x02); // значок градуса
   lcd.print(F("-->"));
   lcd.print(TstatTemp[channel],1);        //температура термостатирования
   if ( Relay[channel] == RelayOn ) {      //если  реле канала включено
    lcd.write(0x01); // значок градуса с пламенем
   }
   else lcd.write(0x02); // значок градуса
   lcd.print(F("  "));
    if ((Temperature[channel] < AlarmTemp[channel])&(blink500ms)) {       //выводим предупреждение о холоде 
     lcd.print(F("*"));
    }
    else {
    lcd.print(F(" "));
    }
  if (digitalRead(RelayMain) == RelayOn)  //если котел включен, то выводим на lcd
   lcd.write(0x03);   //значок работы котла
   else lcd.print(F(" "));
//инфо на LCD 3 строка
  lcd.setCursor(0, 2);
  lcd.print(F(" "));
  for (int i=0;i<4;i++){
    if ((Timer[i][channel].Enabled)&!((Timer[i][channel].Activated)&(blink500ms))) {    // если таймер включен - надпись светится, если сработал - мигает, обрабатываем все 4 таймера
      lcd.print(F("T"));
      lcd.print(i+1); 
      lcd.print(F("   "));     
     }
    else {lcd.print(F("     "));          //стираем таймеры с экрана
    }
    }
//инфо на LCD 4 строка
    lcd.setCursor(0, 3);       
    for (int i=0;i<4;i++){
    if ((Timer[i][channel].Enabled)&!((Timer[i][channel].Activated)&(blink500ms))) {   //выводим на экран температуры таймеров
      lcd.print(Timer[i][channel].Temperature,1);
      lcd.print(F(" "));
    }
    else {
      lcd.print(F("     "));
    }
    }
  }
//если нет сенсора, то не стираем с экрана все ненужное  
  else {                   
    lcd.setCursor(16, 0);
    lcd.print(F("    "));
    lcd.setCursor(0, 1);
    lcd.print(F("No sensor       "));
    lcd.setCursor(0, 2);
    lcd.print(F("                    "));          //стираем таймеры с экрана
    lcd.setCursor(0, 3);
    lcd.print(F("                    "));          //стираем температуры таймеров с экрана
  }
  
// печатаем текущее время
  PrintYesNo = false;
  PrintRTC(0,0);
  Termostat();// термостатирование каждого канала в отдельности, т.е включение/выключение исполнительного устройства отвечающего за температуру по каждому из каналов
  RelayMainON (); //включение/выключение котла, т.е замыкание /размыкание контактов реле, подключенных к колодки  комнатного термостата (плата котла)
   
// если прошла 1 секунда - делаем ежесекундные дела
  if (plus1sec) {
    plus1sec = false; // сбрасываем до следующей секунды
    RTC.readClock();   // обновляем часы
    Hours=RTC.getHours();
    Minutes=RTC.getMinutes();
    Seconds=RTC.getSeconds();
    getTemp(); // читаем температуру с датчиков  
   
// проверка таймеров и изменение температуры термостатирования при совпадении (активации таймера)
     for (int j=0; j<qtyChannal; j++){
      if (Temperature[j]!=-127){     //если нет сенсора, то и не включаем таймеры
       for (int i=0;i<4;i++){
         if ((Hours == Timer[i][j].Hours)&(Minutes == Timer[i][j].Minutes)&(Timer[i][j].Enabled)&(Seconds == 0)) { // время T совпадает с RTC
          Timer[0][j].Activated = Timer[1][j].Activated = Timer[2][j].Activated = Timer[3][j].Activated = false;
          Timer[i][j].Activated = true;
          TstatTemp[j] = Timer[i][j].Temperature;
          EEPROM.updateFloat(int(&TstatTempEE[j]), TstatTemp[j]);
          if (BeepEnabled) {
           tone(BeepPin,4000,5);
          }
          break; // это чтобы статус Activated остался взведённым
         }
        }
       }
      }
      
// дебаг-инфо - в терминал если раскомментировано выше
 #ifdef serialenabled
 Serial.print(F("iteration ="));
 Serial.print (DS18B20iteration);
 Serial.print(F("  channalOn="));
 Serial.print (channalOn); 
 if (digitalRead(RelayMain) == RelayOn) Serial.println(F("  MAIN Relay ON    "));
 else Serial.println(F("  MAIN Relay Of "));
 for (int j=0; j<qtyChannal; j++){
    Serial.print(F("sensor"));
    Serial.print(j,1);
    Serial.print(F(" Temp="));
    Serial.print(Temperature[j], 1);
    Serial.print(F(" TstatTemp="));
    Serial.print(TstatTemp[j], 1);
    Serial.print(F(" Hyster="));
    Serial.print(Hysteresis[j], 1);
    Serial.print(F("  ("));
    Serial.print(DS18B20Temperature[j], 1);
    Serial.print(F("   "));
    Serial.print(DS18B20TempTmp[j], 1);
    Serial.print(F(",corr "));
    Serial.print(TemperatureCorr[j], 1);
    Serial.print(F("),Timer="));
    Serial.print(TstatTimer[j]);
    if (Relay[j]==RelayOn) Serial.println(F(" ON    "));
    else Serial.println(F(" OF   "));
} 
    Serial.println(F("     "));
   #endif
//сообщение о недопустимом снижении температуры
  for (int j=0; j<qtyChannal; j++){
    if ((Temperature[j] < AlarmTemp[j])&(Temperature[j]!=-127))     
      tone(BeepPin,4000,5);
  }
  }   //конец ежесекундных дел
  
// обработка поворота энкодера на изменение канала))
  rotating = true;  // reset the debouncer
  if ((encoderR)|(encoderL)) {
    if (encoderR) {
      channel += 1;
    }
    else
    {
      channel -= 1;
    }
    if ( channel > qtyChannal-1) {      // количество каналов, т.к считаем с нуля
          channel = 0;
        } 
        if ( channel < 0 ) {
          channel = qtyChannal-1;
        }
    encoderR = false;
    encoderL = false;
   
  }
// ================ по нажатию кнопки энкодера - меню настроек ====================
  if(digitalRead(encoderK) == 0) {
    MenuTimeoutTimer = 10; //таймер таймаута, секунд
    lcd.clear();
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("< SETUP > Channel ")); 
    lcd.print(channel);
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    delay(200);
    int menuitem = 0;
   
   
    do {              //обработка пунктов меню (по выбранному каналу)
      rotating = true;  // reset the debouncer
      if ((encoderR)|(encoderL)) {
        MenuTimeoutTimer = 10; //таймер таймаута, секунд
        if (encoderR) {
          menuitem += 1;
        }
        else  {
          menuitem -= 1;
        }
        if ( menuitem > 10 ) {
          menuitem = 0;
        } // границы пунктов меню
        if ( menuitem < 0 ) {
          menuitem = 10;
        }
        encoderR = false;
        encoderL = false;
      }
 
      // индикация пункта меню (номер пункта - в menuitem)
      lcd.setCursor(0, 1); //инфо на LCD
      switch(menuitem)
      {
      case 0:
        lcd.print(F("0.T-ON Relay   "));
        break;
      case 1:
        lcd.print(F("1.TIMER1 SET    ")); 
        break;
      case 2:
        lcd.print(F("2.TIMER2 SET    ")); 
        break;
      case 3:
        lcd.print(F("3.TIMER3 SET    ")); 
        break;     
      case 4:
        lcd.print(F("4.TIMER4 SET    ")); 
        break;     
      case 5:
        lcd.print(F("5.CLOCK SET     ")); 
        break;
      case 6:
        lcd.print(F("6.HYSTERESIS SET")); 
        break;
      case 7:
        lcd.print(F("7.T-CORRECT SET ")); 
        break;
      case 8:
        lcd.print(F("8.SOUND SET     ")); 
        break;
      case 9:
        lcd.print(F("9.T-ALARM SET   ")); 
        break; 
      case 10:
         lcd.print(F("10.BACK          ")); 
        break;         
      }
      if (MenuTimeoutTimer == 0) {
        menuitem = 10;
      }
 
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0)); // если нажата кнопка энкодера или таймаут - обработка пункта меню (номер пункта - в menuitem)
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    switch(menuitem)
    {
      // ====== пункт 0 - выход
    case 10:
      if (BeepEnabled) {
        tone(BeepPin,BeepToneNo,BeepToneNoDuration);
      } //звук "NO"
      break; // case 0 out
 
      // ====== пункт 1 - установка Timer1
    case 1:
      TimerXSetup(0);
      break; // case 1 out
 
      // ====== пункт 2 - установка Timer2
    case 2:
      TimerXSetup(1);
      break; // case 2 out
 
      // ====== пункт 3 - установка Timer3
    case 3:
      TimerXSetup(2);
      break; // case 3 out
 
      // ====== пункт 4 - установка Timer4
    case 4:
      TimerXSetup(3);
      break; // case 4 out
 
      // ====== пункт 5 - установка RTC
    case 5:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP CLOCK")); 
      delay(200);
      RTC.readClock();
      Hours=RTC.getHours();
      Minutes=RTC.getMinutes();
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo)
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          RTC.setHours(Hours);
          RTC.setMinutes(Minutes);
          RTC.setSeconds(0);
          RTC.setClock();
          RTC.start();
        }
        else
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 5 out
 
      // ====== пункт 6 - установка гистерезиса
    case 6:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      HysteresisOld[channel] = Hysteresis[channel];
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP HYSTERESIS")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   "); 
        }
        else {
          lcd.print(Hysteresis[channel], 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          Hysteresis[channel] += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          Hysteresis[channel] -= 0.1;
          encoderL = false;
        }
        Hysteresis[channel] = constrain(Hysteresis[channel], 0.1, 20); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&HysteresisEE[channel]), Hysteresis[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        Hysteresis[channel] = HysteresisOld[channel];
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 6 out
 
      // ====== пункт 7 - установка коррекции температуры
    case 7:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-CORRECT ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (TemperatureCorr[channel] >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(TemperatureCorr[channel], 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          TemperatureCorr[channel] += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          TemperatureCorr[channel] -= 0.1;
          encoderL = false;
        }
        TemperatureCorr[channel] = constrain(TemperatureCorr[channel], -8, 8); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&TemperatureCorrEE[channel]), TemperatureCorr[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        TemperatureCorr[channel] = EEPROM.readFloat(int(&TemperatureCorrEE[channel]));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 7 out     
 
      // ====== пункт 8 - вкл/выкл звука
    case 8:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SOUND SET       ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (BeepEnabled) {
          lcd.print(F("BEEP ON         ")); 
        }     
        else {
          lcd.print(F("BEEP OFF        ")); 
        }
 
        rotating = true;  // reset the debouncer
        if ((encoderR)|(encoderL)) {
          BeepEnabled = !BeepEnabled;
          encoderR = false;
          encoderL = false;
        }
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
        EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
      }
      if (MenuTimeoutTimer == 0) {
        BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
      }
      break; // case 8 out
 
      // ====== пункт 9 - установка предупреждалки о холоде
    case 9:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("ALARM-TEMP SET  ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (AlarmTemp[channel] >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(AlarmTemp[channel], 0); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          AlarmTemp[channel] += 1;
          encoderR = false;
        }
        if (encoderL) {
          AlarmTemp[channel] -= 1;
          encoderL = false;
        }
        AlarmTemp[channel] = constrain(AlarmTemp[channel], 5, 40); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&AlarmTempEE[channel]), AlarmTemp[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        AlarmTemp[channel] = EEPROM.readFloat(int(&AlarmTempEE[channel]));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 9 out
      
      // ====== пункт 10 - установка температуры термостатирования
   case 0:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      TstatTempOld[channel]=TstatTemp[channel];
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-ON Raley")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   "); 
        }
        else {
          lcd.print(TstatTemp[channel], 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
         TstatTemp[channel] += 0.1;
          encoderR = false;
        }
        if (encoderL) {
         TstatTemp[channel] -= 0.1;
          encoderL = false;
        }
        TstatTemp[channel] = constrain(TstatTemp[channel], 1, 80); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        for (int i=0;i<4;i++){
        Timer[i][channel].Activated=false;    
        }
        EEPROM.updateFloat(int(&TstatTempEE[channel]), TstatTemp[channel]); // запись в ЕЕПРОМ  и если питание пропадет, то будет
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
       TstatTemp[channel] = TstatTempOld[channel];
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 10 out
    }
 
    delay(200);
    lcd.clear();
  }
}
  
// ===== SUBROUTINES ==================================================================
 //функция очистки сдвигового регистра
 void cleanreg(){
  for(int i=0; i<8; i++) 
  Relay[i]=!RelayOn;
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, 0);
  digitalWrite(latch, HIGH);
  }
// ========================================
//функция управления сдвиговым регистром
 void sendpin(int pin, boolean state){
  
  Relay[pin]=state;
   
  byte value = 0;
  byte add = 1;
  for(int i=0; i<8; i++){
    if(Relay[i]==HIGH) value+=add;
    add*=2;
  }
  digitalWrite(latch, LOW);
  shiftOut(data, clock, MSBFIRST, value);
  digitalWrite(latch, HIGH);
}
// ========================================
// измеряем температуру  на всех датчиках
 void getTemp(){   
    DS18B20.requestTemperatures(); //запускаем новое измерение   
    for (int j=0; j<qtyChannal; j++){ 
      DS18B20TempTmp[j] = DS18B20.getTempC(sensor[j]); //получаем температуру с каждого датчика
      
      //иногда температура не измеряется, и проскакивают значения -127, среднее значение резко падает и канал меняет состоянее. Нижние 2 строчки помогают. Ничего другого не придумал   
      if (DS18B20TempTmp[j]==-127&Temperature[j]!=-127&Temperature[j]!=0)     //если проскочит значение -127, а ранее фиксировали не -127 и не первое измерение
       DS18B20TempTmp[j]=Temperature[j];                    //проигнорируем его и заменим на среднюю измеренную ранее, а затем просуммирум

      DS18B20Temperature[j] += DS18B20TempTmp[j]; // суммируем для усреднения
    }
    DS18B20iteration ++;
      if (DS18B20iteration == 10){
        DS18B20iteration = 0;
        for (int j=0; j<qtyChannal; j++){
            Temperature[j] = (DS18B20Temperature[j] / 10) + TemperatureCorr[j]; //усреднённая + коррекци
            DS18B20Temperature[j] = 0;
        }
      }
 }
   // ========================================
   void Termostat() {//термостатирование
   for (int j=0; j<8; j++){
     if(Temperature[j]==-127) //если нет датчика, выключаем реле
     {
      if ( Relay[j]!=RelayOn) // если реле включено
      {
       sendpin(j, !RelayOn); // выключить реле;
      }
     }
    else {
    if ( TstatTimer[j] == 0 )       //если пауза между вкл/выкл реле превысила TstatTimerMax;
    {
      if ( Temperature[j] > ( TstatTemp[j] + Hysteresis[j] ) ) // если температра канала > температура заданная+гистерезис
    {
       if ( Relay[j]==RelayOn ) // если реле включено -
      {
        sendpin(j, !RelayOn); // выключить реле; 
        TstatTimer[j] = TstatTimerMax; // реле держать выключённым не менее заданного в TstatTimerMax времени
      }
    }
    if (Temperature[j] < TstatTemp[j])       //если температура канала меньше заданной
    {
      if (  Relay[j]==!RelayOn ) // если реле выключено
      {
        sendpin(j, RelayOn); // включить реле
        TstatTimer[j] = TstatTimerMax; // реле держать включённым не менее заданного в TstatTimerMax времени
      }
    }
    }
   }
   }}
    // ========================================
void RelayMainON () {//вкл и выключение котла
  channalOn=qtyChannal;   //количество вкл каналов=количеству каналов
 for (int j=0; j<qtyChannal; j++){
    if (Relay[j]==!RelayOn) //если канал выключен
      channalOn-=1;         //считаем количество включенных каналов
    }
  if (channalOn==0) {  //если нет включенных реле (нет запроса на тепло ни по одному из каналов)
  if ( digitalRead(RelayMain) == RelayOn)  //если котел включен
  digitalWrite(RelayMain, !RelayOn);    //выключить котел
  }
 
  if (channalOn>0) {  //если есть включенные реле (есть запрос на тепло хотя бы по одному из каналов)
  if ( digitalRead(RelayMain) == !RelayOn)  //если котел выключен
  digitalWrite(RelayMain, RelayOn);    //включить котел
  }
}

// ========================================
void SetTime(char x, char y)
{
  // ========= set hours
  SetH = true;
  do {
    PrintRTC(x,y);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Hours += 1;
      if(Hours > 23) {
        Hours = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Hours -= 1;
      if(Hours < 0) {
        Hours = 23;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  SetH = false;
  delay(200);
  // ========= set minutes
  SetM = true;
  do {
    PrintRTC(0,1);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Minutes += 1;
      if(Minutes > 59) {
        Minutes = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Minutes -= 1;
      if(Minutes < 0) {
        Minutes = 59;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  if (PrintYesNo) {
    SetM = false;
    delay(200);
    // ========= set yes-no
    SetYesNo = false;
    do {
      PrintRTC(0,1);
      rotating = true;  // reset the debouncer
      if ((encoderR)||(encoderL)) {
        SetYesNo = !SetYesNo;
        encoderR = false;
        encoderL = false;
      }
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    delay(200);
  }
 
}
 
// ========================================
void PrintRTC(char x, char y)
{
  lcd.setCursor(x,y);
  if (SetH&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Hours < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Hours);
  }
 
  // мигающее двоеточие, если не в режиме установки времени
  if (!(SetH||SetM||PrintYesNo||blink500ms))
  {
    lcd.print(F(" "));
  }
  else {
    lcd.print(F(":"));
  }
 
  if (SetM&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Minutes < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Minutes);
  }
  lcd.print(F(" "));
 
  if (PrintYesNo) {
    lcd.print(F("["));
    if (!(SetH||SetM||blink500ms))
    {
      lcd.print(F("   "));
    }
    else {
      if (SetYesNo)
      {
        lcd.print(F("YES"));
      }
      else {
        lcd.print(F("NO "));
      }
    }
    lcd.print(F("]"));
  }
 
}
 
// ============================ Encoder interrupts =============================
// Interrupt on A changing state
void doEncoderA(){
  if ( rotating ) {
    delay (1) ;  // wait a little until the bouncing is done
  }
  // Test transition, did things really change?
  if( digitalRead(encoderA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set )
    {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderR = true;
      rotating = false;  // no more debouncing until loop() hits again
    }
  }
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) {
    delay (1);
  }
  if( digitalRead(encoderB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderL = true;
      rotating = false;
    }
  }
}
// ============================ Timer0 interrupt =============================
// run every 500ms
void timerIsr()
{
  blink500ms = !blink500ms; // инверсия мерцающего бита
  if(blink500ms) {
    plus1sec = true; // ежесекундно взводится
    for (int j=0; j<qtyChannal; j++) {
    if (TstatTimer[j] != 0) {
      TstatTimer[j] --; // ежесекундный декремент этого таймера
    }
    }
    if (MenuTimeoutTimer != 0) {
      MenuTimeoutTimer --; // ежесекундный декремент этого таймера
    }
  
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TimerXSetup(int X) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER")); 
      lcd.print(X+1);  // выводим номер таймера на LCD
      delay(200);
      Hours=Timer[X][channel].Hours;
      Minutes=Timer[X][channel].Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer[X][channel].Hours = Hours;
          Timer[X][channel].Minutes = Minutes;
          Timer[X][channel].Enabled = true;
          EEPROM.updateBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
 
          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer"));
          lcd.print(X+1);
          lcd.print(F(" Temp. Set")); 
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     ")); 
            }
            else {
              lcd.print(Timer[X][channel].Temperature, 1); 
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer[X][channel].Temperature += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer[X][channel].Temperature -= 0.1;
              encoderL = false;
            }
            Timer[X][channel].Temperature = constrain(Timer[X][channel].Temperature, 5, 80); // крайние значения
          }
          while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
          EEPROM.updateBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
          EEPROM.readBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }      
 
        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          EEPROM.readBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
          Timer[X][channel].Enabled = false;
          EEPROM.updateBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~````
 

 

 

IRONDED
Offline
Зарегистрирован: 01.03.2017

Огромное спасибо за вашь труд!!!)) Суточный термостат это очень полезная в быту вещь, я его потключил к обогревателю для экономии электроэнегрии.

Я слегка изменил ваш скечь т.к. небыло ds1307 и DS18B20 пришлось поставить ds1302 и DHT 11.

Вот скечь если кому интересно

/*
Суточный термостат для котла Baxi Mainfour 240F
 >Encoder control
 >LCD1602 parallel
 >DS1302 RTC
 >DHT11
 */

//#include <avr\eeprom.h>
#include <Wire.h> // i2c (для RTC)
#include <EEPROMex.h> // EE
#include <LiquidCrystal_I2C.h>
#include <TimerOne.h> // прерывания по таймеру1
#include "DHT.h"
#define DHTPIN A0 

#include <DS1302.h>          // библиотека работы с часами
DS1302 rtc(9, 10, 11);         // Часы на DS1302 9-RST,10-DAT,11-CLK
Time t;

#define encoderA    2 // энкодер - поворот вправо (об землю)
#define encoderB    3 // энкодер - поворот влево (об землю)
#define encoderK    4 // энкодер - кнопка (об землю)
#define BeepPin     11 // пищалка
#define BeepToneNo  2000 // тон звука "No", герц
#define BeepToneYes 4000 // тон звука "Yes", герц
#define BeepToneNoDuration 200 // длительность звука "No", мс
#define BeepToneYesDuration 200 // длительность звука "Yes", мс
#define Relay  12 // нога, к которой подключено реле
#define RelayOn LOW // полярность сигнала включения реде (HIGH/LOW)


LiquidCrystal_I2C lcd(0x3F,16,2); 

DHT dht(DHTPIN, DHT11);

byte block1[8] = { 
  0x06,0x09,0x09,0x06,0x00,0x04,0x0E,0x1F }; // значок градуса с пламенем снизу
byte block2[8] = { 
  0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00 }; // значок градуса

//#define serialenabled // раскомментировать для выдачи в порт отладочной инфы

#define TstatTimerMax 180 //минимальная пауза между включениями горелки, сек
unsigned int TstatTimer = 20; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса

float DHT11Temperature = 0; //сырая температура от датчика
float Temperature = 0; //вычисленная температура с коррекцией
float DHT11TempTmp; //времянка
byte DHT11iteration = 0; //счётчик измерений температуры для усреднения

float TstatTemp = 23; //температура термостатирования, может изменяться настройками
float TemperatureCorr = 0; //коррекция температуры, может изменяться настройками
float Hysteresis = 0.1; // гистерезис термостата, может изменяться настройками
float HysteresisOld;

int Hour = 0; // времянка часов RTC для отображения и установки
int Minutes = 0; // времянка минут RTC для отображения и установки
int Seconds;
boolean PrintYesNo = false; // показывать ли после времени Yes/No (косвенно - указание на режим установка/отображение)
boolean SetH = false; // выделение часов при отображении
boolean SetM = false; // выделение минут при отображении
boolean SetYesNo = false; // выделение Yes/No при установке часов

boolean blink500ms = false; // мигающий бит, инвертируется каждые 500мс
boolean plus1sec = false; // ежесекундно взводится

boolean BeepEnabled = true;

byte MenuTimeoutTimer;

// структура для суточных таймеров (8 байт)
struct buffer_template {
  byte Hour;
  byte Minutes;
  float Temperature;
  boolean Enabled;
  boolean Activated;
};
buffer_template Timer[4] = {
  0, 0, 23.0, false, false}; //объявление 4-х суточных таймеров и их начальные значения

float AlarmTemp = 20; // температура для замерзательного орала

// encoder vars
static boolean rotating=false;      // debounce management
boolean A_set = false;              
boolean B_set = false;
boolean encoderR = false;
boolean encoderL = false;

// EEPROM
EEMEM float TstatTempEE; //EE температура термостатирования
EEMEM float TemperatureCorrEE; // EE коррекция температуры
EEMEM float HysteresisEE; // EE гистерезис
EEMEM boolean BeepEnabledEE; // EE признак разрешения звука
EEMEM float AlarmTempEE; // EE значение недопустимого снижения температуры
EEMEM buffer_template TimerEE[4]; // EE структуры для 4 суточных таймеров

// ===== SETUP ========================================================================
void setup() {
  lcd.init();
  lcd.backlight();
#ifdef serialenabled
  Serial.begin(9600);
#endif
  pinMode(Relay, OUTPUT);
  digitalWrite(Relay, HIGH);
  lcd.begin(16, 2);
  lcd.createChar(1, block1);
  lcd.createChar(2, block2);
  pinMode(encoderA, INPUT);
  digitalWrite(encoderA, HIGH);
  pinMode(encoderB, INPUT);
  digitalWrite(encoderB, HIGH);
  pinMode(encoderK, INPUT);
  digitalWrite(encoderK, HIGH);
  attachInterrupt(0, doEncoderA, CHANGE);   // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 (pin 3)
  Timer1.initialize(500000); // Timer0 interrupt - set a timer of length 500000 microseconds
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
  EEPROM.setMaxAllowedWrites(32767);
  if ((digitalRead(encoderK)) == 0) 
  { // если первая запись однокристалки (зажата кнопка при включении питания)- записать начальные значения в EE
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("Cold start..."));
    for (int i=0; i<4; i++)
    {
      //      Timer[i].Hours = Timer[i].Minutes = 0;
      //      Timer[i].Temperature = 23.0;
      //      Timer[i].Enabled = Timer[i].Activated = false;
      EEPROM.updateBlock(int(&TimerEE[i]), Timer[i]);
    }
    EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);
    EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
    EEPROM.updateFloat(int(&TemperatureCorrEE), TemperatureCorr);
    EEPROM.updateFloat(int(&HysteresisEE), Hysteresis);
    EEPROM.updateFloat(int(&AlarmTempEE), AlarmTemp);
    tone(BeepPin,2000,50);
    delay(50);
    tone(BeepPin,3000,50);
    delay(50);
    tone(BeepPin,4000,50);
    delay(1000);  
  }
  lcd.clear();
  lcd.setCursor(0, 0); //инфо на LCD
  lcd.print(F("Read settings..."));
  BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
  TstatTemp = EEPROM.readFloat(int(&TstatTempEE));
  TemperatureCorr = EEPROM.readFloat(int(&TemperatureCorrEE));
  Hysteresis = EEPROM.readFloat(int(&HysteresisEE));
  AlarmTemp = EEPROM.readFloat(int(&AlarmTempEE));
  for (int i=0; i<4; i++)
  {
    EEPROM.readBlock(int(&TimerEE[i]), Timer[i]);
  }


  tone(BeepPin,4000,50);
  delay(100);  
  tone(BeepPin,4000,50);
  delay(1000);  
  lcd.clear();

}

// ===== MAIN CYCLE ===================================================================
void loop() {
  lcd.setCursor(8, 0); //инфо на LCD
  if ((Temperature < AlarmTemp)&(blink500ms)) {
    lcd.print(F("*"));
  } 
  else {
    lcd.print(F(" "));
  }
  lcd.print(F("t="));
  if (Temperature < 10) {
    lcd.print(F(" "));
  }
  lcd.print(Temperature,1);
  lcd.write(0x02); // значок градуса

  // если таймер включен - надпись светится, если сработал - мигает, обрабатываем все 4 таймера
  lcd.setCursor(0, 1); //инфо на LCD
  for (int i=0;i<4;i++){
    if ((Timer[i].Enabled)&!((Timer[i].Activated)&(blink500ms))) {
      lcd.print(F("T"));
      lcd.print(i+1);
    } 
    else { 
      lcd.print(F("  "));
    }
  }

  lcd.setCursor(9, 1); //инфо на LCD
  lcd.print(F("s="));
  lcd.print(TstatTemp,1);
  if ( digitalRead(Relay) == RelayOn ) { 
    lcd.write(0x01); // значок градуса с пламенем
  } 
  else { 
    lcd.write(0x02); // значок градуса
  }

  // печатаем текущее время
  PrintYesNo = false;
  PrintRTC(0,0);

  // термостатирование
  if ( TstatTimer == 0 )
  {
    if ( Temperature > ( TstatTemp + Hysteresis ) ) // гистерезис
    {
      if ( digitalRead(Relay) == RelayOn ) // если горелка включена - 
      {
        digitalWrite(Relay, !RelayOn); // выключить горелку
        TstatTimer = TstatTimerMax; // горелку держать выключённой не менее заданного в TstatTimerMax времени
      }
    }
    if (Temperature < TstatTemp)
    {
      if ( digitalRead(Relay) == !RelayOn ) // если горелка выключена - 
      {
        digitalWrite(Relay, RelayOn); // включить горелку
        TstatTimer = TstatTimerMax; // горелку держать включённой не менее заданного в TstatTimerMax времени
      }
    }
  }

  // если прошла 1 секунда - делаем ежесекундные дела
  if (plus1sec) {
    plus1sec = false; // сбрасываем до следующей секунды
    t = rtc.getTime();// обновляем часы
    Hour = t.hour;
    Minutes = t.min;
    Seconds = t.sec;

    // измеряем температуру воздуха
    DHT11TempTmp = dht.readTemperature(); // получить температуру от датчика
    if (DHT11TempTmp != -127)
    {
      DHT11Temperature += DHT11TempTmp; // суммируем для усреднения
      DHT11iteration ++;
      if (DHT11iteration == 10)
      {
        DHT11iteration = 0;
        Temperature = (DHT11Temperature / 10) + TemperatureCorr; //усреднённая + коррекция
        DHT11Temperature = 0;
      }
    }

    // если уставку термостата поменяли вручную - запись её в EE, не чаще 1 раза в минуту
    //(экономия ресурса EE)
    if ((EEPROM.readFloat(int(&TstatTempEE)) != TstatTemp)&(Seconds == 0)) {
      EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);
    }

    // проверка таймеров и изменение уставки термостата при совпадении  
    for (int i=0;i<4;i++)
    {
      if ((Hour == Timer[i].Hour)&(Minutes == Timer[i].Minutes)&(Timer[i].Enabled)&(Seconds == 0)) { // время T совпадает с RTC
      Timer[0].Activated = Timer[1].Activated = Timer[2].Activated = Timer[3].Activated = false;
        Timer[i].Activated = true;
        TstatTemp = Timer[i].Temperature;
        EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);
        if (BeepEnabled) {
          tone(BeepPin,4000,5);
        }
        break; // это чтобы статус Activated остался взведённым
      }
    }

    // дебаг-инфо - в терминал
#ifdef serialenabled
    Serial.print(F("Temp="));
    Serial.print(Temperature, 1);
    Serial.print(F("("));
    Serial.print(DHT11Temperature, 4);
    Serial.print(F(",corr "));
    Serial.print(TemperatureCorr, 1);
    Serial.print(F("),TstatTimer="));
    Serial.println(TstatTimer);
#endif

    if (Temperature < AlarmTemp) {
      tone(BeepPin,4000,5);
    }
  }

  // обработка поворота энкодера на лету (ручное изменение уставки температуры))
  rotating = true;  // reset the debouncer
  if ((encoderR)|(encoderL)) {
    if (encoderR) {
      TstatTemp += 0.1;
    } 
    else 
    {
      TstatTemp -= 0.1;
    }
    TstatTemp = constrain(TstatTemp, 10, 35);
    encoderR = false;
    encoderL = false;
    Timer[0].Activated = Timer[1].Activated = Timer[2].Activated = Timer[3].Activated = false;
  }

  // ================ по нажатию кнопки энкодера - меню настроек ====================
  if(digitalRead(encoderK) == 0) {
    MenuTimeoutTimer = 10; //таймер таймаута, секунд
    lcd.clear();
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("< SETUP >"));  
    if (BeepEnabled) { 
      tone(BeepPin,4000,50); 
    }
    delay(200);
    int menuitem = 0;

    do {
      rotating = true;  // reset the debouncer
      if ((encoderR)|(encoderL)) {
        MenuTimeoutTimer = 10; //таймер таймаута, секунд
        if (encoderR) { 
          menuitem += 1; 
        } 
        else  { 
          menuitem -= 1; 
        }
        if ( menuitem > 9 ) { 
          menuitem = 0; 
        } // границы пунктов меню
        if ( menuitem < 0 ) { 
          menuitem = 9; 
        }
        encoderR = false;
        encoderL = false;
      }

      // индикация пункта меню (номер пункта - в menuitem)
      lcd.setCursor(0, 1); //инфо на LCD
      switch(menuitem)
      {
      case 0:
        lcd.print(F("0.BACK          "));  
        break;
      case 1:
        lcd.print(F("1.TIMER1 SET    "));  
        break;
      case 2:
        lcd.print(F("2.TIMER2 SET    "));  
        break;
      case 3:
        lcd.print(F("3.TIMER3 SET    "));  
        break;      
      case 4:
        lcd.print(F("4.TIMER4 SET    "));  
        break;      
      case 5:
        lcd.print(F("5.CLOCK SET     "));  
        break;
      case 6:
        lcd.print(F("6.HYSTERESIS SET"));  
        break;
      case 7:
        lcd.print(F("7.T-CORRECT SET "));  
        break;
      case 8:
        lcd.print(F("8.SOUND SET     "));  
        break;
      case 9:
        lcd.print(F("9.T-ALARM SET   "));  
        break;      
      }
      if (MenuTimeoutTimer == 0) {
        menuitem = 0;
      }

    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    // если нажата кнопка энкодера или таймаут - обработка пункта меню (номер пункта - в menuitem)
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    } 
    switch(menuitem)
    {
      // ====== пункт 0 - выход
    case 0: 
      if (BeepEnabled) { 
        tone(BeepPin,BeepToneNo,BeepToneNoDuration); 
      } //звук "NO"
      break; // case 0 out

      // ====== пункт 1 - установка Timer1
    case 1:
      TimerXSetup(0);
      break; // case 1 out

      // ====== пункт 2 - установка Timer2
    case 2:
      TimerXSetup(1);
      break; // case 2 out

      // ====== пункт 3 - установка Timer3
    case 3:
      TimerXSetup(2);
      break; // case 3 out

      // ====== пункт 4 - установка Timer4
    case 4:
      TimerXSetup(3);
      break; // case 4 out

      // ====== пункт 5 - установка RTC
    case 5:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP CLOCK"));  
      delay(200);
      t = rtc.getTime();
      Hour = t.hour;
      Minutes = t.min;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo)
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }       
           rtc.halt(false);
           rtc.writeProtect(false);
           rtc.setTime(Hour, Minutes, 00);    // Установка времени ЧАС(реальное время):30:00
           delay (2000);                    // Задержка на перевод стрелок :-)
        }
        else
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 5 out

      // ====== пункт 6 - установка гистерезиса
    case 6:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      HysteresisOld = Hysteresis;
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP HYSTERESIS"));  
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   ");  
        } 
        else {
          lcd.print(Hysteresis, 1);  
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          Hysteresis += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          Hysteresis -= 0.1;
          encoderL = false;
        }
        Hysteresis = constrain(Hysteresis, 0.1, 1); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&HysteresisEE), Hysteresis); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        Hysteresis = HysteresisOld;
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 6 out

      // ====== пункт 7 - установка коррекции температуры 
    case 7:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-CORRECT "));  
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    "));  
        } 
        else {
          if (TemperatureCorr >= 0) {
            lcd.print(F("+")); 
          }

          lcd.print(TemperatureCorr, 1);  
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          TemperatureCorr += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          TemperatureCorr -= 0.1;
          encoderL = false;
        }
        TemperatureCorr = constrain(TemperatureCorr, -8, 8); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));

      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&TemperatureCorrEE), TemperatureCorr); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        TemperatureCorr = EEPROM.readFloat(int(&TemperatureCorrEE));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 7 out      

      // ====== пункт 8 - вкл/выкл звука
    case 8:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SOUND SET       "));  
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (BeepEnabled) {
          lcd.print(F("BEEP ON         "));  
        }      
        else {
          lcd.print(F("BEEP OFF        "));  
        }

        rotating = true;  // reset the debouncer
        if ((encoderR)|(encoderL)) {
          BeepEnabled = !BeepEnabled;
          encoderR = false;
          encoderL = false;
        }
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));

      if (MenuTimeoutTimer != 0) {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
        EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
      }
      if (MenuTimeoutTimer == 0) {
        BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
      }
      break; // case 8 out

      // ====== пункт 9 - установка предупреждалки о холоде 
    case 9:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("ALARM-TEMP SET  "));  
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    "));  
        } 
        else {
          if (AlarmTemp >= 0) {
            lcd.print(F("+")); 
          }

          lcd.print(AlarmTemp, 0);  
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          AlarmTemp += 1;
          encoderR = false;
        }
        if (encoderL) {
          AlarmTemp -= 1;
          encoderL = false;
        }
        AlarmTemp = constrain(AlarmTemp, 15, 30); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));

      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&AlarmTempEE), AlarmTemp); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        AlarmTemp = EEPROM.readFloat(int(&AlarmTempEE));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 9 out

    }

    delay(200);
    lcd.clear();
  }
}

// ===== SUBROUTINES ==================================================================

// ========================================
void SetTime(char x, char y)
{
  // ========= set hours
  SetH = true;
  do {
    PrintRTC(x,y);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Hour += 1;
      if(Hour > 23) {
        Hour = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Hour -= 1;
      if(Hour < 0) {
        Hour = 23;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  SetH = false;
  delay(200);
  // ========= set minutes
  SetM = true;
  do {
    PrintRTC(0,1);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Minutes += 1;
      if(Minutes > 59) {
        Minutes = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Minutes -= 1;
      if(Minutes < 0) {
        Minutes = 59;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  if (PrintYesNo) {
    SetM = false;
    delay(200);
    // ========= set yes-no
    SetYesNo = false;
    do {
      PrintRTC(0,1);
      rotating = true;  // reset the debouncer
      if ((encoderR)||(encoderL)) {
        SetYesNo = !SetYesNo;
        encoderR = false;
        encoderL = false;
      }
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    delay(200);
  }

}

// ========================================
void PrintRTC(char x, char y)
{
  lcd.setCursor(x,y);
  if (SetH&&blink500ms) {
    lcd.print(F("  ")); 
  } 
  else {
    if (Hour < 10) { 
      lcd.print(F("0")); 
    }    
    lcd.print(Hour);
  }

  // мигающее двоеточие, если не в режиме установки времени
  if (!(SetH||SetM||PrintYesNo||blink500ms))
  {
    lcd.print(F(" "));
  } 
  else {
    lcd.print(F(":"));
  }

  if (SetM&&blink500ms) {
    lcd.print(F("  ")); 
  } 
  else {
    if (Minutes < 10) { 
      lcd.print(F("0")); 
    }    
    lcd.print(Minutes);
  }
  lcd.print(F(" "));

  if (PrintYesNo) {
    lcd.print(F("["));
    if (!(SetH||SetM||blink500ms))
    {
      lcd.print(F("   "));
    }
    else {
      if (SetYesNo)
      {
        lcd.print(F("YES"));
      } 
      else {
        lcd.print(F("NO "));
      }
    }
    lcd.print(F("]"));
  }

}

// ============================ Encoder interrupts =============================
// Interrupt on A changing state
void doEncoderA(){
  if ( rotating ) {
    delay (1) ;  // wait a little until the bouncing is done
  }
  // Test transition, did things really change? 
  if( digitalRead(encoderA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set )
    {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderR = true;
      rotating = false;  // no more debouncing until loop() hits again
    }
  }
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) {
    delay (1);
  }
  if( digitalRead(encoderB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderL = true;
      rotating = false;
    }
  }
}
// ============================ Timer0 interrupt =============================
// run every 500ms
void timerIsr()
{
  blink500ms = !blink500ms; // инверсия мерцающего бита
  if(blink500ms) {
    plus1sec = true; // ежесекундно взводится
    if (TstatTimer != 0) {
      TstatTimer --; // ежесекундный декремент этого таймера
    }
    if (MenuTimeoutTimer != 0) {
      MenuTimeoutTimer --; // ежесекундный декремент этого таймера
    }
  }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TimerXSetup(int X) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER"));  
      lcd.print(X+1);  // выводим номер таймера на LCD
      delay(200);
      Hour=Timer[X].Hour;
      Minutes=Timer[X].Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer[X].Hour = Hour;
          Timer[X].Minutes = Minutes;
          Timer[X].Enabled = true;
          EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);

          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer"));
          lcd.print(X+1);
          lcd.print(F(" Temp. Set"));  
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     "));  
            } 
            else {
              lcd.print(Timer[X].Temperature, 1);  
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer[X].Temperature += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer[X].Temperature -= 0.1;
              encoderL = false;
            }
            Timer[X].Temperature = constrain(Timer[X].Temperature, 10, 35); // крайние значения
          }
          while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));

          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
          EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
          EEPROM.readBlock(int(&TimerEE[X]), Timer[X]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }     

        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          EEPROM.readBlock(int(&TimerEE[X]), Timer[X]);
          Timer[X].Enabled = false;
          EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~````

 

IRONDED
Offline
Зарегистрирован: 01.03.2017

И схема подключения в догонку:

DHC11  OUT - A0

lcd 1602 i2c SDA-A4, SCL-A5

RTC DS1302 RST-D9, DAT-D10, CLK-D11

Encoder(Keyes) CLK-D2, DT-D3, SW-D4

Твердотельное реле D12+, GND-

eXL
Offline
Зарегистрирован: 23.07.2017

Народ, кто нибуть может дать список библиотек использованных в авторском скетче. Что-то я понакачал стандартных библиотек с Arduino.cc у них имена другие, переименовывал, переименовывал и все равно ошибки вылазят при подключении библиотек. Может не все нашел ? Нахрена их надо было переименовывать в проекте ?

vlad219i
Offline
Зарегистрирован: 05.03.2014

eXL пишет:

Народ, кто нибуть может дать список библиотек использованных в авторском скетче. Что-то я понакачал стандартных библиотек с Arduino.cc у них имена другие, переименовывал, переименовывал и все равно ошибки вылазят при подключении библиотек. Может не все нашел ? Нахрена их надо было переименовывать в проекте ?

Список библиотек - в первых строках проекта:

#include <Wire.h> // i2c (для RTC)
#include <RealTimeClockDS1307.h> // RTC
#include <EEPROMex.h> // EE
#include <LiquidCrystal.h> // LCD 16*2
#include <TimerOne.h> // прерывания по таймеру1
#include <OneWire.h> // 1wire для DS18B20
#include <DallasTemperature.h> // DS18B20
 
В проекте я ничего не переименовывал.

 

eXL
Offline
Зарегистрирован: 23.07.2017

vlad219i пишет:

Список библиотек - в первых строках проекта:

#include <Wire.h> // i2c (для RTC)
#include <RealTimeClockDS1307.h> // RTC
#include <EEPROMex.h> // EE
#include <LiquidCrystal.h> // LCD 16*2
#include <TimerOne.h> // прерывания по таймеру1
#include <OneWire.h> // 1wire для DS18B20
#include <DallasTemperature.h> // DS18B20
 
В проекте я ничего не переименовывал.

 

Что библиотеки должны подключаться в первых строках командой #includ я в курсе.

Но что то не получается.

eXL
Offline
Зарегистрирован: 23.07.2017

...

C:\skech\skech.ino:13:40: fatal error: RealTimeClockDS1307.h: No such file or directory

 #include <RealTimeClockDS1307.h> // RTC

                                        ^

compilation terminated.

Используем библиотеку Wire версии 1.0 из папки: C:\Electronics\Arduino\hardware\arduino\avr\libraries\Wire
exit status 1
Ошибка компиляции для платы Arduino Pro or Pro Mini.

Пишет вот это, я понимаю что нет библиотеки в указанной папке.

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

У тебя не установлена библиотека  RealTimeClockDS1307

eXL
Offline
Зарегистрирован: 23.07.2017

Запихал в librares RealTimeClockDS1307,   теперь не может найти другую -

...

C:\skech\skech.ino:14:28: fatal error: EEPROMex.h: No such file or directory

 #include <EEPROMex.h> // EE

                            ^

compilation terminated.

Используем библиотеку Wire версии 1.0 из папки: C:\Electronics\Arduino\hardware\arduino\avr\libraries\Wire
Используем библиотеку RealTimeClockDS1307 в папке: C:\Electronics\Arduino\libraries\RealTimeClockDS1307 (legacy)
exit status 1
Ошибка компиляции для платы Arduino Pro or Pro Mini.

eXL
Offline
Зарегистрирован: 23.07.2017

Запихал    EEPROMex теперь другая -

...

C:\skech\skech.ino:16:67: fatal error: TimerOne.h: No such file or directory

 #include <TimerOne.h> // прерывания РїРѕ таймеру1

                                                                   ^

compilation terminated.

Используем библиотеку Wire версии 1.0 из папки: C:\Electronics\Arduino\hardware\arduino\avr\libraries\Wire
Используем библиотеку RealTimeClockDS1307 в папке: C:\Electronics\Arduino\libraries\RealTimeClockDS1307 (legacy)
Используем библиотеку EEPROMEx версии 1.0.0 из папки: C:\Electronics\Arduino\libraries\EEPROMEx
Используем библиотеку LiquidCrystal версии 1.0.7 из папки: C:\Electronics\Arduino\libraries\LiquidCrystal
exit status 1
Ошибка компиляции для платы Arduino Pro or Pro Mini.

eXL
Offline
Зарегистрирован: 23.07.2017

В дистрибутиве вообще никаких библиотек нет чтоли ? Их все нужно добавлять ?

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

Ну так найди и установи её...

P.S. Все пользователские библиотеки вставляй сюда - Мои документы\Arduino\libraries\  . Не надо лезть в папку, где находятся стандартные библиотеки Arduino IDE.

eXL
Offline
Зарегистрирован: 23.07.2017

Jeka_M пишет:

Ну так найди и установи её...

P.S. Все пользователские библиотеки вставляй сюда - Мои документы\Arduino\libraries\  . Не надо лезть в папку, где находятся стандартные библиотеки Arduino IDE.

Я их туда и пихаю.

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

eXL пишет:

В дистрибутиве вообще никаких библиотек нет чтоли ? Их все нужно добавлять ?

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

Из стандартных там используется всего две: Wire и LiquidCrystal, остальные все сторонние.

eXL
Offline
Зарегистрирован: 23.07.2017

TimerOne нашел аж 4 штуки-

TimerOne

TimerOne v9

TimerOne r11

TimerOne-master

Все с сайта Arduino.cc

Какой пихать то ?

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

eXL пишет:

Я их туда и пихаю.

Нет, не туда:

eXL пишет:

Используем библиотеку RealTimeClockDS1307 в папке: C:\Electronics\Arduino\libraries\RealTimeClockDS1307
Используем библиотеку EEPROMEx версии 1.0.0 из папки: C:\Electronics\Arduino\libraries\EEPROMEx

Надо сюда: Мои документы\Arduino\libraries\