Zeitschaltuhr mit ESP32, DS3231 & Relais bauen: Geräte zeitgesteuert schalten
Eine Baumarkt-Zeitschaltuhr kann genau eines: zu festen Uhrzeiten ein- und ausschalten. Deine selbst gebaute Version kann viel mehr – beliebig viele Schaltzeiten, sekundengenaue Uhr auch nach Stromausfall, Statusanzeige und jederzeit im Code anpassbar. In diesem Tutorial baust du eine echte Zeitschaltuhr aus dem ESP32, der präzisen DS3231-Echtzeituhr und einem KY-019-Relais. Du legst mehrere Schaltzeiten fest, lässt das Relais automatisch schalten und zeigst Uhrzeit und Zustand optional auf einem OLED an. Kompletter Code und eine unmissverständliche Sicherheitswarnung zum Thema Netzspannung inklusive.
Warum selbst bauen statt Steckdosen-Timer kaufen?
Fertige Zeitschaltuhren sind billig – aber starr. Willst du eine Aquarium-Beleuchtung morgens und abends für je zwei Stunden, dazwischen aus, plus eine CO₂-Pumpe mit eigenem Rhythmus, stößt die Baumarktlösung schnell an ihre Grenzen. Eine selbst gebaute Schaltuhr auf ESP32-Basis kennt diese Grenzen nicht: Du definierst so viele Schaltzeiten, wie du willst, kombinierst Bedingungen und kannst das Ganze später sogar per WLAN steuern.
Das Herzstück der Genauigkeit ist die DS3231-Echtzeituhr. Sie hat einen eigenen, temperaturkompensierten Quarz und eine Knopfzelle – dadurch tickt sie autark weiter, selbst wenn der ESP32 stromlos ist. Nach einem Stromausfall kennt deine Schaltuhr also sofort wieder die exakte Zeit, ohne dass du irgendetwas neu stellen musst. Ihre Abweichung liegt bei nur rund zwei Minuten pro Jahr.
⚠️ Wichtige Sicherheitswarnung – bitte zuerst lesen: Sobald du mit dem Relais echte 230-V-Netzspannung schaltest, wird das Projekt lebensgefährlich, wenn du Fehler machst. Arbeiten an der Netzspannung dürfen nur von entsprechend ausgebildeten Personen ausgeführt werden. Zum Lernen und Testen schaltest du ausschließlich ungefährliche Kleinspannung (z. B. eine 5-V-LED-Leiste oder einen 12-V-Verbraucher aus einem Steckernetzteil). Alle Beispiele in diesem Artikel gehen von Kleinspannung aus.
Das brauchst du
ℹ️ DS3231 und OLED teilen sich einen Bus: Beide sprechen über I2C – also nur zwei Datenleitungen (SDA und SCL), an die du beide parallel anschließt. Das Relais hängt an einem einfachen digitalen Ausgang. Wie der I2C-Bus mehrere Geräte an zwei Kabeln verwaltet, erklärt der Artikel I2C-Bus mit Arduino verstehen.
1Verkabelung
Zuerst die DS3231 und das OLED an den I2C-Bus des ESP32, dann das Relais an einen freien GPIO:
| Modul | Pin | ESP32 |
|---|---|---|
| DS3231 | VCC / GND | 3V3 / GND |
| DS3231 | SDA / SCL | GPIO 21 / GPIO 22 |
| OLED | VCC / GND | 3V3 / GND |
| OLED | SDA / SCL | GPIO 21 / GPIO 22 |
| KY-019 | VCC / GND | 5V (VIN) / GND |
| KY-019 | S (Signal) | GPIO 26 |
⚠️ Das Relais braucht 5 V: Die Relaisspule zieht mehr Strom, als ein 3,3-V-Pin liefern kann. Versorge das KY-019 deshalb am VIN/5V-Pin des ESP32 (der aus dem USB-Strom kommt), nicht an 3V3. Das Signal (S) darf am 3,3-V-GPIO bleiben – das KY-019 ist ein High-Level-Trigger und reagiert zuverlässig darauf. Wie du Relais grundsätzlich richtig ansteuerst, vertieft der Artikel Relais-Modul KY-019 schalten.
2Die Uhr einmalig stellen
Damit die Schaltuhr weiß, wie spät es ist, setzen wir die DS3231 einmalig auf die aktuelle Zeit. Wir nutzen die RTClib von Adafruit (über den Bibliotheksverwalter installieren). Der folgende Sketch stellt die Uhr auf die Compile-Zeit deines PCs.
#include <Wire.h>
#include "RTClib.h"
RTC_DS3231 rtc;
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA=21, SCL=22 beim ESP32
if (!rtc.begin()) {
Serial.println("DS3231 nicht gefunden!");
while (1) delay(100);
}
// Setzt die Uhr auf die Compile-Zeit.
// NUR EINMAL aktiv lassen, danach auskommentieren!
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
void loop() {
DateTime t = rtc.now();
Serial.printf("%02d:%02d:%02d\n", t.hour(), t.minute(), t.second());
delay(1000);
}
⚠️ adjust()-Zeile danach auskommentieren! Solange rtc.adjust(...) aktiv im Code steht, wird die Uhr bei jedem Neustart wieder auf die (dann veraltete) Compile-Zeit zurückgesetzt. Setze die Zeit einmal, kommentiere die Zeile aus und lade neu hoch. Ab jetzt läuft die DS3231 dank Knopfzelle autark weiter. Mehr zur DS3231 im Artikel DS3231 RTC Echtzeituhr mit Arduino.
3Das Relais testen
Bevor wir Zeiten programmieren, prüfen wir, ob das Relais sauber schaltet. Dieser Sketch klickt es im Sekundentakt an und aus – du hörst das charakteristische Klicken und siehst die LED auf dem Modul.
const int RELAIS = 26;
void setup() {
pinMode(RELAIS, OUTPUT);
}
void loop() {
digitalWrite(RELAIS, HIGH); // Relais an (High-Level-Trigger)
delay(1000);
digitalWrite(RELAIS, LOW); // Relais aus
delay(1000);
}
ℹ️ NO oder NC? Das Relais hat drei Schraubklemmen: COM (gemeinsam), NO (normally open – offen in Ruhe) und NC (normally closed – geschlossen in Ruhe). Für eine Schaltuhr nutzt du meist COM und NO: Der Verbraucher ist aus, solange nicht geschaltet wird, und geht mit HIGH an.
4Schaltzeiten definieren
Jetzt kommt das Herz der Schaltuhr. Wir speichern die Schaltzeiten in einer kleinen Tabelle (einem Array von Strukturen). Jeder Eintrag hat eine Stunde, eine Minute und eine Aktion (an oder aus). So legst du beliebig viele Schaltpunkte fest – hier ein Beispiel für eine Aquarium-Beleuchtung, die zweimal am Tag läuft:
struct Schaltpunkt {
int stunde;
int minute;
bool einschalten; // true = an, false = aus
};
// Beliebig erweiterbar – hier vier Schaltpunkte:
Schaltpunkt plan[] = {
{ 8, 0, true }, // 08:00 Uhr AN
{ 10, 0, false }, // 10:00 Uhr AUS
{ 18, 30, true }, // 18:30 Uhr AN
{ 21, 30, false } // 21:30 Uhr AUS
};
const int ANZAHL = sizeof(plan) / sizeof(plan[0]);
💡 Warum eine Struktur-Tabelle? So trennst du die Konfiguration (wann soll was passieren) sauber von der Logik (wie wird geschaltet). Willst du eine Schaltzeit ändern, tippst du nur eine Zahl um – der eigentliche Programmablauf bleibt unberührt. Genau so bauen auch Profis wartbaren Code.
5Die komplette Schaltuhr
Jetzt verbinden wir Uhr, Schaltplan und Relais. Der Kniff: Wir prüfen einmal pro Minute, ob die aktuelle Uhrzeit einem Schaltpunkt entspricht – und schalten dann. Damit ein Schaltpunkt nicht 60-mal pro Minute auslöst, merken wir uns die zuletzt geprüfte Minute.
#include <Wire.h>
#include "RTClib.h"
RTC_DS3231 rtc;
const int RELAIS = 26;
struct Schaltpunkt { int stunde; int minute; bool einschalten; };
Schaltpunkt plan[] = {
{ 8, 0, true },
{ 10, 0, false },
{ 18, 30, true },
{ 21, 30, false }
};
const int ANZAHL = sizeof(plan) / sizeof(plan[0]);
int letzteMinute = -1;
bool relaisAn = false;
void schalte(bool an) {
digitalWrite(RELAIS, an ? HIGH : LOW);
relaisAn = an;
Serial.println(an ? "-> EINGESCHALTET" : "-> AUSGESCHALTET");
}
void setup() {
Serial.begin(115200);
pinMode(RELAIS, OUTPUT);
digitalWrite(RELAIS, LOW);
Wire.begin(21, 22);
rtc.begin();
}
void loop() {
DateTime t = rtc.now();
// nur einmal pro Minute pruefen
if (t.minute() != letzteMinute) {
letzteMinute = t.minute();
for (int i = 0; i < ANZAHL; i++) {
if (plan[i].stunde == t.hour() && plan[i].minute == t.minute()) {
schalte(plan[i].einschalten);
}
}
}
delay(1000);
}
Das war's im Kern: Der ESP32 fragt jede Sekunde die Zeit ab, prüft aber nur beim Minutenwechsel den Schaltplan. Trifft eine Uhrzeit auf einen Schaltpunkt, schaltet das Relais – zuverlässig und sekundengenau dank DS3231.
⚠️ Was passiert nach einem Stromausfall mitten in einer "An"-Phase? Der obige Code schaltet nur zu den Schaltpunkten. Fällt der Strom um 12:00 aus und kommt um 12:05 zurück, würde das Relais bis zum nächsten "An"-Punkt aus bleiben. Die Lösung zeigt der nächste Schritt: den Soll-Zustand aus dem Plan berechnen.
6Robust: den Soll-Zustand berechnen
Statt nur auf Schaltpunkte zu reagieren, ermitteln wir bei jedem Durchlauf, welchen Zustand das Relais gerade jetzt haben sollte – nämlich den des letzten vergangenen Schaltpunkts. So ist die Schaltuhr auch nach einem Stromausfall sofort wieder im richtigen Zustand.
// gibt zurueck, ob das Relais zur aktuellen Zeit AN sein soll
bool sollZustand(DateTime t) {
int jetzt = t.hour() * 60 + t.minute(); // Minuten seit Mitternacht
bool zustand = false;
int letzterPunkt = -1;
// suche den juengsten Schaltpunkt, der schon vorbei ist
for (int i = 0; i < ANZAHL; i++) {
int pkt = plan[i].stunde * 60 + plan[i].minute;
if (pkt <= jetzt && pkt > letzterPunkt) {
letzterPunkt = pkt;
zustand = plan[i].einschalten;
}
}
return zustand;
}
void loop() {
DateTime t = rtc.now();
bool soll = sollZustand(t);
if (soll != relaisAn) { // nur schalten, wenn noetig
schalte(soll);
}
delay(1000);
}
ℹ️ Warum das die bessere Lösung ist: Diese Variante ist zustandsbasiert statt ereignisbasiert. Sie fragt nicht "ist gerade ein Schaltpunkt?", sondern "welchen Zustand verlangt der Plan jetzt?". Dadurch korrigiert sie sich nach jedem Neustart und Stromausfall automatisch selbst – ein Prinzip, das dir in vielen Steuerungsprojekten begegnet.
7Optional: Status auf dem OLED anzeigen
Ein kleines OLED-Display macht aus der Schaltuhr ein fertiges Gerät: Es zeigt die aktuelle Uhrzeit und ob der Ausgang gerade an ist. Installiere die Bibliotheken "Adafruit SSD1306" und "Adafruit GFX", dann ergänzt du:
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
void oledSetup() {
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
}
void oledUpdate(DateTime t, bool an) {
oled.clearDisplay();
oled.setTextSize(2);
oled.setCursor(0, 0);
char buf[9];
sprintf(buf, "%02d:%02d:%02d", t.hour(), t.minute(), t.second());
oled.println(buf);
oled.setTextSize(2);
oled.setCursor(0, 34);
oled.println(an ? "AUSGANG: AN" : "AUSGANG: AUS");
oled.display();
}
Rufst du oledSetup() im setup() und oledUpdate(t, relaisAn) im loop() auf, hast du eine gut ablesbare Anzeige. Wie du das SSD1306 im Detail bedienst – Text, Grafik, Animationen – zeigt der Artikel OLED Display SSD1306 mit Arduino.
🔌 Ideen zum Weiterbauen
- WLAN-Steuerung: Schaltzeiten per Browser ändern – siehe ESP32 Webserver einrichten
-
Wochentage berücksichtigen: unterschiedliche Pläne für Werktag und Wochenende (
t.dayOfTheWeek()) - Zufalls-Anwesenheit: Licht zu leicht variierenden Zeiten schalten, wenn du im Urlaub bist
- Smart-Home-Anbindung per MQTT an Home Assistant – siehe MQTT mit ESP32 & Home Assistant
Perfekt für: Aquarium- & Terrarium-Beleuchtung, Pflanzenlampen, Anwesenheitssimulation
Troubleshooting
⚠️ DS3231 nicht gefunden: Lass einen I2C-Scanner laufen – die DS3231 meldet sich unter 0x68, das OLED unter 0x3C. Erscheint nichts, sind SDA/SCL vertauscht oder VCC fehlt.
⚠️ Relais schaltet nicht / nur schwach: Fast immer die Stromversorgung. Das KY-019 an 5V (VIN) betreiben, nicht an 3V3. Speist du den ESP32 nur über einen schwachen USB-Port, kann die Spannung einbrechen – ein stärkeres Netzteil hilft.
⚠️ Schaltuhr schaltet zur falschen Zeit: Meist ist die Uhr auf eine falsche Zeitzone oder Sommerzeit gestellt. Setze sie mit dem Sketch aus Schritt 2 neu und beachte, dass die DS3231 keine automatische Sommer-/Winterzeit kennt – die musst du selbst umstellen.
⚠️ Nach Neustart bleibt der Ausgang aus: Nutzt du noch die ereignisbasierte Variante aus Schritt 5? Wechsle auf die zustandsbasierte Lösung aus Schritt 6 – sie stellt den korrekten Zustand nach jedem Start selbst wieder her.
🚀 Alles für deine Zeitschaltuhr
Diese Teile brauchst du für das Projekt:
- ESP32 Entwicklungsboard – der Rechner mit WLAN-Reserve
- DS3231 Echtzeituhr-Modul – präzise Zeit, auch ohne Strom
- KY-019 1-Kanal-Relais – schaltet den Verbraucher
- 0,96″ OLED-Display – für die Status-Anzeige
- Breadboard 400 Kontakte – zum Aufbau
Fazit
Eine selbst gebaute Zeitschaltuhr ist eines der nützlichsten Projekte überhaupt – und ein hervorragendes Stück, um wichtige Konzepte zu lernen. Du hast die präzise DS3231-Echtzeituhr angebunden, Schaltzeiten sauber in einer Struktur-Tabelle organisiert, ein Relais angesteuert und mit der zustandsbasierten Logik dafür gesorgt, dass die Uhr auch nach einem Stromausfall sofort wieder richtig schaltet. Das optionale OLED macht aus dem Breadboard-Aufbau ein fertiges Gerät.
Die wichtigsten Erkenntnisse: Die DS3231 hält die Zeit autark – deshalb ist deine Schaltuhr unabhängig vom Stromnetz. Zustandsbasiert zu denken ("was soll jetzt gelten?") ist robuster als ereignisbasiert ("ist jetzt ein Schaltpunkt?"). Und beim Schalten echter Netzspannung hört der Spaß auf: Zum Lernen bleibst du bei ungefährlicher Kleinspannung. Von hier aus ist der Weg zur WLAN-gesteuerten, wochentagsabhängigen Smart-Home-Schaltuhr nur noch ein paar Zeilen Code entfernt. Viel Spaß beim Bauen!