Heizung Aduino Mega


DIE HIER GEZEIGTEN SCRIPTE SOLLEN LEDIGLICH ALS ANREGUNG DIENEN. FÜR EVENTUELLE SCHÄDEN BEIM NACHBAU KANN KEINE HAFTUNG ÜBERNOMMEN WERDEN!

Problem:

Die EBV Heizungsregelung mit der Bezeichnung "Delta 2B" hat das Zeitliche gesegnet.

Es handelt sich hierbei um eine Witterungsgeführte einstufige Heizungsregelung mit drei NTC Temperaturfühler für Außen,Kessel und Speicher und vier Relais für Brenner, Heiz-,Speicher- und Zirkulationspumpe.

Also wen Neu, dann richtig.

Lösung:

Inhalt:

Temperaturen

Als steuerndes Element wurde ein Arduino Microcontroller gewählt, da dieser vielseitig und günstig ist. Die vorhandenen Temperaturfühler (NTC 10kOhm) werden an den Analogeingängen eingelesen. Damit dies möglich ist benötigen wir einen Spannungsteiler mit ebenfalls 10kOhm. Der Wert ist ein Bitwert und kann der Betriebspannung zugeordnet werden. Anhand der bekannten Daten, kann nun mit folgendem Sketch die Temperatur in °C ausgegeben werden.


//analog Input pins
const byte SensorKessel = 0;     //Analogeingang Kesseltemp.
const byte SensorAussen = 1;     //Analogeingang Aussentemp.
const byte SensorSpeicher = 2;   //Analogeingang Speichertemp.

//NTC 10kOhm@25°C
const float R1 = 10000;
const float c1 = 1.009249522e-03;
const float c2 = 2.378405444e-04;
const float c3 = 2.019202697e-07;

float tempAussen, tempKessel, tempSpeicher;

void setup() {
    Serial.begin(9600);
}

void loop() {
    //Funktions Übergabe Analogwert zu Temp°C
    tempAussen = analogToC(analogRead(SensorAussen));
    tempKessel = analogToC(analogRead(SensorKessel));
    tempSpeicher = analogToC(analogRead(SensorSpeicher));

    Serial.println("ta = "+tempAussen+" / tk = "+tempKessel+" / ts = "+tempSpeicher);
}

//analog glesener Wert zu °C Umrechnen
double analogToC(float analogValue) {
    float R2, T, valueInC, logR2;
    R2 = R1 * (1023.0 / (float)analogValue - 1.0);
    logR2 = log(R2);
    T = (1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2));
    valueInC = T - 273.15;
    return (valueInC);
}

Heizkurve

Da wir nun die Temperaturen in °C als Variablen verfügbar haben, können wir uns mit der witterungsgeführten Steuerung befassen.
Das heißt, es muss eine Solltemperatur in abhängigkeit von Außentemperatur und gewählter Heizkurve gebildet werden. Die Heizkurve stellen wir als mehrdimensionales Array dar. Somit haben wir ein Lookuptable für unser Funktion.

 
float tempAussen, tempKessel, tempSpeicher, KesselSoll;

//einstellbare Werte (Werkseinstellung)
int Heizkurve = 2;    //1-7

float HeizkurveTag[21][8] = {
    //  °C      0,5    1     1,5     2     2,5     3     3,5
    { 20.00, 37.00, 37.00, 37.00, 37.00, 37.00, 37.00, 37.00},
    { 18.00, 37.01, 37.01, 37.01, 37.01, 37.01, 37.01, 37.64},
    { 16.00, 37.02, 37.02, 37.02, 38.68, 41.34, 44.01, 46.68},
    { 14.00, 37.03, 37.03, 39.66, 43.55, 47.44, 51.33, 55.21},
    { 12.00, 37.04, 38.09, 43.13, 48.17, 53.21, 58.26, 63.30},
    { 10.00, 37.05, 40.28, 46.43, 52.57, 58.71, 64.85, 70.99},
    { 8.00, 37.00, 42.38, 49.58, 56.77, 63.96, 71.15, 78.34},
    { 6.00, 37.00, 44.40, 52.59, 60.79, 68.99, 77.19, 84.87},
    { 4.00, 37.17, 46.33, 55.50, 64.66, 73.83, 82.99, 84.88},
    { 2.00, 38.10, 48.20, 58.29, 68.39, 78.49, 84.89, 84.89},
    { 0.00, 39.00, 50.00, 61.00, 72.00, 84.90, 84.90, 84.90},
    { -2.00, 39.87, 51.75, 63.62, 75.50, 84.91, 84.91, 84.91},
    { -4.00, 40.72, 53.45, 66.17, 78.89, 84.92, 84.92, 84.92},
    { -6.00, 41.55, 55.10, 68.65, 82.20, 84.93, 84.93, 84.93},
    { -8.00, 42.36, 56.71, 71.07, 84.94, 84.94, 84.94, 84.94},
    { -10.00, 43.14, 58.28, 73.43, 84.95, 84.95, 84.95, 84.95},
    { -12.00, 43.91, 59.82, 75.74, 84.96, 84.96, 84.96, 84.96},
    { -14.00, 44.67, 61.33, 78.00, 84.97, 84.97, 84.97, 84.97},
    { -16.00, 45.41, 62.81, 80.22, 84.98, 84.98, 84.98, 84.98},
    { -18.00, 46.13, 64.27, 82.40, 84.99, 84.99, 84.99, 84.99},
    { -20.00, 46.85, 65.70, 84.55, 85.00, 85.00, 85.00, 85.00},
};

