/*
Forum: https://forum.arduino.cc/t/gleicher-code-uno-nano/1414771/38
Wokwi: https://wokwi.com/projects/447981734015225857
Der Sketch entspricht dem Stand aus Post 38 mit folgenden Anpassungen:
* Anstelle der DS18B20 werden DHT22 verwendet, um Temperaturwerte zu generieren
(DS18B20 wird derzeit nur eingeschränkt von Wokwi unterstützt). Um die Aufrufe im Sketch identisch
beibehalten zu können, werden diese Aufrufe in der Klasse DHTSensors im File DHTSensors.h soweit erforderlich
emuliert oder als Dummy ohne Funktion abgebildet.
* Anstelle der RTC_DS3231 wird die weitgehend kompatible RTC_1307 verwendet.
* Mittels #define WOKWI werden die dazu erforderlichen Anpassungen im Sourcecode per bedingter Compilation
eingebunden.
Beachten: Das Schreiben auf SDCard wird von Wokwi zwar unterstützt, die dabei entstehenden Daten stehen jedoch nur
während des Programmlaufs zum Auslesen zur Verfügung und sind nach Programmabbruch nicht mehr verfügbar.
2025/11/19
ec2021
Code von my_xy_projekt Arduinoforum bearbeitet
===========================================================
Heizungsüberwachung – Arduino UNO R3
-----------------------------------------------------------
Funktionen:
- Messung Vorlauf/Rücklauf mit 3 x DS18B20 (OneWire-Bus)
- Erfassung der Zustände:
Brenner EIN/AUS / BZ
Ventil Heizung
Ventil Warmwasser
- Logging auf SD-Karte (nur bei Zustandsänderung)
- erweitert wenn Brenner Ein & BZ
loggen in sekundentakt
- Echtzeit über DS3231 RTC
- Alarm-Ausgang bei Vorlauf > 75 °C (mit Hysterese)
- zum Testen serielle Eingabe der Vorlauftemperatur 14.11.25
- Zähler der Alarme im EEPROM
Reset über Pin 5
-----------------------------------------------------------
Hardware:
- DS18B20 Data → Pin 2, Pullup 4,7kΩ nach 5V
- IO PINS
- Brenner
- BZ
- Ventil Warmwasser
- Alarm-LED/Relais
SD-Karte wie folgt an SPI-Bus angeschlossen:
- MOSI - Pin 11 auf Arduino Uno/Nano/Duemilanove/Diecimila
- MISO - Pin 12 auf Arduino Uno/Nano/Duemilanove/Diecimila
- CLK - Pin 13 auf Arduino Uno/Nano/Duemilanove/Diecimila
- SD-CS -Pin 10
- RTC DS3231 via I2C (A4 = SDA, A5 = SCL)
- ON-Board-LED Fehlererkennung
===========================================================
*/
#define WOKWI
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
#include <EEPROM.h>
// ----------------------------------------------------------
// Pinbelegung angepasst 30-10-25
// ----------------------------------------------------------
constexpr uint8_t ONE_WIRE_BUS{ 2 };
constexpr uint8_t VENTIL_WW_PIN{ 4 };
constexpr uint8_t RESET_PIN{ 5 }; // zum reset des Alarmzählers beim starten nach Ground verdrahten
constexpr uint8_t BRENNER_PIN{ 6 };
constexpr uint8_t BZ_PIN{ 7 };
constexpr uint8_t ALARM_PIN{ 8 };
constexpr uint8_t SP_PIN{ 9 }; // nur bei Uno Anzeige speichern
constexpr uint8_t SD_CS_PIN{ 10 };
constexpr uint8_t EEPROM_ADDR{ 0 }; // Die Adresse im EEPROM, an der der Zähler gespeichert wird
// ----------------------------------------------------------
// Globale Objekte
// ----------------------------------------------------------
#ifdef WOKWI
#include "DHTSensors.h"
DHTSensors sensors;
RTC_DS1307 rtc;
#else
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
RTC_DS3231 rtc;
#endif
File logfile;
DateTime now;
constexpr uint32_t _ONESECOND{ 1000 };
constexpr char heizungFile[]{ "heizung.csv" };
/* Für die Zählfunktion des Alarms */
// Globale oder statische Variablen, die ihren Zustand beibehalten müssen
static uint8_t zaehler = 0;
static int lastAlarmState = HIGH; // Der vorherige Zustand des Pins
// Schwellwerte für Alarm
constexpr uint8_t ALARM_ON_TEMP{ 75 }; // Alarm EIN über 75°C
constexpr uint8_t ALARM_OFF_TEMP{ 68 }; // Alarm AUS unter 68°C
constexpr bool alarmOn{ LOW }; // Pinzustand wenn Relais EIN geschaltet
constexpr bool alarmOff{ !alarmOn };
bool brennerStatus;
bool BZStatus;
bool ventilWWStatus;
bool writeSuccess = false; // Flag, das den Erfolg des letzten Schreibvorgangs speichert
// Letzte bekannte Zustände
bool alarmState = alarmOff;
// Temperaturwerte
float tempVorlauf;
float tempRueckl_HZG;
float tempRueckl_WW;
/* 14-11-2025 Werteeingabe zum testen
Temperaturwert Vorlauf manuell setzen
*/
float temperaturWert = 0.0;
bool benutzeEingabeWert = false;
/*-------------------------------------*/
uint32_t lastCheckTime; // Merker, wann das letzte Mal die sensoren abgefragt wurden
bool changed;
char speicher[10];
// ----------------------------------------------------------
// Adressen der Sensoren 3.11.25
// mit den Adressen könnte ich
// tempVorlauf = sensors.getTempCByIndex(1);
// gegen tempVorlauf = sensors.getTempC(sensorVlauf); ersetzen
//
// DeviceAddress sensorHzg = { 0x28, 0x55, 0x37, 0xB4, 0x00, 0x00, 0x00, 0x91 }; // Adresse Sensor 1
// DeviceAddress sensorVlauf = { 0x28, 0x4B, 0xCA, 0xB2, 0x00, 0x00, 0x00, 0x9A }; // Adresse Sensor 2
// DeviceAddress sensorWw = { 0x28, 0xEF, 0xD3, 0xB1, 0x00, 0x00, 0x00, 0x1D }; // Adresse Sensor 3
// ----------------------------------------------------------
// Setup
// ----------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(500);
Serial.println(F("=== Heizungsüberwachung startet ==="));
sdCardInit();
inputPinInit();
// Ausgang konfigurieren (Alarm)
pinMode(ALARM_PIN, OUTPUT);
digitalWrite(ALARM_PIN, true); // Anfangszustand: aus
pinMode(SP_PIN, OUTPUT);
digitalWrite(SP_PIN, false); // Anfangszustand: aus
// RTC starten
if (!rtc.begin()) {
Serial.println(F("RTC nicht gefunden!"));
/* 31.10.25 */
}
/*
erweitert am 13.11.25
auskommentieren wenn nicht erwünscht
*/
#ifndef WOKWI
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
#endif
lastCheckTime = millis();
/* 14.11.25 Zähler der Alarme */
// --- Logik zum Nullen beim Start ---
// Prüfen des RESET_PIN
if (digitalRead(RESET_PIN) == LOW) {
// Wenn der Pin mit Ground verbunden ist (LOW), nullen wir den Zähler im EEPROM
EEPROM.write(EEPROM_ADDR, 0);
zaehler = 0;
Serial.println(F("Zaehler wurde genullt (Reset-Pin war LOW)."));
} else {
// Andernfalls laden wir den letzten Wert aus dem EEPROM
zaehler = EEPROM.read(EEPROM_ADDR);
Serial.print(F("Zaehler aus EEPROM geladen: "));
Serial.println(zaehler);
}
// LED an Pin 9 entsprechend des Zählerstandes blinken lassen
for (uint8_t i = 0; i < zaehler; i++) {
digitalWrite(SP_PIN, HIGH);
delay(1000);
digitalWrite(SP_PIN, LOW);
delay(1000);
}
/* eingefügt 18-11-25 */
lastCheckTime = millis();
}
// ----------------------------------------------------------
// Hauptschleife
// ----------------------------------------------------------
void loop() {
now = rtc.now();
getPinData();
if (millis() - lastCheckTime >= _ONESECOND) {
lastCheckTime = millis();
getSensorsData();
checkAlarm();
/* Blinken wenn erfolgreich geschrieben wurde */
if (writeSuccess == true) { digitalWrite(SP_PIN, !digitalRead(SP_PIN)); } // toggelt
else {
digitalWrite(SP_PIN, false);
}
/* loggen wenn Brenner Ein 13.11.25 */
if (digitalRead(BRENNER_PIN) == LOW && digitalRead(BZ_PIN) == LOW) {
data2LogFile();
}
/* Temperatur manuell setzen 14.11.25 */
temp(); // Temperaturwert Vorlauf manuell setzen
}
if (changed == true) {
data2LogFile();
changed = false;
}
}
void checkAlarm() {
// --------------------------------------------------------
// Alarmsteuerung mit Hysterese
// Erweitert um den zaehler 14.11.25
// nach dem 4ten auslösen wird das Relais
// nicht mehr automatisch zurückgesetzt
// --------------------------------------------------------
if (alarmState == alarmOff) {
if (tempVorlauf > ALARM_ON_TEMP) {
alarmState = alarmOn;
strcpy(speicher, "Alarm");
}
} else {
if (tempVorlauf < ALARM_OFF_TEMP && zaehler < 4) {
alarmState = alarmOff;
}
}
// Ausgang entsprechend setzen
digitalWrite(ALARM_PIN, alarmState);
zaehl();
}
//
void data2LogFile() {
// --------------------------------------------------------
// Loggen bei Änderung
// --------------------------------------------------------
char buf[200] = { '\0' };
// sprintf kann ggfls. kein float. Darum vorher zusammenbauen
char vlT[5] = { '\0' };
dtostrf(tempVorlauf, 2, 1, vlT);
char rlH[5] = { '\0' };
dtostrf(tempRueckl_HZG, 2, 1, rlH);
char rlW[5] = { '\0' };
dtostrf(tempRueckl_WW, 2, 1, rlW);
// CSV-Zeile zusammensetzen
// ACHTUNG! die zweite Zeile gehört dazu!
snprintf(buf, sizeof(buf), "%02d.%02d.%d,%02d:%02d:%02d,%s,%s,%s,%d,%d,%d,%d,%s,%d",
now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second(),
vlT, rlH, rlW, ventilWWStatus, brennerStatus, BZStatus, alarmState == alarmOff ? 0 : 1, speicher, zaehler);
// Auf SD-Karte schreiben
writeSuccess = false; // DEFAULT: Fehler beim Öffnen oder Schreiben
logfile = SD.open(heizungFile, FILE_WRITE);
if (logfile) {
logfile.println(buf);
writeSuccess = true; // Schreibvorgang erfolgreich
}
logfile.close();
// Ausgabe zur Kontrolle
Serial.println(buf);
strcpy(speicher, " ");
}
//
void getPinData() {
// --------------------------------------------------------
// Digitale Eingänge lesen (bei Optokopplern evtl. invertiert)
// --------------------------------------------------------
if (!digitalRead(BRENNER_PIN) != brennerStatus) {
brennerStatus = !brennerStatus;
strcpy(speicher, "LÜ");
changed = true;
}
if (!digitalRead(VENTIL_WW_PIN) != ventilWWStatus) {
ventilWWStatus = !ventilWWStatus;
strcpy(speicher, "WW");
changed = true;
}
if (!digitalRead(BZ_PIN) != BZStatus) {
BZStatus = !BZStatus;
strcpy(speicher, "BZ");
changed = true;
}
}
//
void getSensorsData() {
// --------------------------------------------------------
// Temperaturen erfassen
// --------------------------------------------------------
tempVorlauf = sensors.getTempCByIndex(1);
tempVorlauf = temperaturWert + tempVorlauf; // temperaturWert zum Simulieren eingefügt
tempRueckl_HZG = sensors.getTempCByIndex(0);
tempRueckl_WW = sensors.getTempCByIndex(2);
sensors.requestTemperatures(); // löst neuen Lese-Zyklus aus! Braucht je nach Auflösung bis zu 750ms
// Serial.println(tempVorlauf);
}
void sdCardInit() {
/* 31.10.2025 */
if (!SD.begin(SD_CS_PIN)) {
Serial.println(F("SD-Karte nicht initialisierbar oder nicht vorhanden."));
writeSuccess = false; // Initialisierungsfehler
} else {
writeSuccess = true;
}
if (writeSuccess == true) {
Serial.println(F("SD-Karte initialisiert."));
// Logdatei vorbereiten
logfile = SD.open(heizungFile, FILE_WRITE);
Serial.print(F("System "));
if (logfile) {
logfile.println(F("Datum,Uhrzeit,Vorl [°C], HZG [°C], WW [°C] , WW , BR , BZ , Al , ,ALZä"));
logfile.close();
} else {
writeSuccess = false;
Serial.print(F("nicht "));
}
Serial.println(F("bereit"));
}
}
//
void inputPinInit() {
// Eingänge konfigurieren
pinMode(BRENNER_PIN, INPUT_PULLUP);
pinMode(RESET_PIN, INPUT_PULLUP);
pinMode(VENTIL_WW_PIN, INPUT_PULLUP);
pinMode(BZ_PIN, INPUT_PULLUP);
getPinData();
sensors.begin();
/* eingefügt 18-11-252*/
sensors.requestTemperatures(); // löst neuen Lese-Zyklus aus! Braucht je nach Auflösung bis zu 750ms
}
//
void temp() {
// 1. Seriellen Monitor auf neue Eingaben pruefen
if (Serial.available() > 0) {
// Lesen des eingegebenen Float-Wertes
float eingabe = Serial.parseFloat();
// Prüfen, ob die Eingabe gültig war (parseFloat gibt 0.0 zurück, wenn keine gültige Zahl gefunden wurde)
if (eingabe != 0.0 || (eingabe == 0.0 && Serial.peek() == '0')) {
temperaturWert = eingabe;
benutzeEingabeWert = true;
Serial.print("Temperatur auf manuellen Wert gesetzt: ");
Serial.println(temperaturWert);
} else {
Serial.println("Ungueltige Eingabe.");
}
// Puffer leeren, um alte Daten zu entfernen
while (Serial.available() > 0) {
Serial.read();
}
}
// Serial.print(F(" Temperatur eingegeben : "));
// Serial.println(temperaturWert);
}
//
void zaehl() {
// Aktuellen Zustand des Pins lesen
int currentAlarmState = digitalRead(ALARM_PIN);
// Prüfen, ob eine fallende Flanke vorliegt:
// War der Pin vorher HIGH UND ist jetzt LOW?
if (currentAlarmState == LOW && lastAlarmState == HIGH) {
zaehler++;
changed = true;
Serial.print(F("Flanke erkannt. Zaehler: "));
Serial.println(zaehler);
// Sofort nach dem Inkrementieren im EEPROM speichern
EEPROM.write(EEPROM_ADDR, zaehler);
}
// Den aktuellen Zustand für den nächsten Durchlauf speichern
lastAlarmState = currentAlarmState;
}Reset
BZ
Brenner
Ventil
Speichern
Alarm
Rücklauf
Heizung
Vorlauf
Heizung
Rücklauf
Warmwasser