Термостат газового котла

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Доброго времени суток, уважаемые!

Довел до рабочей и месячно оттестированной версии термостат для газового котла. И представляю его на ваш суд. 

Небольшая предыстория - главная проблема моего котла в том, что он стоит в подвале и имеет чисто электро-механическое управление (термостат). Чтобы поменять температуру батарей надо идти в подвал. Осенью и весной, когда дневная и ночная температуры сильно отличаются это делать просто лениво. Поэтому или холодно или жарко. В начале октября механический термостат помер. Я его починил, но понял, что надо делать электронный с управлением возле себя. В итоге получилось вот это чудо.

Программа управления:

//ver. 2
#include <OneWire.h>  // on pin 2 (a 4.7K resistor is necessary)
#include <LiquidCrystal.h>
#include <EEPROM.h> 

#define NoSensor 0
#define ErrCRC 1
#define BUTTON_NONE 0
#define BUTTON_RIGHT 1
#define BUTTON_UP 2
#define BUTTON_DOWN 3
#define BUTTON_LEFT 4
#define BUTTON_SELECT 5

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

 OneWire  ds(2);// Датчик на порту 2
byte fire1[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00100,
  B01110,
  B11011,
}; 
byte fire2[8] =
{
  B00000,
  B00100,
  B00100,
  B01110,
  B01110,
  B11011,
  B11011,
}; 
byte fire3[8] =
{
  B00100,
  B01110,
  B01110,
  B01110,
  B11011,
  B11011,
  B11011,
}; 



const int Gorelka=11; //Порт горелки используем нормально замкнутый контакт

const int Auto=3; //Порт включения автоматики
const int TimeStrobe=500; // Время стробирования


boolean flagNagrev; //Флаг нагрева
boolean flagAnim;

int Geteresis = 5; //Гетерезис температуры изначально 5 градусов
unsigned long lastMillis;
byte temperatureStrobe;
byte sensorStrobe;

byte Tset;
byte plam;
int  Ttn;

byte addrIN[8]; //={0x10,0x31,0xC5,0xC8,0x0,0x0,0x0,0xF4}; // Адрес датчика теплоносителя


void setup(void) {
  Serial.begin(9600);
    Tset=EEPROM.read(0);
    if ((Tset <20)|(Tset>80)) {
      Tset=20;
      EEPROM.write(0,Tset);
    }
    
    lcd.begin(16, 2);
    lcd.createChar(1, fire1);
    lcd.createChar(2, fire2);
    lcd.createChar(3, fire3);
    lcd.clear();

    pinMode(Gorelka, OUTPUT);
    pinMode(Auto, OUTPUT);

    digitalWrite(Gorelka, HIGH);
    digitalWrite(Auto, HIGH);
    Serial.println("Auto OFF");
      Serial.println("Gorelka OFF");
      plam=0;

 
    if(!datchikEnable()) SensorsError(NoSensor);

    do {
      Ttn = Temperatura(&addrIN[0]);
      } while (Ttn>82);
    lcd.setCursor(0,0);
    lcd.print("Ttn =");
    lcd.print(Ttn);
    lcd.print("C"); 
      Serial.println(Ttn);
    lastMillis=millis();
    temperatureStrobe=0;
    sensorStrobe=0;
    flagNagrev=false;
    flagAnim=false;

    lcd.setCursor(0,1);
    lcd.print("Tset=");
    lcd.print(Tset);
    lcd.print("C");    
    digitalWrite(Auto, LOW); //Включаем автоматику 
    lcd.setCursor(15,1);
    lcd.print("A");
 }

void loop(void) {
// ***************Стробирование
  if ((millis()-lastMillis)>= TimeStrobe) { //стробируем раз в пол-секунды
    lastMillis=millis();
    temperatureStrobe++;
    sensorStrobe++;

    flagAnim=true;
  }

// *************Считывание кнопки

  switch (getPressedButton()){
    case BUTTON_UP:
      Tset++; //увеличение температуры на 1 градус
      if (Tset>80) Tset=80;
      lcdPrintTset(Tset);
      EEPROM.write(0,Tset);
      break;
    case BUTTON_DOWN:
      Tset--; //уменьшение температуры на 1 градус
      if (Tset<20) Tset=20;
      lcdPrintTset(Tset);
      EEPROM.write(0,Tset);
      break;
    case BUTTON_RIGHT:
      Tset+=5; //уменьшение температуры на 1 градус
      if (Tset>80) Tset=80;
      lcdPrintTset(Tset);
      EEPROM.write(0,Tset);
      break;
    case BUTTON_LEFT:
      Tset-=5; //уменьшение температуры на 1 градус
      if (Tset<20) Tset=20;
      lcdPrintTset(Tset);
      EEPROM.write(0,Tset);
      break; 
    case BUTTON_SELECT:
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Ttn =");
      lcd.print(Ttn);
      lcd.print("C");
      lcdPrintTset(Tset);
      lcd.setCursor(15,1);
      lcd.print("A");                     
     
  }

// *************Управление горелкой
  
  if (flagNagrev){   // Если горит
    if (temperatureStrobe==10){ //Смотрим каждые 5 секунд
      temperatureStrobe=0;
      do {
      Ttn = Temperatura(&addrIN[0]);
      } while (Ttn>82);
      lcd.setCursor(0,0);
      lcd.print("Ttn =");
      lcd.print(Ttn);
      lcd.print("C"); 
      if (Ttn>=Tset) {
        digitalWrite(Gorelka, HIGH); //Выключаем горелку
        flagNagrev=false;
        lcd.setCursor(15,0);
        lcd.print(" ");
      }
    }
  } else{   //Если не горит
     if (temperatureStrobe==40){ //Смотрим каждые 20 секунд
      temperatureStrobe=0;
      do {
      Ttn = Temperatura(&addrIN[0]);
      } while (Ttn>82);
      lcd.setCursor(0,0);
      lcd.print("Ttn =");
      lcd.print(Ttn);
      lcd.print("C"); 
      if ( Ttn <=(Tset-Geteresis)) {
        digitalWrite(Gorelka, LOW); //Включаем горелку
        flagNagrev=true;

      }    
     } 
  }

//*******************Проверка датчика
  if (sensorStrobe==240){ //Проверяем наличие датчика каждые 120 секунд
    sensorStrobe=0;
    if(!datchikEnable()) SensorsError(NoSensor); //проверяем наличие датчика
  }
//**************************Отображение пламени
  if (flagNagrev){
    if (flagAnim){
      lcdAnim();
      flagAnim=false;
    }
  }



}