void setup() {
    Serial.begin(9600);
}

void loop() {
    KesselSoll = (kesselZielTemp(tempAussen, Heizkurve));  //Bildung Kesselsolltemp

    Serial.println("KSoll = "+KesselSoll);
}

//Bildung Kesselsolltemp in Abhängigkeit zur Aussentemp
double kesselZielTemp(float aussenTempInC, int Heizkurve) {
  float x;      //rückgabewert
  float XP1, XP2, YP1, YP2, m;
  int zeile = 0;
  Heizkurve = constrain(Heizkurve, 1, 7);   //Begrenzung der Auswahl auf 1-7

  if (aussenTempInC >= HeizkurveTag[0][0])return (HeizkurveTag[0][Heizkurve]);   //wärmer als 20°C
  if (aussenTempInC <= HeizkurveTag[20][0])return (HeizkurveTag[20][Heizkurve]); //kälter -20°C
  while (aussenTempInC < HeizkurveTag[zeile][0])zeile++;
  XP2 = HeizkurveTag[zeile][Heizkurve];
  YP2 = HeizkurveTag[zeile][0];
  XP1 = HeizkurveTag[zeile - 1][Heizkurve];
  YP1 = HeizkurveTag[zeile - 1][0];
  m = (YP2 - YP1) / (XP2 - XP1); //bsp:19°C: m=(18°C-20°C)/(37.01-37.00) => -200
  x = (aussenTempInC - (YP1 - (m * XP1))) / m; //x=(19°C-(20-(-200*37.00)))/(-200)
  return (x);
}

Brennersteuerung

Jetzt haben wir die grundlegenden Variablen zusammen um den Brenner ein und aus zuschalten.Wir benötigen noch eine Taktsperre um ein zu häufiges Widereinschalten zu verhindern. Eine Hysterese zur Solltemperatur werden auch realisiert. Der output für den Brenner wird im Setup auf HIGH gesetzt, da die verwendeten Relais "high active" sind und somit bei anliegendem LOW Signal den Kontakt schließen.


//output pins
const byte Brenner = 7;

//einstellbare Werte (Werkseinstellung)
int KesselMin = 35;   //10-60°
int KesselMax = 90;   //70-90°
int BrennerHyst = 20;   //1-10
int ZeitTktsp = 15;   //Taktsperre in min
int alterZustand, aktuellerZustand;
unsigned long ZeitStart;
unsigned long ZeitVergangen;

float tempAussen, tempKessel, tempSpeicher, KesselSoll;

void setup() {
    Serial.begin(9600);
    pinMode(Brenner, OUTPUT);     //Brenner Ausgang
    digitalWrite(Brenner, HIGH);
}

void loop() {
    //Taktsperre
    aktuellerZustand = digitalRead(Brenner);
    if ((aktuellerZustand == HIGH) && (alterZustand == LOW))ZeitStart = millis();
    alterZustand = aktuellerZustand;
    ZeitVergangen = millis() - ZeitStart;
    if (ZeitVergangen >= (ZeitTktsp * 60000))Taktsperre = HIGH;     //keine Sperre
    else Taktsperre = LOW;    //Sperre aktiv

    //Kesselsteuerung
        if ((tempKessel < ((KesselSoll - (BrennerHyst / 2)))) && Taktsperre)digitalWrite(Brenner, LOW);
        if (tempKessel > (KesselSoll + (BrennerHyst / 2)))digitalWrite(Brenner, HIGH);    
}

Zeitschaltung

Um Speicher-, Heiz- und Zirkulationspumpe zu steuern, müssen wir das RTC-Modul (Uhr) mit einbinden. Es wird der Wochentag und die Uhrzeit benötigt um die unterschiedlichen Schaltzeiten zu programmieren. Unter der Woche sind Warmwasser und Heizung früh und nachmittags bis abends an. Am Wochenende früh bis abend. Sollte die Aussentemperatur über 15°C steigen, wird die Heizung nichtmehr benötigt. Sinkt sie unter 3°C läuft die Heizung auch außerhalb der Schaltzeiten zum Frostschutz. Das Füllen des Speichers mit Warmwasser hat immer vorrang und schaltet somit für diese Zeit die Heizpumpe ab. die Brauchwassertemperatur wird auf 50°C eingestellt. Um ausserhalb der Schaltzeiten, Warmwasser zu bekommen wird eine Warmwasser anfordern und Anwesheits Variable realisert. Die Unterschiedlichen Betriebsarten (Standby, Automatik, Eco, etc.) werden wir später implementieren.


#include RTClib.h
RTC_DS1307 RTC;

//output pins
const byte ZirkuP = 8;
const byte SpeicherP = 6;
const byte HeizungP = 5;

//einstellbare Werte (Werkseinstellung)
int tempSpeicherMax = 50;
int Aussentempabschaltung = 15;    //15-30°
int Frostschutz = 3;    //-5 bis 10°

float tempAussen, tempSpeicher;
boolean istTag, Sommerbetrieb, Winterbetrieb, Taktsperre, Warmwasser, Anwesend;

void setup() {
    Serial.begin(9600);

    // Uhr starten
    RTC.begin();
    //RTC.adjust(DateTime(YYYY,MM,DD,hh,mm,ss));
    //RTC.adjust(dayOfTheWeek(4));
    if (! RTC.isrunning())Serial.println("RTC is NOT running!");
}

