// Arduinoforum
// Beitrag https://forum.arduino.cc/t/onboard-led-zur-fehlererkennung/1411562
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
#include <Streaming.h>
Print &cout {Serial};
using Millis_t = decltype(millis()); // Datentyp für Millis
using Register_t = uint8_t;
//
// Globale Konstanten
//
// Decice IDs müssen an reale IDs angepaast werden
constexpr DeviceAddress gcDS18B20dev[3] {
{0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x9B},
{0x10, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3B},
{0x10, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5B},
};
// ----------------------------------------------------------
// Pinbelegung angepasst 30-10-25
// ----------------------------------------------------------
constexpr uint8_t gcOneWirePin {2};
constexpr uint8_t gcVentilWWPin {4};
constexpr uint8_t gcVentilHzgPin {5};
constexpr uint8_t gcBrennerPin {6};
constexpr uint8_t gcBzPin {7};
constexpr uint8_t gcAlarmPin {8};
constexpr uint8_t gcSDCardCSPin {10};
/* 31.10.25 Erweiterung Fehlererkennung mit ON-Board-Led */
constexpr uint8_t gcLedPin = A3; // Entspricht Pin 13 auf dem Arduino UNO 31.10.25
constexpr uint8_t gcFloatToStrLen {8}; // Lenght + '\0'
constexpr Millis_t gcIntervallSchreibe_ms {1000}; // LED x Millisekunden an
constexpr Millis_t gcIntervallSDError_ms {200}; // Blink-Intervall (x an, x ms aus)
// Schwellwerte für Alarm
constexpr float gcAlarmEinTemp {70.0}; // Alarm EIN über 70°C
constexpr float gcAlarmAusTemp {68.0}; // Alarm AUS unter 68°C
// Bitposition Status Flag Register
constexpr uint8_t gcVentilHzgStatusBit {0};
constexpr uint8_t gcVentilWWStatusBit {1};
constexpr uint8_t gcBrennerStatusBit {2};
constexpr uint8_t gcBzStatusBit {3};
constexpr uint8_t gcAlarmStatusBit {4};
constexpr const char* gcDateiname {"heizung.csv"};
// ----------------------------------------------------------
// Definitionen Klassen / Datentypen
// ----------------------------------------------------------
class Timer {
public:
void start() { timeStamp = millis(); }
bool operator()(const Millis_t duration) const { return (millis() - timeStamp >= duration); }
private:
Millis_t timeStamp {0};
};
struct FloatBuffer { // Für snprintf bzw. dtostrf
char tVorlauf[gcFloatToStrLen];
char tRuecklHzg[gcFloatToStrLen];
char tRuecklWW[gcFloatToStrLen];
} float2strBuffer;
struct Temperaturen {
float vorlauf;
float ruecklaufHzg;
float ruecklaufWW;
};
struct Alarm {
uint8_t pin;
uint8_t registerShift;
float einTemperatur;
float ausTemperatur;
};
struct HeizungDaten {
const uint8_t ventilHzgPin;
const uint8_t ventilHzgShift;
const uint8_t ventilWWPin;
const uint8_t ventilWWShift;
const uint8_t brennerPin;
const uint8_t brennerShift;
const uint8_t bzPin;
const uint8_t bzShift;
};
// ----------------------------------------------------------
// Globale Objekte
// ----------------------------------------------------------
Alarm alarm {gcAlarmPin, gcAlarmStatusBit, gcAlarmEinTemp, gcAlarmAusTemp};
HeizungDaten hzDaten {gcVentilHzgPin, gcVentilHzgStatusBit, gcVentilWWPin, gcVentilWWStatusBit,
gcBrennerPin, gcBrennerStatusBit, gcBzPin, gcBzStatusBit};
OneWire oneWire(gcOneWirePin);
DallasTemperature sensors(&oneWire);
RTC_DS3231 rtc;
Timer warte;
Timer statusCheck;
// Temperatursensor Adressen anzeigen
void showDs18b20Id(OneWire& ow) {
uint8_t numDevices {0};
uint8_t address[8];
while (ow.search(address)) {
cout << F("Bus index=") << numDevices << F("; Sensor Adresse=");
for (auto const& id : address) { cout << _HEX(id) << ' '; }
cout << endl;
++numDevices;
}
}
// --------------------------------------------------------
// Temperaturen erfassen
// --------------------------------------------------------
Temperaturen holeTemperaturen(DallasTemperature& ts) {
ts.requestTemperatures();
return {ts.getTempC(gcDS18B20dev[0]), ts.getTempC(gcDS18B20dev[1]), ts.getTempC(gcDS18B20dev[2])};
}
// --------------------------------------------------------
// Alarmsteuerung mit Hysterese
// --------------------------------------------------------
Register_t pruefeVorlauftemperatur(Register_t statusReg, const Temperaturen& tmp, const Alarm& alarm) {
bool isAlarm = bitRead(statusReg, alarm.registerShift);
Register_t statusFlagRegNeu {statusReg};
if (!isAlarm && tmp.vorlauf > alarm.einTemperatur) {
// Temperatur überschreitet 70°C → Alarm EIN
bitSet(statusFlagRegNeu, alarm.registerShift);
digitalWrite(alarm.pin, HIGH);
cout << F("Alarm on\n");
} else if (isAlarm && tmp.vorlauf < alarm.ausTemperatur) {
// Temperatur fällt unter 68°C → Alarm AUS
bitClear(statusFlagRegNeu, alarm.registerShift);
digitalWrite(alarm.pin, LOW);
cout << F("Alarm off\n");
}
return statusFlagRegNeu;
}
// --------------------------------------------------------
// Digitale Eingänge lesen (bei Optokopplern evtl. invertiert)
// --------------------------------------------------------
Register_t pruefePins(const HeizungDaten& hzd) {
Register_t statusFlagRegNeu {0};
!digitalRead(hzd.ventilHzgPin) ? bitSet(statusFlagRegNeu, hzd.ventilHzgShift)
: bitClear(statusFlagRegNeu, hzd.ventilHzgShift);
!digitalRead(hzd.ventilWWPin) ? bitSet(statusFlagRegNeu, hzd.ventilWWShift)
: bitClear(statusFlagRegNeu, hzd.ventilWWShift);
!digitalRead(hzd.brennerPin) ? bitSet(statusFlagRegNeu, hzd.brennerShift)
: bitClear(statusFlagRegNeu, hzd.brennerShift);
!digitalRead(hzd.bzPin) ? bitSet(statusFlagRegNeu, hzd.bzShift) : bitClear(statusFlagRegNeu, hzd.bzShift);
// cout << F("RegisterNeu in Pins: ") << statusFlagRegNeu << endl;
return statusFlagRegNeu;
}
// --------------------------------------------------------
// Loggen bei Änderung
// --------------------------------------------------------
bool schreibeLog(RTC_DS3231& uhr, const Temperaturen& t, const HeizungDaten& hzd, const Alarm& al, FloatBuffer& fb,
uint8_t stfRegister) {
bool logErfolgreich {false};
char buffer[60];
dtostrf(t.vorlauf, 3, 1, fb.tVorlauf);
dtostrf(t.ruecklaufHzg, 3, 1, fb.tRuecklHzg);
dtostrf(t.ruecklaufWW, 3, 1, fb.tRuecklWW);
DateTime now = uhr.now();
// TT.MM.JJJJ-HH:MM:SS,12345.0,12345.0,12345.0,12,12,12,12,12 // 58 Zeichen + '\0' = min. Puffergöße = 59
snprintf(buffer, sizeof(buffer), "%02u.%02u.%04u-%02u:%02u:%02u,%7s,%7s,%7s,%2u,%2u,%2u,%2u,%2u", now.day(),
now.month(), now.year(), now.hour(), now.minute(), now.second(), float2strBuffer.tVorlauf, float2strBuffer.tRuecklHzg,
float2strBuffer.tRuecklWW, bitRead(stfRegister, hzd.ventilHzgShift), bitRead(stfRegister, hzd.ventilWWShift),
bitRead(stfRegister, hzd.brennerShift), bitRead(stfRegister, hzd.bzShift),
bitRead(stfRegister, al.registerShift));
// Auf SD-Karte schreiben
File logfile = SD.open(gcDateiname, FILE_WRITE);
if (logfile) {
logfile.println(buffer);
logfile.close();
/* 31.10.25 */
logErfolgreich = true; // Schreibvorgang erfolgreich
}
// Ausgabe zur Kontrolle
cout << buffer << endl;
// cout << logErfolgreich << endl;
return logErfolgreich;
}
// ----------------------------------------------------------
// Setup
// ----------------------------------------------------------
void setup() {
Serial.begin(115200);
cout << F("=== Heizungsüberwachung startet ===\n");
// Eingänge konfigurieren
pinMode(hzDaten.ventilHzgPin, INPUT_PULLUP);
pinMode(hzDaten.ventilWWPin, INPUT_PULLUP);
pinMode(hzDaten.brennerPin, INPUT_PULLUP);
pinMode(hzDaten.bzPin, INPUT_PULLUP);
// Ausgang konfigurieren (Alarm)
pinMode(alarm.pin, OUTPUT);
digitalWrite(alarm.pin, LOW); // Anfangszustand: aus
/* 31.10.2025 */
// Setze den LED-Pin als Ausgang
pinMode(gcLedPin, OUTPUT);
// Sensoren starten
sensors.begin();
showDs18b20Id(oneWire);
delay(3000);
// RTC starten
if (!rtc.begin()) {
cout << F("RTC nicht gefunden!\n");
/* 31.10.25 */
while (1);
}
/* 31.10.2025 */
if (!SD.begin(gcSDCardCSPin)) {
cout << F("SD-Karte nicht initialisierbar oder nicht vorhanden.\n");
while (1) {
if (warte(gcIntervallSDError_ms)) {
warte.start();
digitalWrite(gcLedPin, !digitalRead(gcLedPin));
}
}
}
cout << F("SD-Karte initialisiert.\n");
// Logdatei vorbereiten
File logfile = SD.open(gcDateiname, FILE_WRITE);
if (logfile) {
logfile.println(F("Datum,Uhrzeit,Vorlauf [°C],Rücklauf HZG [°C],Rücklauf WW [°C], HZG , WW ,Brenner, BZ ,Alarm"));
logfile.close();
}
cout << F("System bereit.\n");
}
// ----------------------------------------------------------
// Hauptschleife
// ----------------------------------------------------------
void loop() {
static Register_t statusFlagRegisterSpeicher {0}; // Letzte bekannte Zustände
static bool schreibenErfolgt {false};
// Nur jede Sekunde einmal Temperatur und Pins checken.
if (statusCheck(1000)) { // Wahr wenn Timer abgelaufen ist
statusCheck.start(); // Timer neu starten
Temperaturen temp = holeTemperaturen(sensors);
Register_t statusFlagRegister = pruefeVorlauftemperatur(statusFlagRegisterSpeicher, temp, alarm);
statusFlagRegister &= (1 << alarm.registerShift); // alle Statusbits außer dem Alarmbit löschen
statusFlagRegister |= pruefePins(hzDaten); // Statusbits für Heizungsins setzen
cout << F("Register : ") << _WIDTHZ(_BIN(statusFlagRegister),8) << '\n';
cout << F("Registerspeicher: ") << _WIDTHZ(_BIN(statusFlagRegisterSpeicher),8) << '\n';
// Prüfen auf Zustandsänderung oder Alarmwechsel und loggen bei Änderung
if (statusFlagRegister != statusFlagRegisterSpeicher) {
statusFlagRegisterSpeicher = statusFlagRegister;
schreibenErfolgt = schreibeLog(rtc, temp, hzDaten, alarm, float2strBuffer, statusFlagRegister);
digitalWrite(gcLedPin, HIGH);
warte.start(); // Starte Timer für Schreiben LED
}
}
if (schreibenErfolgt && warte(gcIntervallSchreibe_ms)) {
digitalWrite(gcLedPin, LOW);
schreibenErfolgt = false;
}
}Blaue LED:
SD-Karte Fehler: schnell blinkend
Schreiben: 1Sek leuchten
Rote LED:
Alarm
Temperaturwechsel
Sensor anklicken
Vorlauf
Anklicken
oder Taste:
Y
X
C
V
Loading
ds18b20
ds18b20
Loading
ds18b20
ds18b20
Loading
ds18b20
ds18b20