//Считывание температуры по адресу датчика и проверка контрольной суммы
float Temperatura(byte* addr){
//  byte data;
  byte i;

  int16_t raw;
  byte data[12];


  for (int k=0; k<6; k++){
      ds.reset();
      ds.select(addr);
      ds.write(0x44, 1);        // start conversion, with parasite power on at the end
   
  delay(1000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
   
  ds.reset();
  ds.select(addr); 
  ds.write(0xBE);         // Read Scratchpad
   for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    }
  if (OneWire::crc8(data, 8) == data[8]) { // Проверка контрольной суммы
  raw = (data[1] << 8) | data[0];
//    raw = raw << 3; // 9 bit resolution default
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
     

  return (float)raw / 16.0;
  }
  delay(500);
  
  }

     SensorsError(ErrCRC);
     return 0;
  
}

// ***********************ПП ошибки датчиков **************************
void SensorsError(int err) {
  
    digitalWrite(Auto, HIGH); //Отключаем автоматику
    lcd.setCursor(15,1);
    lcd.print(" ");
    switch (err) {
      case NoSensor:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("No sensor");
        break; 
      case ErrCRC:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("CRC error");  
        break;      
    }
    Serial.println("ERROR");
    while (true); //затыкаемся здесь
       
}

  
  //**********************************ПП Считывание нажатия клавиш************************************
  int getPressedButton() {
  int button;
  int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0) 
  if (buttonValue > 1000) {
    return BUTTON_NONE;
  }
  else if (buttonValue < 100) {
    button = BUTTON_RIGHT;  
  }
  else if (buttonValue < 200) {
    button = BUTTON_UP;
  }
  else if (buttonValue < 400){
    button = BUTTON_DOWN;
  }
  else if (buttonValue < 600){
    button = BUTTON_LEFT;
  }
  else if (buttonValue < 800){
    button = BUTTON_SELECT;
  }else {
    return BUTTON_NONE;
  }
 
  while (analogRead(0)<1000);
  Serial.println(button);

  return button;
}

//*******************************пп проверки наличая датчика********************************************

boolean datchikEnable(){
    int k;

    k=0;
      
   while (k<6){    // Опрашиваем до 6 раз
    ds.reset();
    if (ds.search(addrIN)){
//     Serial.println("break");       
      break;{ //Если датчик есть выходим 
    }
 
    delay(500); //Задержка 2 сек    
    k++; 
    }
      ds.reset_search(); //Обнуляем опрос датчиков 
   }
                  for( int i = 0; i < 8; i++) {
// Обнуляем

   
          }

  if (k >= 5){
    return false; //отсутствует датчик с заданным адресом
  } else {
    return true;
  }
  
}


void lcdPrintTset(byte Tt){
  lcd.setCursor(0,1);
  lcd.print("Tset=");
  lcd.print(Tt);
  lcd.print("C");       
}

void lcdAnim(){
  lcd.setCursor(15,0);
  switch (plam){
    case 0:
      plam++;
      lcd.print("\1");
    break;
    case 1:
      plam++;
      lcd.print("\2");
    break;
    case 2:
      plam=0;
      lcd.print("\3");
    break;  
  }
  
}

Что может:

1. Отслеживать температуру теплоносителя. При достижении установленной величины выключает горелку. При опускании температуры ниже установленной минус гетерезис - включает горелку.

2. Запоминает установленную температуру в EEPROM

3. Отслеживает наличие датчика температуры. При его отсутствии отключает автоматику.

4. Анимирует процесс горения :)

5. Реализует многозадачность на очень малых скоростях.

Из чего сделано:

1. Arduino UNO

2. LCD 1602 keyboard shield

3. Датчик DS12b28 в корпусе

4. Блок реле с опторазвязкой

5. Витая пара 6 категории (другой не было)

6. Распаячная коробка

7. Синяя изолента!

Проблемы:

1. Периодические ошибки датчика температуры - он выдает "левые" показания при запуске клапанов котла. Я их отсекаю программно, поскольку почему-то выдается значение больше 82 градусов. Запрашивать температуру несколько раз просто так не хочется.

2. Периодически экран выдает "кракозябры". Почему не понимаю, но добавил кнопку для обновления экрана.

3. Попытался гасить подсветку экрана по 10 пину, но он или гасится "насмерть" или не выключается. Буду копать...

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Забыл - еще светодиоды 5мм с обкусанными ножками как кнопки :) 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Гистерезис - а то слух режет

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

ua6em пишет:

Гистерезис - а то слух режет

Да самому режет - перечитал, хотел исправить, а не позволяют первое сообщение никак...