void loop() {
    DateTime now = RTC.now();

    //Außentempabschaltung
    if (tempAussen > (Aussentempabschaltung + 2))Sommerbetrieb = true;
    else if (tempAussen < (Aussentempabschaltung - 2))Sommerbetrieb = false;

    //Frostschutz
    if (tempAussen > (Frostschutz + 1))Winterbetrieb = false;
    else if (tempAussen < (Frostschutz - 1)) Winterbetrieb = true;

    //Bestimmung der Schaltzeiten
    boolean istTagbetrieb() {
    DateTime now = RTC.now();

    if (((Anwesend == true) || (Warmwasser == true)) && now.hour() >= 7 && now.hour() < 21)istTag = true; //Warmwasseranforderung
    else if (now.dayOfTheWeek() > 0 && now.dayOfTheWeek() < 6) {      //MO-FR 5-7 && 14-21
        if ((now.hour() >= 5 && now.hour() < 17) || (now.hour() >= 14 && now.hour() < 21)) {     //Tag-Nacht Betrieb
            istTag = true;
        }
        else istTag = false;
    }
    else if (now.hour() >= 7 && now.hour() < 21)istTag = true;      //SA+SO 7-21
    else istTag = false;
    return (istTag);
    }

    if ((tempSpeicher < tempSpeicherMin) && istTagbetrieb())digitalWrite(SpeicherP, LOW);
    if ((tempSpeicher > tempSpeicherMax) || !istTagbetrieb())digitalWrite(SpeicherP, HIGH);

    if (istTagbetrieb())digitalWrite(ZirkuP, LOW);
    else digitalWrite(ZirkuP, HIGH);

    if (Sommerbetrieb || (digitalRead(SpeicherP) == LOW)) digitalWrite(HeizungP, HIGH);
    else digitalWrite (HeizungP, LOW);
}

Betriebsartenwahl

Nun ist es Zeit für die Betriebsarten Standby, Automatik, Eco, Tagbetrieb, Absenkbetrieb und nur Warmwasserbetrieb. Hierfür lagern wir die Steuerung der Aggregate in einzelne Funktionen aus. Außerdem kommt auch der Schornsteinfeger-Modus und die Sicherheitsabschaltungen dazu. Aus Rostshutzgründen darf bei diesem Kessel dessen Temperatur nicht unter 35°C fallen, um Kondenswasser zu vermeiden. durch die KesselMin Temperatur wird dies gewehrleistet.


#include RTClib.h
RTC_DS1307 RTC;

//analog Input pins
const byte SensorKessel = 0;     //Analogeingang Kesseltemp.
const byte SensorAussen = 1;     //Analogeingang Aussentemp.
const byte SensorSpeicher = 2;     //Analogeingang Speichertemp.

//digital Input pins
const byte InputSsf = 9;
const byte InputAnwesend = 11;

//output pins
const byte ZirkuP = 8;
const byte Brenner = 7;
const byte SpeicherP = 6;
const byte HeizungP = 5;

//einstellbare Werte (//Werkseinstellung)
int Betriebsart = 1;    //0=StandBy,1=Automatik,2=ECO,3=dauernd Tag,4=dauernd Absenk,5=Brauchwasser
int Heizkurve = 2;    //1-7
int tempSpeicherMax = 50;
int KesselMin = 35;   //10-60°
int KesselMax = 90;   //70-90°
int Aussentempabschaltung = 15;    //15-30°
int Frostschutz = 3;    //-5 bis 10°
int BrennerHyst = 20;   //1-10
int ZeitTktsp = 15;   //Taktsperre in min

float tempAussen, tempKessel, tempSpeicher, KesselSoll;
boolean istTag, Sommerbetrieb, Winterbetrieb, Taktsperre, SsFeger, Warmwasser, Anwesend;
int alterZustand, aktuellerZustand, tempSpeicherMin;
unsigned long ZeitStart;
unsigned long ZeitVergangen;

//NTC 10kOhm@25°C
const float R1 = 10000;
const float c1 = 1.009249522e-03;
const float c2 = 2.378405444e-04;
const float c3 = 2.019202697e-07;

void setup() {
  Serial.begin(9600);
  pinMode(ZirkuP, OUTPUT);     //Zirku Pumpe
  digitalWrite(ZirkuP, HIGH);
  pinMode(Brenner, OUTPUT);     //Brenner Ausgang
  digitalWrite(Brenner, HIGH);
  pinMode(SpeicherP, OUTPUT);     //Speicher Pumpe
  digitalWrite(SpeicherP, HIGH);
  pinMode(HeizungP, OUTPUT);     //Heizungs Pumpe
  digitalWrite(HeizungP, HIGH);
  pinMode(InputSsf, INPUT);     //Schornsteinfeger Schalter
  pinMode(InputAnwesend, INPUT);

  // Uhr starten
  RTC.begin();
  //RTC.adjust(DateTime(2020,10,25,16,55,00));
  //RTC.adjust(dayOfTheWeek(4));
  if (! RTC.isrunning())Serial.println("RTC is NOT running!");
}

