Im Beitrag Arduino und DS3231: Relais jeden Tag automatisch zur gleichen Uhrzeit schalten habe ich dir gezeigt, wie du ein Relais zu einem fest definierten Zeitpunkt aktivierst. Aufbauend auf diesem Wissen erkläre ich dir heute, wie du das System erweiterst, um an mehreren Zeitpunkten pro Tag automatische Schaltvorgänge durchzuführen.
Hinweis: Die Idee zu dieser Erweiterung entstand aus einem Wunsch eines Lesers meines Blogs, der sich eine Mehrfachschaltung des Relais gewünscht hat. Wenn auch du Anregungen, Fragen oder Wünsche hast, melde dich gerne per E-Mail oder schreibe mir in den Kommentaren.
Inhaltsverzeichnis
- Rückblick – Aufbau der Schaltung und benötigte Ressourcen
- Erweitern des bestehenden Codes
- Ausblick – Relais zu variablen Uhrzeiten schalten
Rückblick – Aufbau der Schaltung und benötigte Ressourcen
Für den Aufbau der Schaltung benötigt man:
- einen Arduino Nano V3*
- ein USB-Datenkabel*
- eine RTC DS3231*
- ein 2fach Relais-Modul*
- ein 20×2 LC-Display mit I2C Schnittstelle*
- diverse Breadboardkabel*
- ein 400 Pin Breadboard*
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Für die Schaltung verwende ich den Maker Nano welchen ich dir im Beitrag Vorstellung des Maker Nano von Cytron vorgestellt habe. Dieser hat die Abmaße des Arduino Nano bietet aber ein paar nette zusätzliche Features.
Erweitern des bestehenden Codes
Wie bereits erwähnt, habe ich das kleine Projekt bereits vorgestellt. Nun gehe ich auf die notwendige kleine Änderung ein, die es ermöglicht, pro Tag mehrere Schaltvorgänge umzusetzen.
Schritt 1 – Strukturen zum ablegen der Zeiten anpassen
Da das Projekt nun darauf abzielt, das Relais täglich zu festen Uhrzeiten zu steuern, ist es nicht mehr notwendig, auch das Datum zu berücksichtigen. Ursprünglich war die Struktur so aufgebaut, dass sie an einem bestimmten Datum und zu einer bestimmten Uhrzeit aktivierte – das Datum war also ein entscheidendes Kriterium. Jetzt, wo die Schaltvorgänge jeden Tag erfolgen, genügt es, nur die Uhrzeit zu prüfen. Das Datumsfeld kann daher entfernt werden, was den Code übersichtlicher und zielgerichteter macht.
// Struktur für einen RTC-Zeitstempel. // Speichert Datum und Zeit als Strings, wie sie z.B. von einer Real-Time Clock (RTC) geliefert werden. struct RTC_Zeitstempel { String datum; // Datum als String, z.B. "2025-02-23" String zeit; // Zeit als String, z.B. "12:34:56" }; // Struktur für einen allgemeinen Zeitstempel. // Enthält zwei Zeitangaben: eine für das Einschalten (ON) und eine für das Ausschalten (OFF) des Relais. struct Zeitstempel { String zeitON; // Zeitpunkt, zu dem das Relais eingeschaltet werden soll String zeitOFF; // Zeitpunkt, zu dem das Relais ausgeschaltet werden soll }; // Struktur zur Darstellung eines Relais. // Speichert Informationen über das Relais und die zugehörigen Schaltzeitpunkte. struct Relais { int digitalPin; // Digitaler Pin, an den das Relais angeschlossen ist String desc; // Beschreibung oder Name des Relais int numZeitstempel; // Anzahl der aktuell gesetzten Zeitstempel im Array Zeitstempel zeitstempel[10]; // Array, das bis zu 10 Zeitstempel (Schaltvorgänge) aufnehmen kann };
Schritt 2 – Anpassen der Funktion zum auslesen der aktuellen Uhrzeit von der RTC DS3231
Die Struktur zum speichern des aktuellen Zeitstempels habe ich umbenannt, daher musste im Code dieses an den entsprechenden Stellen angepasst werden.
void loop() { ... //bleibt leer RTC_Zeitstempel zeitstempel = readRtc(); … } //auslesen der Daten von der RealtimeClock RTC_Zeitstempel readRtc() { ... } void checkCurrentTimestamp(Relais relais, RTC_Zeitstempel zeitstempel) { ... }
Schritt 3 – prüfen aller gespeicherten Zeiten
Die Funktion checkCurrentTimestamp dient dazu den aktuellen Zeitstempel gegen die gespeicherten Zeiten für die beiden Relais zu prüfen.
// Funktion zum Überprüfen, ob der aktuelle RTC-Zeitstempel einem // der definierten Schaltzeitpunkte des Relais entspricht. void checkCurrentTimestamp(Relais relais, RTC_Zeitstempel zeitstempel) { // Schleife über alle gesetzten Zeitstempel für dieses Relais for (int index = 0; index < relais.numZeitstempel; index++) { // Aktuellen Zeitstempel (mit On- und Off-Zeit) aus dem Array lesen Zeitstempel relaisZeitstempel = relais.zeitstempel[index]; // Überprüfen, ob die aktuelle Zeit mit der Einschaltzeit übereinstimmt if (zeitstempel.zeit == relaisZeitstempel.zeitON) { // Ausgabe an den seriellen Monitor, dass das Relais aktiviert wird Serial.println("aktivieren"); // Relais aktivieren digitalWrite(relais.digitalPin, LOW); // Überprüfen, ob die aktuelle Zeit mit der Ausschaltzeit übereinstimmt } else if (zeitstempel.zeit == relaisZeitstempel.zeitOFF) { // Ausgabe an den seriellen Monitor, dass das Relais deaktiviert wird Serial.println("deaktivieren"); // Relais deaktivieren digitalWrite(relais.digitalPin, HIGH); } } }
fertiges Projekt
Hier kannst du das fertige Programm als ZIP-Datei herunterladen.
Quellcode
/* * Autor: Stefan Draeger * Webseite: https://draeger-it.blog * Blogbeitrag: https://draeger-it.blog/arduino-und-ds3231-relais-jeden-tag-automatisch-zu-variablen-uhrzeiten-schalten/ * * Benötigte Bauteile: * - Arduino Nano V3 * - USB-Datenkabel * - RTC DS3231 * - 2fach Relais-Modul * - 20x2 LC-Display mit I2C Schnittstelle * - diverse Breadboardkabel * - 400 Pin Breadboard */ // Bibliothek zum Ansteuern des LCD-Displays via I2C #include <LiquidCrystal_I2C.h> // Bibliothek für die Kommunikation mit der RTC DS3231 #include <Wire.h> // Bibliothek zur Kommunikation mit dem Bluetooth Modul über SoftwareSerial #include <SoftwareSerial.h> // I2C Adresse des RTC DS3231 #define RTC_I2C_ADDRESS 0x68 // Struktur zur Speicherung eines RTC-Zeitstempels // Enthält Datum und Uhrzeit als Strings struct RTC_Zeitstempel { String datum; // Datum, z.B. "23.02.2025" String zeit; // Uhrzeit, z.B. "12:34:56" }; // Struktur zur Speicherung eines Schalt-Zeitstempels für ein Relais // Definiert, wann das Relais eingeschaltet (zeitON) und wann es ausgeschaltet (zeitOFF) werden soll struct Zeitstempel { String zeitON; // Zeitpunkt zum Aktivieren des Relais String zeitOFF; // Zeitpunkt zum Deaktivieren des Relais }; // Struktur zur Beschreibung eines Relais und seiner Schaltzeitpunkte struct Relais { int digitalPin; // Der digitale Pin, an dem das Relais angeschlossen ist String desc; // Beschreibung oder Name des Relais int numZeitstempel; // Anzahl der definierten Zeitstempel im Array Zeitstempel zeitstempel[10]; // Array zur Speicherung von bis zu 10 Schalt-Zeitstempeln }; // Initialisierung des LCD-Displays via I2C // Adresse: 0x27, 20 Zeichen pro Zeile, 2 Zeilen LiquidCrystal_I2C lcd(0x27, 20, 2); // Puffer zum Speichern von seriellen Eingaben char linebuf[30] = {}; bool readData = false; // Definition des ersten Relais (Relais #1) // Enthält 3 Schalt-Zeitstempel Relais relais1 = { 9, // Digitaler Pin 9 "Relais #1", // Beschreibung 3, // Anzahl der Zeitstempel { { "12:48:00", "12:48:10" }, { "12:48:25", "12:48:30" }, { "12:48:40", "12:49:00" } } }; // Definition des zweiten Relais (Relais #2) // Enthält 1 Schalt-Zeitstempel Relais relais2 = { 8, // Digitaler Pin 8 "Relais #2", // Beschreibung 1, // Anzahl der Zeitstempel { { "12:48:00", "12:49:00" } } }; void setup() { // Start der seriellen Kommunikation Serial.begin(9600); // Initialisierung des LCD-Displays lcd.init(); // Aktivierung der Hintergrundbeleuchtung des Displays lcd.backlight(); // Setzt die digitalen Pins der Relais als Ausgang pinMode(relais1.digitalPin, OUTPUT); pinMode(relais2.digitalPin, OUTPUT); // Setzt beide Relais initial auf HIGH (in diesem Fall deaktiviert) digitalWrite(relais1.digitalPin, HIGH); digitalWrite(relais2.digitalPin, HIGH); } void loop() { // Lese Daten von der seriellen Schnittstelle readDataFromSerial(); // Wenn Daten empfangen wurden, verarbeite den Zeitstempel if (readData) { String timestamp = linebuf; // Das Datum wird als 10 Zeichen (inkl. Punkte) erwartet String datum = timestamp.substring(0, 10); String tag = datum.substring(0, 2); String monat = datum.substring(3, 5); String jahr = datum.substring(6, 10); // Die Uhrzeit beginnt ab dem 11. Zeichen des Zeitstempels String uhrzeit = timestamp.substring(11); String stunde = uhrzeit.substring(0, 2); String minute = uhrzeit.substring(3, 5); String sekunde = uhrzeit.substring(6); // Setzt den RTC-Zeitstempel entsprechend der empfangenen Werte rtcWriteTimestamp(stunde.toInt(), minute.toInt(), sekunde.toInt(), tag.toInt(), monat.toInt(), jahr.toInt()); } // Lese den aktuellen Zeitstempel von der RTC RTC_Zeitstempel zeitstempel = readRtc(); // Anzeige des Datums auf der ersten Zeile des LCD-Displays lcd.setCursor(0, 0); lcd.print(zeitstempel.datum); // Anzeige der Uhrzeit auf der zweiten Zeile des LCD-Displays lcd.setCursor(0, 1); lcd.print(zeitstempel.zeit); // Überprüfung der aktuellen Uhrzeit mit den definierten Schalt-Zeitstempeln der Relais checkCurrentTimestamp(relais1, zeitstempel); checkCurrentTimestamp(relais2, zeitstempel); // Kurze Pause zur Entlastung delay(500); } // Funktion zur Überprüfung, ob der aktuelle RTC-Zeitstempel // mit einem der Schalt-Zeitstempel eines Relais übereinstimmt void checkCurrentTimestamp(Relais relais, RTC_Zeitstempel zeitstempel) { // Schleife durch alle definierten Zeitstempel für das gegebene Relais for (int index = 0; index < relais.numZeitstempel; index++) { // Lese den aktuellen Schalt-Zeitstempel aus dem Array Zeitstempel relaisZeitstempel = relais.zeitstempel[index]; // Falls die aktuelle Uhrzeit dem Einschaltzeitpunkt entspricht: if (zeitstempel.zeit == relaisZeitstempel.zeitON) { Serial.println("aktivieren"); // Relais aktivieren (in diesem Fall wird LOW gesetzt, um zu schalten) digitalWrite(relais.digitalPin, LOW); } // Falls die aktuelle Uhrzeit dem Ausschaltzeitpunkt entspricht: else if (zeitstempel.zeit == relaisZeitstempel.zeitOFF) { Serial.println("deaktivieren"); // Relais deaktivieren (HIGH schaltet das Relais aus) digitalWrite(relais.digitalPin, HIGH); } } } // Funktion zum Einlesen von Daten von der seriellen Schnittstelle void readDataFromSerial() { // Zähler für die empfangenen Zeichen byte counter = 0; readData = false; // Prüfen, ob Daten verfügbar sind if (Serial.available() > 0) { delay(250); // Kurze Verzögerung, um sicherzustellen, dass alle Daten empfangen wurden readData = true; // Solange Daten von der seriellen Schnittstelle verfügbar sind: while (Serial.available()) { // Lese ein Zeichen aus der seriellen Schnittstelle char c = Serial.read(); // Füge das Zeichen in den Puffer ein, solange es nicht das Zeilenumbruch-Zeichen ist if (c != '\n') { linebuf[counter] = c; // Stelle sicher, dass der Puffer nicht überläuft if (counter < sizeof(linebuf) - 1) { counter++; } } } // Ausgabe des eingelesenen Puffers zur Kontrolle Serial.println(linebuf); } } // Funktion zum Schreiben eines Zeitstempels in den RTC DS3231 // Übergabe: Stunde, Minute, Sekunde, Tag, Monat, Jahr void rtcWriteTimestamp(int stunde, int minute, int sekunde, int tag, int monat, int jahr) { Wire.beginTransmission(RTC_I2C_ADDRESS); Wire.write(0); // Setzt den internen Zeiger des RTC auf 0 (Beginn des Registers) // Schreibe die Zeitdaten in BCD-Format in die RTC Wire.write(decToBcd(sekunde)); Wire.write(decToBcd(minute)); Wire.write(decToBcd(stunde)); Wire.write(decToBcd(0)); // Wochentag wird hier nicht berücksichtigt Wire.write(decToBcd(tag)); Wire.write(decToBcd(monat)); // Das Jahr wird als Offset zu 2000 gespeichert Wire.write(decToBcd(jahr - 2000)); Wire.endTransmission(); } // Funktion zum Auslesen der aktuellen Zeit vom RTC DS3231 RTC_Zeitstempel readRtc() { // Aufbau der Verbindung zum RTC Wire.beginTransmission(RTC_I2C_ADDRESS); Wire.write(0); // Setzt den internen Zeiger auf 0 Wire.endTransmission(); // Fordert 7 Bytes an: Sekunden, Minuten, Stunde, Wochentag, Tag, Monat, Jahr Wire.requestFrom(RTC_I2C_ADDRESS, 7); int sekunde = bcdToDec(Wire.read() & 0x7f); // Sekunde (Bit 7 wird maskiert) int minute = bcdToDec(Wire.read()); int stunde = bcdToDec(Wire.read() & 0x3f); // Stunde (nur 24-Stunden Format) int wochentag = bcdToDec(Wire.read()); // Wochentag (wird nicht weiter genutzt) int tag = bcdToDec(Wire.read()); int monat = bcdToDec(Wire.read()); int jahr = bcdToDec(Wire.read()) + 2000; // Jahr als Offset zu 2000 // Formatierung des Datums als "TT.MM.JJJJ" char datum[30]; sprintf(datum, "%02d.%02d.%4d", tag, monat, jahr); // Formatierung der Uhrzeit als "HH:MM:SS" char zeit[30]; sprintf(zeit, "%02d:%02d:%02d", stunde, minute, sekunde); // Rückgabe des Zeitstempels als Struktur return { datum, zeit }; } // Hilfsfunktion zum Konvertieren von Dezimal in BCD (Binary Coded Decimal) byte decToBcd(byte val) { return ((val / 10 * 16) + (val % 10)); } // Hilfsfunktion zum Konvertieren von BCD in Dezimal byte bcdToDec(byte val) { return ((val / 16 * 10) + (val % 16)); }
Ausblick – Relais zu variablen Uhrzeiten schalten
Nachdem in diesem Projektkommentar auf Wunsch eines Lesers bereits die Funktionalität um mehrere Schaltzeitpunkte erweitert wurde, steht nun der nächste Schritt an: der Umstieg auf den ESP32. Mit dem integrierten WiFi bietet der ESP32 enorme Vorteile, denn zukünftig sollen die Schaltzeiten flexibel und benutzerfreundlich über eine eigene Webseite konfigurierbar sein. Außerdem wird die aktuelle Uhrzeit nicht mehr ausschließlich von einer RTC ermittelt, sondern direkt von einem NTP-Zeitserver aus dem Internet bezogen – für eine präzise und wartungsfreie Zeitsynchronisation.
Diese Neuerungen machen das Relaissteuerungs-Projekt noch flexibler und vielseitiger. Ich freue mich darauf, in den kommenden Beiträgen mehr über die Umsetzung und die erweiterten Möglichkeiten zu berichten. Bleib also gespannt!