void loop() {
  DateTime now = RTC.now();

  //Anwesenheit
  if (digitalRead(InputAnwesend) == true) Anwesend = true;
  else Anwesend = false;

  //Steuerung
  if ((tempKessel > KesselMax) || (tempSpeicher > 80)) {   //Sicherheitsabschaltung
    digitalWrite(Brenner, HIGH);
    digitalWrite(SpeicherP, HIGH);
    while (tempKessel > 65) {
      digitalWrite(HeizungP, LOW); //abbau Kesseltemperatur
    }
    while (tempSpeicher > 65) {
      digitalWrite(ZirkuP, LOW); //abbau Speichertemperatur
    }
  }
  else {
    if (SsFeger == true) {     //Schornsteinfeger
      digitalWrite(SpeicherP, LOW);
      digitalWrite(ZirkuP, LOW);
      digitalWrite(HeizungP, LOW);
      digitalWrite(Brenner, LOW);
    }
    else {

      if (Betriebsart == 0) {     //Stand by
        if (Winterbetrieb) {
          KesselMinBetrieb();
          PumpenAutomatik();
        }
        else {
          KesselMinBetrieb();
          PumpenAus();
        }
      }

      else if ((Betriebsart == 1) || (Betriebsart == 3) || (Betriebsart == 4)) {  //Auto,dauer Tag, dauer Absenk
        PumpenAutomatik();
        KesselAutomatik();
      }

      else if (Betriebsart == 2) {    //ECO
        if (istTagbetrieb()) {
          PumpenAutomatik();
          KesselAutomatik();
        }
        else {
          if (Winterbetrieb) {
            KesselMinBetrieb();
            PumpenAutomatik();
          }
          else {
            KesselMinBetrieb();
            PumpenAus();
          }
        }
      }

      else if ((Betriebsart == 5) || Sommerbetrieb) {   //Warmwasserbetrieb
        KesselMinBetrieb();
        PumpenAutomatik();
      }
    }
  }
  delay(1000);
}

void PumpenAus(void) {
  digitalWrite(SpeicherP, HIGH);
  digitalWrite(ZirkuP, HIGH);
  digitalWrite(HeizungP, HIGH);
}

void KesselMinBetrieb(void) {
  if ((tempKessel <= KesselMin) && Taktsperre) {
    digitalWrite(Brenner, LOW);
  }
  if (tempKessel > (KesselMin + BrennerHyst)) digitalWrite(Brenner, HIGH);
}

void PumpenAutomatik(void) {
  if ((tempSpeicher < tempSpeicherMin) && istTagbetrieb())digitalWrite(SpeicherP, LOW);
  if ((tempSpeicher > tempSpeicherMax) || !istTagbetrieb())digitalWrite(SpeicherP, HIGH);

  if (istTagbetrieb())digitalWrite(ZirkuP, LOW);
  else digitalWrite(ZirkuP, HIGH);

  if (Sommerbetrieb || (Betriebsart == 5) || (digitalRead(SpeicherP) == LOW)) digitalWrite(HeizungP, HIGH);
  else digitalWrite (HeizungP, LOW);
}

void KesselAutomatik(void) {
  if ((tempKessel < ((KesselSoll - (BrennerHyst / 2)))) && Taktsperre)digitalWrite(Brenner, LOW); //AN lowactiv
  if (tempKessel > (KesselSoll + (BrennerHyst / 2)))digitalWrite(Brenner, HIGH); //AUS lowactiv
}

Display Ausgabe

Die Heizungsteuerung läuft jetzt autonom und witterungsgeführt. Nun ist es an der Zeit die Werte auf dem LCD darzustellen. Für die Zukunft ist auch eine einfache Menüstruktur zur Bedienung angedacht. zum einsatz kommt ein 20 Stelliges LCD mit 4 Zeilen.


#include LiquidCrystal_I2C.h
LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup() {
  Serial.begin(9600);

  //LCD starten
  lcd.init();
  lcd.backlight();
  delay(1000);
}

loop() {
    LCDprint();
}

void LCDprint(void) {
    DateTime now = RTC.now();

    lcd.setCursor(0, 0);
    lcd.print("TS:");
    lcd.print(tempSpeicher, 0);

    lcd.setCursor(6, 0);
    lcd.print("SM:");
    lcd.print(tempSpeicherMax);

    lcd.setCursor(12, 0);
    lcd.print("SpP=");
    if (digitalRead(SpeicherP) == 1)lcd.print("aus");
    else lcd.print("ein");

    lcd.setCursor(0, 1);
    lcd.print("TK:");
    lcd.print(tempKessel, 0);

    lcd.setCursor(6, 1);
    lcd.print("KS:");
    lcd.print(KesselSoll, 0);

    lcd.setCursor(12, 1);
    lcd.print("Brn=");
    if (digitalRead(Brenner) == 1)lcd.print("aus");
    else lcd.print("ein");

    lcd.setCursor(0, 2);
    lcd.print("TA:");
    lcd.print(tempAussen, 0);

    lcd.setCursor(6, 2);
    lcd.print("Bta=");
    lcd.print(Betriebsart);

    lcd.setCursor(12, 2);
    lcd.print("ZiP=");
    if (digitalRead(ZirkuP) == 1)lcd.print("aus");
    else lcd.print("ein");

    lcd.setCursor(0, 3);
    if (now.hour() < 10) {
      lcd.print("0");
      lcd.print(now.hour(), DEC);
    }
    else lcd.print(now.hour(), DEC);
    lcd.print(":");
    if (now.minute() < 10) {
      lcd.print("0");
      lcd.print(now.minute(), DEC);
    }
    else lcd.print(now.minute(), DEC);

    lcd.setCursor(6, 3);
    lcd.print("ts=");
    lcd.print(ZeitTktsp);
    
    lcd.setCursor (12, 3);
    lcd.print ("HzP=");
    if (digitalRead(HeizungP) == 1)lcd.print("aus");
    else lcd.print("ein");
  }

Wifi-Fernsteurung mittels ESP8266

Letzte aber nicht unwichtige Funktion ist die Bedienbarkeit über das heimische Netzwerk. Hierfür werden die Daten per Serialkommunikation mit einem ESP ausgetauscht. Dieser arbeitet für sich allein und hat im Fehlerfall keinen Einfluss auf den Heizungsbetrieb. Die Verarbeitung im ESP und auf der Serverseite wird in einem anderen Kapitel betrachtet.


//output pins
const byte ZirkuP = 8;
const byte Brenner = 7;
const byte SpeicherP = 6;
const byte HeizungP = 5;

//einstellbare Werte (Werkseinstellung)
int Betriebsart = 1;    //0=StandBy,1=Automatik,2=ECO,3=dauernd Tag,4=dauernd Absenk,5=Brauchwasser
int Heizkurve = 2;    //1-7
int tempSpeicherMax = 50;
int ZeitTktsp = 15;   //Taktsperre in min

float tempAussen, tempKessel, tempSpeicher, KesselSoll;
boolean SsFeger, Warmwasser, Anwesend;
String data2send, Data, rest; //Daten an und vom ESP

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600); // Kommunikation mit ESP8266
}

void loop() {
    //Remoteverbindung
    WriteESP();
    ReadESP();
}

//schreiben auf ESP
void WriteESP(void) {
  String data2send = String(tempAussen, 0);
  data2send += ",";
  data2send += String(tempKessel, 0);
  data2send += ",";
  data2send += String(tempSpeicher, 0);
  data2send += ",";
  data2send += String(digitalRead(Brenner));
  data2send += ",";
  data2send += String(digitalRead(SpeicherP));
  data2send += ",";
  data2send += String(digitalRead(HeizungP));
  data2send += ",";
  data2send += String(digitalRead(ZirkuP));
  data2send += ",";
  data2send += String(KesselSoll, 0);
  data2send += ",";
  data2send += int(tempSpeicherMax);
  data2send += ",";
  data2send += int(Betriebsart);
  data2send += ",";
  Serial1.println(data2send);
  //Serial.print("send: ");
  //Serial.println(data2send);
}

//lesen von ESP
void ReadESP(void) {
  if (Serial1.available()) {
    Data = Serial1.readString();
    Warmwasser = Data.substring(0, Data.indexOf(',')).toInt();
    rest = Data.substring(Data.indexOf(',') + 1);
    int TM = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    SsFeger = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    int HK = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    int TKS = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    int BA = rest.substring(0, rest.indexOf('}')).toInt();
    //Serial.print("read: ");
    //Serial.println(Data);
    if ((HK >= 1) && (HK <= 7)) Heizkurve = HK;
    else Heizkurve = 2;                                 //Rückfallwert
    if ((TM >= 35) && (TM <= 60)) tempSpeicherMax = TM;
    else tempSpeicherMax = 50;                          //Rückfallwert
    if ((TKS >= 1) && (TKS <= 30)) ZeitTktsp = TKS;
    else ZeitTktsp = 15;                                //Rückfallwert
    if ((BA >= 0) && (BA <= 5)) Betriebsart = BA;
    else Betriebsart = 1;                               //Rückfallwert
  }
}

Kompletter Script

Diese Steuerung läuft seit Ende 2018 stabil.

Dennoch DIE HIER GEZEIGTEN SCRIPTE SOLLEN LEDIGLICH ALS ANREGUNG DIENEN. FÜR EVENTUELLE SCHÄDEN BEIM NACHBAU KANN KEINE HAFTUNG ÜBERNOMMEN WERDEN!


#include RTClib.h
RTC_DS1307 RTC;
#include LiquidCrystal_I2C.h
LiquidCrystal_I2C lcd(0x27, 20, 4);

//analog Input pins
const byte SensorKessel = 0;     //Analogeingang Kesseltemp.
const byte SensorAussen = 1;     //Analogeingang Aussentemp.
const byte SensorSpeicher = 2;     //Analogeingang Speichertemp.

//digital Input pins
const byte InputSsf = 9;
const byte InputEspErr = 2;
const byte InputBT = 10;
const byte InputAnwesend = 11;

//output pins
const byte ZirkuP = 8;
const byte Brenner = 7;
const byte SpeicherP = 6;
const byte HeizungP = 5;

//einstellbare Werte (Werkseinstellung)
int Betriebsart = 1;    //0=StandBy,1=Automatik,2=ECO,3=dauernd Tag,4=dauernd Absenk,5=Brauchwasser
int Heizkurve = 2;    //1-7
int tempSpeicherMax = 50;
int KesselMin = 35;   //10-60°
int KesselMax = 90;   //70-90°
int Aussentempabschaltung = 15;    //15-30°
int Frostschutz = 3;    //-5 bis 10°
int BrennerHyst = 20;   //1-10
int ZeitTktsp = 15;   //Taktsperre in min

float tempAussen, tempKessel, tempSpeicher, KesselSoll;
boolean istTag, Sommerbetrieb, Winterbetrieb, Taktsperre, SsFeger, Warmwasser, Anwesend;
int alterZustand, aktuellerZustand, tempSpeicherMin;
unsigned long ZeitStart;
unsigned long ZeitVergangen;
String data2send, Data, rest; //Daten an und vom ESP

//NTC 10kOhm@25°C
const float R1 = 10000;
const float c1 = 1.009249522e-03;
const float c2 = 2.378405444e-04;
const float c3 = 2.019202697e-07;

float HeizkurveTag[21][8] = {
  //  °C      0,5    1     1,5     2     2,5     3     3,5 
  { 20.00, 37.00, 37.00, 37.00, 37.00, 37.00, 37.00, 37.00},
  { 18.00, 37.01, 37.01, 37.01, 37.01, 37.01, 37.01, 37.64},
  { 16.00, 37.02, 37.02, 37.02, 38.68, 41.34, 44.01, 46.68},
  { 14.00, 37.03, 37.03, 39.66, 43.55, 47.44, 51.33, 55.21},
  { 12.00, 37.04, 38.09, 43.13, 48.17, 53.21, 58.26, 63.30},
  { 10.00, 37.05, 40.28, 46.43, 52.57, 58.71, 64.85, 70.99},
  { 8.00, 37.00, 42.38, 49.58, 56.77, 63.96, 71.15, 78.34},
  { 6.00, 37.00, 44.40, 52.59, 60.79, 68.99, 77.19, 84.87},
  { 4.00, 37.17, 46.33, 55.50, 64.66, 73.83, 82.99, 84.88},
  { 2.00, 38.10, 48.20, 58.29, 68.39, 78.49, 84.89, 84.89},
  { 0.00, 39.00, 50.00, 61.00, 72.00, 84.90, 84.90, 84.90},
  { -2.00, 39.87, 51.75, 63.62, 75.50, 84.91, 84.91, 84.91},
  { -4.00, 40.72, 53.45, 66.17, 78.89, 84.92, 84.92, 84.92},
  { -6.00, 41.55, 55.10, 68.65, 82.20, 84.93, 84.93, 84.93},
  { -8.00, 42.36, 56.71, 71.07, 84.94, 84.94, 84.94, 84.94},
  { -10.00, 43.14, 58.28, 73.43, 84.95, 84.95, 84.95, 84.95},
  { -12.00, 43.91, 59.82, 75.74, 84.96, 84.96, 84.96, 84.96},
  { -14.00, 44.67, 61.33, 78.00, 84.97, 84.97, 84.97, 84.97},
  { -16.00, 45.41, 62.81, 80.22, 84.98, 84.98, 84.98, 84.98},
  { -18.00, 46.13, 64.27, 82.40, 84.99, 84.99, 84.99, 84.99},
  { -20.00, 46.85, 65.70, 84.55, 85.00, 85.00, 85.00, 85.00},
};

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  Serial2.begin(9600);
  Serial3.begin(9600);
  pinMode(ZirkuP, OUTPUT);     //Zirku Pumpe
  digitalWrite(ZirkuP, HIGH);
  pinMode(Brenner, OUTPUT);     //Brenner Ausgang
  digitalWrite(Brenner, HIGH);
  pinMode(SpeicherP, OUTPUT);     //Speicher Pumpe
  digitalWrite(SpeicherP, HIGH);
  pinMode(HeizungP, OUTPUT);     //Heizungs Pumpe
  digitalWrite(HeizungP, HIGH);
  pinMode(InputSsf, INPUT);     //Schornsteinfeger Schalter
  pinMode(InputAnwesend, INPUT);

  // Uhr starten
  RTC.begin();
  //RTC.adjust(DateTime(2020,10,25,16,55,00));
  //RTC.adjust(dayOfTheWeek(4));
  if (! RTC.isrunning())Serial.println("RTC is NOT running!");

  //LCD starten
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, bt);
  lcd.createChar(1, wifi);
  delay(1000);
}

void loop() {
  DateTime now = RTC.now();

  //Anwesenheit
  if (digitalRead(InputAnwesend) == true) Anwesend = true;
  else Anwesend = false;

  //Funktions Übergabe Analogwert zu Temp°C
  tempAussen = (analogToC(analogRead(SensorAussen)) + tempAbgleichA);
  tempKessel = (analogToC(analogRead(SensorKessel)) + tempAbgleichK);
  tempSpeicher = (analogToC(analogRead(SensorSpeicher)) + tempAbgleichS);

  //Remoteverbindung
  WriteESP();
  ReadESP();

  tempSpeicherMin = (tempSpeicherMax - 10);     //Hysterese für Speichertemperatur

  KesselSoll = (kesselZielTemp(tempAussen, Heizkurve));  //Bildung Kesselsolltemp in Abhängigkeit von Aussentemp

  //Außentempabschaltung
  if ((tempAussen > (Aussentempabschaltung + 2)) || (Betriebsart == 5))Sommerbetrieb = true;
  else if (tempAussen < (Aussentempabschaltung - 2))Sommerbetrieb = false;

  //Frostschutz
  if (tempAussen > (Frostschutz + 1))Winterbetrieb = false;
  else if (tempAussen < (Frostschutz - 1)) Winterbetrieb = true;

  //Taktsperre
  aktuellerZustand = digitalRead(Brenner);
  if ((aktuellerZustand == HIGH) && (alterZustand == LOW))ZeitStart = millis(); //war LOW(an) ist jetz HIGH(aus) ...min warten
  alterZustand = aktuellerZustand;
  ZeitVergangen = millis() - ZeitStart;
  if (ZeitVergangen >= (ZeitTktsp * 60000))Taktsperre = HIGH;     //keine Sperre
  else Taktsperre = LOW;    //Sperre aktiv

  //--------------------------------------Steuerung-------------------------------------------------//

  if ((tempKessel > KesselMax) || (tempSpeicher > 80)) {   //Sicherheitsabschaltung
    digitalWrite(Brenner, HIGH);
    digitalWrite(SpeicherP, HIGH);
    while (tempKessel > 65) {
      digitalWrite(HeizungP, LOW); //abbau Kesseltemperatur
      lcd.setCursor(19, 1);
      lcd.print("H");
      delay(500);
      lcd.setCursor(19, 1);
      lcd.print("K");
      delay(500);
    }
    while (tempSpeicher > 65) {
      digitalWrite(ZirkuP, LOW); //abbau Speichertemperatur
      lcd.setCursor(19, 1);
      lcd.print("H");
      delay(500);
      lcd.setCursor(19, 1);
      lcd.print("S");
      delay(500);
    }
  }
  else {
    if (SsFeger == true) {     //Schornsteinfeger
      digitalWrite(SpeicherP, LOW);
      digitalWrite(ZirkuP, LOW);
      digitalWrite(HeizungP, LOW);
      digitalWrite(Brenner, LOW);
    }
    else {

      if (Betriebsart == 0) {     //Stand by
        if (Winterbetrieb) {
          KesselMinBetrieb();
          PumpenAutomatik();
        }
        else {
          KesselMinBetrieb();
          PumpenAus();
        }
      }

      else if ((Betriebsart == 1) || (Betriebsart == 3) || (Betriebsart == 4)) {  //Auto,dauer Tag, dauer Absenk
        PumpenAutomatik();
        KesselAutomatik();
      }

      else if (Betriebsart == 2) {    //ECO
        if (istTagbetrieb()) {
          PumpenAutomatik();
          KesselAutomatik();
        }
        else {
          if (Winterbetrieb) {
            KesselMinBetrieb();
            PumpenAutomatik();
          }
          else {
            KesselMinBetrieb();
            PumpenAus();
          }
        }
      }

      else if ((Betriebsart == 5) || Sommerbetrieb) {   //Warmwasserbetrieb
        KesselMinBetrieb();
        PumpenAutomatik();
      }
    }
  }
  LCDprint();
  delay(1000);
}

//Bildung Kesselsolltemp in Abhängigkeit zur Aussentemp
double kesselZielTemp(float aussenTempInC, int Heizkurve) {
  float x;      //rückgabewert
  float XP1, XP2, YP1, YP2, m;
  int zeile = 0;
  if ((istTagbetrieb() == false) && ((Betriebsart == 1) || (Betriebsart == 3) || (Betriebsart == 4))) {     //Absenkbetrieb
    Heizkurve = Heizkurve - 1;
  }
  else if ((istTagbetrieb() == false) && ((Betriebsart == 0) || (Betriebsart == 2) || (Betriebsart == 5))) {
    return (x = KesselMin);
  }
  else Heizkurve = Heizkurve;
  Heizkurve = constrain(Heizkurve, 1, 7);   //Begrenzung der Auswahl auf 1-7

  if (digitalRead(SpeicherP) == LOW)return (x = 70);      //Speicherpumpe ist an

  if (aussenTempInC >= HeizkurveTag[0][0])return (HeizkurveTag[0][Heizkurve]);      //wärmer als 20°C
  if (aussenTempInC <= HeizkurveTag[20][0])return (HeizkurveTag[20][Heizkurve]);      //kälter -20°C
  while (aussenTempInC < HeizkurveTag[zeile][0])zeile++;
  XP2 = HeizkurveTag[zeile][Heizkurve];
  YP2 = HeizkurveTag[zeile][0];
  XP1 = HeizkurveTag[zeile - 1][Heizkurve];
  YP1 = HeizkurveTag[zeile - 1][0];
  m = (YP2 - YP1) / (XP2 - XP1); //bsp:19°C: m=(18°C-20°C)/(37.01-37.00) => -200
  x = (aussenTempInC - (YP1 - (m * XP1))) / m; //x=(19°C-(20-(-200*37.00)))/(-200)
  return (x);
}

//analog gelesener Wert zu °C Umrechnen
double analogToC(float analogValue) {
  float R2, T, valueInC, logR2;
  R2 = R1 * (1023.0 / (float)analogValue - 1.0);
  logR2 = log(R2);
  T = (1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2));
  valueInC = T - 273.15;
  return (valueInC);
}

//Bestimmung von Tag/Nacht (Absenkbetrieb)
boolean istTagbetrieb() {
  DateTime now = RTC.now();

  if (Betriebsart == 3) istTag = true; //dauernd Tagbetrieb
  else if (Betriebsart == 4) istTag = false;    //dauernd Absenkbetrieb
  else if (WWan == 1)istTag = true; //Warmwasseranforderung über LCD
  else if (((Anwesend == true) || (Warmwasser == true)) && now.hour() >= 5 && now.hour() < 21)istTag = true; //Warmwasseranforderung
  else if (now.dayOfTheWeek() > 0 && now.dayOfTheWeek() < 6) {      //MO-FR 5-7 && 14-21
    if ((now.hour() >= 5 && now.hour() < 7) || (now.hour() >= 14 && now.hour() < 21)) {     //Tag-Nacht Betrieb
      istTag = true;
    }
    else istTag = false;
  }
  else if (now.hour() >= 5 && now.hour() < 21)istTag = true;      //SA+SO 5-21
  else istTag = false;
  return (istTag);
}

// LCD Display Ausgabe

void LCDprint(void) {
  DateTime now = RTC.now();

  lcd.setCursor(0, 0);
  lcd.print("TS:");
  lcd.print(tempSpeicher, 0);

  lcd.setCursor(6, 0);
  lcd.print("SM:");
  lcd.print(tempSpeicherMax);

  lcd.setCursor(12, 0);
  lcd.print("SpP=");
  if (digitalRead(SpeicherP) == 1)lcd.print("aus");
  else lcd.print("ein");

  lcd.setCursor(0, 1);
  lcd.print("TK:");
  lcd.print(tempKessel, 0);

  lcd.setCursor(6, 1);
  lcd.print("KS:");
  lcd.print(KesselSoll, 0);

  lcd.setCursor(12, 1);
  lcd.print("Brn=");
  if (digitalRead(Brenner) == 1)lcd.print("aus");
  else lcd.print("ein");

  lcd.setCursor(0, 2);
  lcd.print("TA:");
  lcd.print(tempAussen, 0);

  lcd.setCursor(6, 2);
  lcd.print("Bta=");
  lcd.print(Betriebsart);

  lcd.setCursor(12, 2);
  lcd.print("ZiP=");
  if (digitalRead(ZirkuP) == 1)lcd.print("aus");
  else lcd.print("ein");

  lcd.setCursor(0, 3);
  if (now.hour() < 10) {
    lcd.print("0");
    lcd.print(now.hour(), DEC);
  }
  else lcd.print(now.hour(), DEC);
  lcd.print(":");
  if (now.minute() < 10) {
    lcd.print("0");
    lcd.print(now.minute(), DEC);
  }
  else lcd.print(now.minute(), DEC);

  lcd.setCursor(6, 3);
  lcd.print("ts=");
  lcd.print(ZeitTktsp);

  lcd.setCursor (12, 3);
  lcd.print ("HzP=");
  if (digitalRead(HeizungP) == 1)lcd.print("aus");
  else lcd.print("ein");
}

//schreiben auf ESP
void WriteESP(void) {
  String data2send = String(tempAussen, 0);
  data2send += ",";
  data2send += String(tempKessel, 0);
  data2send += ",";
  data2send += String(tempSpeicher, 0);
  data2send += ",";
  data2send += String(digitalRead(Brenner));
  data2send += ",";
  data2send += String(digitalRead(SpeicherP));
  data2send += ",";
  data2send += String(digitalRead(HeizungP));
  data2send += ",";
  data2send += String(digitalRead(ZirkuP));
  data2send += ",";
  data2send += String(KesselSoll, 0);
  data2send += ",";
  data2send += int(tempSpeicherMax);
  data2send += ",";
  data2send += int(Betriebsart);
  data2send += ",";
  Serial1.println(data2send);
  //Serial.print("send: ");
  //Serial.println(data2send);
}

//lesen von ESP
void ReadESP(void) {
  if (Serial1.available()) {
    Data = Serial1.readString();
    Warmwasser = Data.substring(0, Data.indexOf(',')).toInt();
    rest = Data.substring(Data.indexOf(',') + 1);
    int TM = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    SsFeger = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    int HK = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    int TKS = rest.substring(0, rest.indexOf(',')).toInt();
    rest = rest.substring(rest.indexOf(',') + 1);
    int BA = rest.substring(0, rest.indexOf('}')).toInt();
    //Serial.print("read: ");
    //Serial.println(Data);
    if ((HK >= 1) && (HK <= 7)) Heizkurve = HK;
    else Heizkurve = 2;
    if ((TM >= 35) && (TM <= 60)) tempSpeicherMax = TM;
    else tempSpeicherMax = 50;
    if ((TKS >= 1) && (TKS <= 30)) ZeitTktsp = TKS;
    else ZeitTktsp = 15;
    if ((BA >= 0) && (BA <= 5)) Betriebsart = BA;
    else Betriebsart = 1;
  }
}

void PumpenAus(void) {
  digitalWrite(SpeicherP, HIGH);
  digitalWrite(ZirkuP, HIGH);
  digitalWrite(HeizungP, HIGH);
}

void KesselMinBetrieb(void) {
  if ((tempKessel <= KesselMin) && Taktsperre) {
    digitalWrite(Brenner, LOW);
  }
  if (tempKessel > (KesselMin + BrennerHyst)) digitalWrite(Brenner, HIGH);
}

void PumpenAutomatik(void) {
  if ((tempSpeicher < tempSpeicherMin) && istTagbetrieb())digitalWrite(SpeicherP, LOW);
  if ((tempSpeicher > tempSpeicherMax) || !istTagbetrieb())digitalWrite(SpeicherP, HIGH);

  if (istTagbetrieb())digitalWrite(ZirkuP, LOW);
  else digitalWrite(ZirkuP, HIGH);

  if (Sommerbetrieb || (Betriebsart == 5) || (digitalRead(SpeicherP) == LOW)) digitalWrite(HeizungP, HIGH);
  else digitalWrite (HeizungP, LOW);
}

void KesselAutomatik(void) {
  if ((tempKessel < ((KesselSoll - (BrennerHyst / 2)))) && Taktsperre)digitalWrite(Brenner, LOW); //AN lowactiv
  if (tempKessel > (KesselSoll + (BrennerHyst / 2)))digitalWrite(Brenner, HIGH); //AUS lowactiv
}