Skip to Content

LED-Ring-Uhr mit WS2812

LED-Ring-Uhr mit WS2812

In diesem Tutorial lernst du, wie du eine LED-Ring-Uhr mit dem WS2812 LED-Streifen und einem ESP32 baust. Wir fügen der Uhr eine automatische Dimmung hinzu und nutzen einen Internet-Zeitdienst, damit sie immer genau geht. Außerdem wird die Uhr per Bewegungssensor (PIR) aktiviert, um unnötigen Energieverbrauch zu vermeiden.

Der kurze Clip unten zeigt die Uhr in Aktion. Du siehst die Stundenmarkierungen (weiße Punkte) und die laufenden Sekunden (bewegender weißer Punkt).

LED Ring Clock in action
LED-Ring-Uhr in Aktion

Die aktuelle Stunde wird durch den orangefarbenen Punkt markiert und die Minuten durch die gelben Punkte. Auf dem Bild oben zeigt die Uhr also 11:36 an.

Durch die Synchronisierung der Uhr über WLAN mit einem Internet-Zeitdienst stellen wir sicher, dass unsere Uhr immer die richtige Zeit anzeigt – unabhängig von Sommerzeitumstellung, Stromausfällen oder einer ungenauen internen Uhr.

Los geht’s mit den benötigten Bauteilen.

Benötigte Bauteile

Hier sind die benötigten Teile für das Projekt. Anstelle des unten aufgeführten ESP32-C3 Mini Development Board habe ich ein sehr ähnliches Board verwendet, das ESP32-C3 SuperMini von AliExpress heißt. Meins hatte nur eine einfarbige eingebaute LED, während das unten gezeigte Board eine RGB-LED besitzt. Abgesehen davon sollten beide nahezu identisch sein und funktionieren. Jeder andere ESP32 oder ESP8266 ist ebenfalls geeignet. Wenn du jedoch ein Arduino verwenden möchtest, benötigst du ein Wi-Fi-Shield.

ESP32-C3 Mini

USB-C-Kabel

RGB-LED-Ring

Dupont wire set

Dupont-Kabelset

Half_breadboard56a

Breadboard

Widerstands- & LED-Kit

Set mit Potentiometern

Bewegungssensor

Set mit Fotowiderständen

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Grundlagen des WS2812 RGB LED-Streifens

Wir verwenden für unsere Uhr einen WS2812 RGB-LED-Ring. Der WS2812 ist ein spezieller Typ von RGB-LED, der auf dem 5050 LED-Gehäuse basiert. Die 5050 RGB-LED bezieht sich auf die Abmessungen des Gehäuses, nämlich 5,0mm x 5,0mm. Das Bild unten zeigt eine 5050 LED mit ihrem Steuer-IC und den drei LEDs (grün, rot, blau).

5050 RBG LED
5050 RGB LED ( source )

Beachte, dass es verschiedene ähnliche RGB-LED-Streifen-Treiber gibt, wie den WS2811, SK6812 und WS2815. Dieser quick guide enthält einen guten Vergleich der verschiedenen Typen. Wir verwenden hier den WS2812.

Der WS2812 arbeitet mit 5V, hat einen integrierten PWM-Controller für Farbmischung und kann in Reihe geschaltet werden, sodass man mit nur einer Datenleitung längere LED-Streifen bauen kann. Am häufigsten findet man den WS2812 in flexiblen RGB-LED-Streifen wie dem unten gezeigten.

WS2812 RGB LED Strip
WS2812 RGB LED-Streifen ( source )

Diese LED-Streifen gibt es mit unterschiedlichen LED-Anzahlen und Dichten und sie können auf die gewünschte Länge zugeschnitten werden. Neben flexiblen Streifen gibt es sie auch als starre Ringe:

WS2812 RGB LED Rings
WS2812 RGB LED-Ringe ( source )

Die kleineren Ringe sind einteilig, aber der große Ring mit 60 LEDs, den wir für unsere Uhr verwenden, besteht aus vier Teilen, die du zusammenlöten musst. Das Bild unten zeigt die Rückseite der vier Segmente:

WS2812 RGB LED Ring in four parts
WS2812 RGB LED-Ring in vier Teilen

Wie man die vier Segmente zu einem großen Ring zusammenbaut, ist Thema des nächsten Abschnitts.

Bau der LED-Ring-Uhr

Da eine Stunde 60 Minuten und eine Minute 60 Sekunden hat, wollen wir einen Ring mit 60 LEDs verwenden. Wie oben erwähnt, kommt dieser Ring wegen seiner Größe in 4 Segmenten (je 15 LEDs), die wir zusammenlöten müssen.

Beachte, dass WS2812 RGB-LED-Streifen und -Ringe eine Eingangsseite (DIN) und eine Ausgangsseite (DO) haben, wie im Bild unten gezeigt:

Input and Output of an WS2812 LED Strip
Eingang und Ausgang eines WS2812 LED-Streifens

Beginne damit, das Signalkabel (gelb) an das DIN-Pad eines der LED-Ring-Segmente zu löten. Als nächstes lötst du ein Massekabel (schwarz) an das GND-Pad und ein Versorgungskabel (rot) an das 5V-Pad. Das sollte dann so aussehen:

Verkabelung des WS2812 LED-Rings

Zum Schluss müssen wir die vier Segmente verbinden. Verbinde GND mit GND, 5V mit 5V und DOUT mit DIN für jedes Segment. Eine Verbindung zwischen Segmenten sollte so aussehen:

Wiring of LED Ring Segments
Verkabelung der LED-Ring-Segmente

Im Grunde kannst du die Pads nicht falsch verbinden, sonst bekommst du keinen schönen Ring:

Wiring of complete LED Ring
Verkabelung des kompletten LED-Rings

Beachte, dass bei langen LED-Streifen ein Spannungsabfall auftreten kann, wodurch die LEDs am Ende des Streifens weniger hell leuchten als die am Anfang. Manchmal findest du daher LED-Streifen, die an beiden Enden mit Strom versorgt werden.

Beim 60-LED-Ring habe ich keinen Unterschied in der Helligkeit zwischen der ersten und der letzten LED bemerkt. Falls doch, kannst du die GND- und 5V-Pads zwischen dem letzten und dem ersten LED-Segment verbinden. In meinem Fall war das aber nicht nötig.

Das Löten kann etwas knifflig sein, da die Pads sehr klein sind. Es hilft, die LED-Ring-Segmente in einer Halterung zu fixieren. Ich habe einen 3D-Drucker verwendet, um die Halterung bzw. das Uhrengehäuse zu erstellen, das im nächsten Abschnitt gezeigt wird.

LED-Uhrengehäuse

Das LED-Uhrengehäuse besteht aus drei Teilen: dem Rücken, auf dem der LED-Ring sitzt, einer transparenten Front und einem Fuß, der es ermöglicht, den LED-Ring aufzustellen.

Parts of the LED Clock Frame
Teile des LED-Uhrengehäuses

Das komplette LED-Uhrengehäuse sieht so aus und du findest die STL files here :

LED Clock Frame
LED-Uhrengehäuse

Als nächstes verbinden wir den LED-Ring mit dem ESP32.

Verkabelung der LED-Ring-Uhr

Das Anschließen des WS2812 LED-Rings an den ESP32 ist einfach. Verbinde zuerst GND des ESP32 mit GND des LED-Rings (blaues Kabel). Dann verbinde 5V vom ESP32 mit dem 5V-Eingang des WS2812 LED-Rings. Schließlich verbindest du GPIO3 des ESP32 über einen 220Ω Widerstand mit DIN des WS2812.

Connecting ESP32 to WS2812 LED Ring
Anschluss ESP32 an WS2812 LED-Ring

Du kannst den 220Ω Widerstand zum Ausprobieren weglassen, aber mit ist es sicherer. Er schützt den GPIO des ESP32 vor Überströmen. Anstelle von GPIO3 kannst du auch jeden anderen GPIO-Pin verwenden, solange er PWM unterstützt.

Beachte, dass der LED-Ring viel Strom ziehen kann! Eine einzelne RGB-LED besteht aus drei LEDs (rot, grün, blau) und jede davon verbraucht bis zu 20mA, wenn sie voll leuchtet. Das heißt, wenn eine RGB-LED komplett eingeschaltet ist (weißes Licht), zieht sie bis zu 60mA (ich habe 45mA gemessen). Multipliziere 60mA mit 60 LEDs und wir kommen auf einen Strom von 3,6A , wenn alle LEDs des Rings voll eingeschaltet sind!

Das ist in der Regel zu viel Strom, um ihn direkt vom ESP32 oder dem USB-Port zu beziehen. Der Testcode im nächsten Abschnitt stellt daher die Helligkeit des LED-Rings auf einen niedrigen Wert von 10 ein, was zum Testen völlig ausreicht.

Übrigens, falls du mehr Infos zum SuperMini-Board haben möchtest, das ich hier verwende, schau dir das ESP32-C3 SuperMini Board Tutorial an.

Testcode für den LED-Ring

Bevor wir etwas Komplexeres umsetzen, testen wir die Funktion des LED-Rings erst einmal mit einfachem Code. Der Code unten schaltet nacheinander alle 60 LEDs ein und wenn alle an sind, wieder aus. So können wir prüfen, ob alle LEDs funktionieren und die Ringsegmente richtig verdrahtet sind.

#include "Adafruit_NeoPixel.h"

#define DINPIN 3
#define NUMPIXELS 60

Adafruit_NeoPixel pixels(NUMPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  for (int i = 0; i < NUMPIXELS; i++) {  // Switch all LEDs on
    pixels.setPixelColor(i, pixels.Color(255, 255, 255)); // White
    pixels.show();
    delay(200);
  }
  for (int i = 0; i < NUMPIXELS; i++) {  // Switch all LEDs off
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    pixels.show();
    delay(200);
  }
}

Im obigen Code-Snippet verwenden wir die Adafruit NeoPixel library . Falls du sie noch nicht installiert hast, musst du sie install the library zuerst installieren, bevor du diesen Code verwenden kannst.

Schauen wir uns den Testcode genauer an, um seine Funktionsweise besser zu verstehen.

Konstanten und Variablen

Zuerst definieren wir die Konstante DINPIN , die den Daten-Eingangspin angibt, an dem der LED-Ring angeschlossen ist, und NUMPIXELS , die die Gesamtanzahl der Pixel/LEDs im Streifen festlegt. In unserem Fall haben wir 60 LEDs und verwenden GPIO 3.

#define DINPIN 3
#define NUMPIXELS 60

Setup-Funktion

In der setup() -Funktion initialisieren wir den LED-Ring, indem wir pixels.begin() aufrufen, löschen alle bestehenden Pixel-Farben mit pixels.clear() , setzen die Helligkeit auf 10 mit pixels.setBrightness(10) und zeigen schließlich den Zustand der Pixel mit pixels.show() an.

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

Wie schon erwähnt: Sei vorsichtig beim Ändern der Helligkeit! Bei voller Helligkeit könnte der LED-Ring zu viel Strom für deinen ESP32, USB-Port oder das Netzteil ziehen.

Loop-Funktion

Die loop() -Funktion enthält eine Schleife, die zuerst alle LEDs weiß schaltet, indem sie jedes Pixel mit einer for-Schleife durchläuft. Die pixels.setPixelColor() -Funktion setzt die Farbe jedes Pixels auf weiß (255, 255, 255) und dann wird pixels.show() aufgerufen, um die LED-Zustände anzuzeigen. Zwischen jedem Pixel-Update gibt es eine Verzögerung von 200ms.

Nachdem alle Pixel auf weiß gesetzt wurden, wird eine weitere Schleife verwendet, um alle LEDs auszuschalten, indem ihre Farbe auf schwarz (0, 0, 0) gesetzt und der Streifen mit pixels.show() erneut mit einer Verzögerung von 200ms aktualisiert wird.

void loop() {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(255, 255, 255)); // White
    pixels.show();
    delay(200);
  }

  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 0, 0)); // Black
    pixels.show();
    delay(200);
  }
}

Wenn das funktioniert, Glückwunsch! Im nächsten Abschnitt probieren wir etwas Komplexeres, indem wir die Zeit für die Anzeige auf dem LED-Ring simulieren und unsere Schaltung etwas verbessern.

Verbesserte Verkabelung der LED-Ring-Uhr

Wie oben erwähnt, kann der LED-Ring bis zu 3,6A ziehen, wenn alle LEDs voll leuchten. Das wollen wir nicht direkt vom ESP32 beziehen. Stattdessen schließen wir den LED-Ring an eine externe Stromversorgung an. Die Schaltung unten zeigt, wie das gemacht wird:

Connecting WS2812 LED Ring to external power supply
Anschluss des WS2812 LED-Rings an eine externe Stromversorgung

Der WS2812 läuft mit 5V, also brauchst du ein 5V-Netzteil mit ausreichend Strom für den LED-Ring bei deiner maximalen Helligkeit. Bei einer Helligkeit von 5 habe ich 80mA gemessen, bei einer Helligkeit von 50 waren es 400mA.

Die niedrige Helligkeit von 5 ist gut in dunklen Räumen und 50 reicht in einem hellen Raum völlig aus. Bei höheren Werten kann die Uhr einen Raum beleuchten, was aber nicht Sinn der Sache ist. In meinem Fall reicht also ein 500mA-Netzteil.

In der Schaltung oben habe ich außerdem einen empfohlenen Kondensator von 100μ bis 1000μ in die Stromleitung eingebaut. Das stabilisiert die Stromversorgung bei Stromschwankungen, die durch das gleichzeitige Schalten vieler LEDs entstehen können.

Im nächsten Abschnitt gehen wir vom Testcode zu einer simulierten Uhr über, mit der wir verschiedene Möglichkeiten und Farben ausprobieren können, um die Zeit auf einem LED-Ring darzustellen.

Code für eine simulierte LED-Ring-Uhr

Es gibt viele verschiedene Möglichkeiten, die Zeit auf einem LED-Ring darzustellen. Das Bild unten zeigt die Version, für die ich mich entschieden habe:

Showing time on an LED Ring
Zeitdarstellung auf einem LED-Ring

Die aktuelle Stunde (oranger Punkt) wird durch eine einzelne LED an der entsprechenden Position markiert. Minuten (gelbe Punkte) werden angezeigt, indem alle LEDs bis zur aktuellen Minute leuchten. Im Bild oben ist es 11:32, daher leuchtet die orange LED an der 11-Uhr-Position und 32 gelbe LEDs zeigen die Minuten an.

Die Ticks des Zifferblatts werden durch schwach weiße LEDs angezeigt und die aktuelle Sekunde wird durch das Aufhellen der LED an der aktuellen Sekundenposition dargestellt. Das ist auf dem Bild oben etwas schwer zu erkennen, aber die LED an der 3-Sekunden-Position ist etwas heller. Wie das gemacht wird, zeigt der folgende Code.

Beachte, dass dieser Code nicht die echte Zeit anzeigt, sondern nur eine beschleunigte Zeit simuliert, um die Anzeige auf dem LED-Ring zu testen. Schau dir den kompletten Code kurz an, bevor wir die Details besprechen.

#include "Adafruit_NeoPixel.h"

#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void simulateClock() {
  for (int h = 0; h < 12; h++) {
    for (int m = 0; m < 60; m++) {
      for (int s = 0; s < 60; s++) {
        showTime(h, m, s);
        delay(100);
      }
    }
  }
}

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  simulateClock();
}

Im obigen Code verwenden wir die Adafruit NeoPixel Bibliothek, um eine Uhr mit einem LED-Streifen mit 60 Pixeln zu simulieren. Die Uhr zeigt Stunden, Minuten und Sekunden an, indem sie die Farben bestimmter Pixel auf dem LED-Streifen ändert.

Konstanten und Variablen

Wie zuvor definieren wir zuerst die Konstanten und Variablen, die für die Uhrsimulation benötigt werden. Wir geben den Datenpin für den LED-Streifen, die Gesamtzahl der Pixel und einen Offset-Wert für die Indizierung an.

#include "Adafruit_NeoPixel.h"

#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);

Sprechen wir kurz über die OFFSET Konstante und warum sie benötigt wird. Wenn du die LED-Ring-Uhr in ihr Gehäuse einbaust, befindet sich die erste LED des Rings (wo die Kabel angeschlossen sind) einen Pixel links von der 6-Uhr-Position (Index 0).

Offset for pixel index
Offset für Pixelindex

Wenn wir also eine Zeit von 12 Uhr (= 0 Uhr) haben, können wir nicht die LED/Pixel an Indexposition 0 einschalten, da diese unten ist. Stattdessen müssen wir die LED an Index 29 einschalten, da das die 12-Uhr-Position ist. Das heißt, wir müssen immer einen Offset von 29 zu jedem Pixelindex addieren, wenn wir die LED an der entsprechenden Uhrenposition einschalten wollen.

Dafür ist die OFFSET Konstante da. Je nach deinem Uhrengehäuse und der mechanischen Position der ersten LED des Rings musst du eventuell einen anderen OFFSET verwenden.

Index-Funktion

Die OFFSET Konstante wird in der index() Funktion verwendet, um einen Zeitindex i auf eine LED-Position am LED-Ring abzubilden. Zusätzlich zum Offset müssen wir auch das Modulo (%) berechnen, damit der LED-Index im Bereich 0..59 bleibt, da wir nur 60 LEDs haben.

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

Angenommen, wir haben eine Zeit von 36 Minuten. Die LED am Ring, die wir zur Anzeige der Minute einschalten müssen, wäre bei Index: 36 + 29 % 60 = 5.

setPixel- und getPixel-Funktionen

Die setPixel() und getPixel() Funktionen verwenden die index() Funktion, um die Farbe für einen gegebenen Index i am LED-Ring zu setzen oder auszulesen:

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

setTick-Funktion

Die setTick()-Funktion markiert die Stundenticks auf dem LED-Ring. Da ein Tag auf einer Uhr 12 Stunden hat, wir aber 60 LEDs, müssen wir jede 5. LED am Ring zur Stundenmarkierung einschalten.

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

Wenn du dir die Funktion ansiehst: Sie läuft über die 12 Stunden, multipliziert eine Stunde h mit 5 und verwendet dann setPixel() und damit index() , um die Stunde in einen LED-Index umzuwandeln, an dem die Farbe gesetzt wird.

Ich verwende ein schwaches Weiß (50, 50, 50), aber du kannst jede beliebige Farbe wählen. Achte nur darauf, dass der Farbwert nicht größer als 200 ist, wegen der Art, wie die Sekunden angezeigt werden. Mehr dazu später.

setHours-Funktion

Die setHours() Funktion funktioniert wie die setTicks() Funktion, zeigt aber eine bestimmte Stunde anstatt aller Stunden und verwendet eine andere Farbe. Ich habe für die Stundenmarkierung ein warmes Orange-Rot gewählt.

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

setMinutes-Funktion

Während die setHours() Funktion nur eine einzelne LED für die aktuelle Stunde leuchten lässt, schaltet die setMinutes() Funktion alle LEDs bis zur aktuellen Minute ein. Deshalb haben wir die for Schleife darin.

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

setSeconds-Funktion

Zum Schluss wollen wir die aktuelle Sekunde anzeigen. Ich wollte die Sekunde nicht über Stunden und Minuten legen, sondern habe mich dafür entschieden, die LED für die aktuelle Sekunde einfach etwas heller zu machen – egal, welche Farbe sie gerade hat.

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

Die Funktion holt sich zuerst die LED-Farbe an der aktuellen Sekunde s durch Aufruf von getPixel() . Dann wird die Farbe heller gemacht, indem 55 zu jedem Farbwert (rot, grün, blau) addiert wird. 55 im Hexadezimal ist 0x37 . Daher kommt dieser Wert von 0x373737 . Und das ist auch der Grund, warum der Basis-Farbwert nicht größer als 200 sein darf, da 200 + 55 = 255 das Maximum für den Farbwert ist.

showTime-Funktion

Um eine Zeit ( h, m, s ) auf der Uhr anzuzeigen, rufen wir einfach die obigen Funktionen in folgender Reihenfolge auf und aktualisieren am Ende die LED-Zustände mit pixels.show() .

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);  
  setHours(h);
  setSeconds(s);
  pixels.show();
}

simulateClock-Funktion

Um die Anzeige der Zeit zu testen, verwende ich eine einfache simulierte Uhr, die die Stunden, Minuten und Sekunden 10x schneller durchläuft ( delay(100) ).

void simulateClock() {
  for (int h = 0; h < 12; h++) {
    for (int m = 0; m < 60; m++) {
      for (int s = 0; s < 60; s++) {
        showTime(h, m, s);
        delay(100);
      }
    }
  }
}

setup- und loop-Funktion

Die setup- und loop-Funktion sind jetzt sehr einfach. In der setup-Funktion initialisieren wir den LED-Ring. In der loop-Funktion simulieren wir das Laufen der Uhr.

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  simulateClock();
}

Abgesehen von der echten Zeitanzeige haben wir jetzt alles, um die Zeit auf unserem LED-Ring darzustellen. Du kannst diesen Simulator-Code nutzen, um Farben und Visualisierungen auszuprobieren, ohne dich um die tatsächliche Zeit kümmern zu müssen.

Für mehr Informationen zu den WS2812-LEDs und den Effekten, die du damit erzielen kannst, schau dir unser Tutorial How To Control WS2812B Individually Addressable LEDs using Arduino an. Beachte aber, dass ich im Mai 2024 die FastLED library , die in diesem Tutorial verwendet wird, nicht mit einem ESP32 zum Laufen gebracht habe.

Im nächsten Abschnitt holen wir die echte Zeit von einem Internet-Zeitdienst und nutzen den obigen Code, um sie auf unserer Uhr anzuzeigen.

Code für eine Web-Zeit-LED-Ring-Uhr

Wir könnten die Echtzeituhr des ESP32 nutzen, um ein Zeitsignal zu bekommen. Das würde aber bedeuten, dass wir nach jedem Stromausfall die aktuelle Zeit neu einstellen müssten. Außerdem müssten wir die Zeit zweimal im Jahr bei der Umstellung auf Sommer- bzw. Winterzeit anpassen.

Das heißt, wir bräuchten Tasten oder zusätzliche Software (Web-Interface, Handy-App), um die Zeit einstellen zu können. Das ist alles ziemlich umständlich. Ich bevorzuge stattdessen eine vollautomatische Uhr, die sich immer die genaue Zeit von einem Internet-Zeitdienst holt.

Wenn du im Detail wissen willst, wie das funktioniert, schau dir unser Tutorial Automatic Daylight Savings Time Clock an. Im Wesentlichen habe ich den Code von dort kopiert und unten eingefügt.

#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "Adafruit_NeoPixel.h"

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"

#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
StaticJsonDocument<2048> doc;

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateDisplay();
  delay(100);
}

Bevor du diesen Code verwenden kannst, musst du zwei zusätzliche Bibliotheken installieren, nämlich „ ArduinoJson.h “ und „ TimeLib.h “. „ WiFi.h “ und „ HTTPClient.h “ sind Teil der ESP32/Arduino-Standardbibliothek und müssen nicht extra installiert werden.

Du musst außerdem die SSID und das Passwort deines WLANs eintragen. Damit kann sich der ESP32 mit dem Internet-Zeitdienst unter der angegebenen URL verbinden:

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"

Damit das erledigt ist, schauen wir uns die neuen Funktionen im Code genauer an.

syncTime-Funktion

Die syncTime() -Funktion verbindet sich mit dem Internet-Zeitdienst, liest die Daten im JSON-Format, extrahiert die relevanten Zeitinformationen (h, m, s, D, M, Y) und stellt die interne Uhr des ESP32 entsprechend ein.

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

shouldSyncTime-Funktion

Wir wollen den Internet-Zeitdienst nicht zu oft abfragen und eventuell blockiert werden. Deshalb beschränke ich die Synchronisierung der internen Uhr des ESP32 mit dem Internet-Zeitdienst auf einmal pro Stunde. Genauer gesagt synchronisieren wir in der 3. Sekunde nach jeder vollen Stunde. Die shouldSyncTime() -Funktion sagt uns, wann es soweit ist.

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

updateDisplay-Funktion

Die updateDisplay() Funktion ersetzt die simulateClock() Funktion, die wir vorher verwendet haben. Sie liest die interne Uhr des ESP32, die wir mit der Internetzeit synchronisiert haben, und ruft showTime() auf, um sie auf dem LED-Ring anzuzeigen.

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

setup-Funktion

In der setup() Funktion fügen wir die Funktionalität hinzu, sich mit dem WLAN zu verbinden, initialisieren den LED-Ring aber ansonsten wie zuvor.

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

loop-Funktion

In der loop() Funktion prüfen wir zuerst, ob wir die Zeit synchronisieren sollten. Falls ja, rufen wir syncTime() auf, um das zu tun. In jedem Fall rufen wir updateDisplay() auf, um die aktuelle Zeit auf dem LED-Ring anzuzeigen.

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateDisplay();
  delay(100);
}

Da wir ein delay(100) im Loop haben, passiert dieses Update alle 100ms – also 10 Mal pro Sekunde.

Und das war’s! Damit hast du eine vollautomatische, immer genaue Uhr, die die Zeit auf einem LED-Ring anzeigt. In den nächsten Abschnitten fügen wir automatische Dimmung und Bewegungsaktivierung hinzu, um die Uhr noch besser zu machen.

Automatische Dimmung der LED-Ring-Uhr

Die WS2812 LEDs sind sehr hell. Nachts möchte man sie meist nicht so hell haben. Andererseits ist die Uhr bei einer für dunkle Räume passenden Helligkeit tagsüber schwer abzulesen. Es wäre praktisch, die Helligkeit der LEDs automatisch an die aktuellen Lichtverhältnisse anzupassen.

Dafür fügen wir unserer Schaltung einen lichtempfindlichen Widerstand (LDR) hinzu, lesen dessen Wert über einen analogen Eingang am ESP32 aus und rufen setBrightness() auf, um die Gesamt-Helligkeit des LED-Rings anzupassen.

Schaltplan der LED-Ring-Uhr mit LDR

Das Bild unten zeigt, wie du den Lichtsensor (LDR) an die bestehende Schaltung anschließt. Ein Pin des LDR wird mit 5V verbunden, der andere mit einem 10KΩ-Potentiometer, das wiederum mit Masse verbunden ist.

Wiring of LDR with LED Ring Clock
Verkabelung des LDR mit der LED-Ring-Uhr

Der Ausgang des LDR (grünes Kabel) wird mit GPIO0 des ESP32 verbunden. Jeder andere GPIO-Pin funktioniert auch, solange er ein analoges Signal lesen kann.

Der LDR und das 10KΩ-Potentiometer bilden einen Spannungsteiler. Für mehr Details, wie das funktioniert, schau dir unser Tutorial How to detect light using an Arduino an, aus dem diese Schaltung stammt.

Je nach Helligkeit des Umgebungslichts erzeugt die LDR-Schaltung eine proportionale Spannung im Bereich von 0V bis 5V. In der Praxis erreicht man nie den vollen Bereich, da es nie komplett dunkel oder maximal hell ist. Das 10KΩ-Potentiometer erlaubt es, einen Arbeitspunkt für die Ausgangsspannung einzustellen. Wir werden die Spannungsänderungen im folgenden Code auf einen passenden Bereich abbilden.

Testcode zur Anpassung des Helligkeitsbereichs

Bevor wir die automatische Dimmfunktion in unsere LED-Ring-Uhr einbauen, müssen wir erst die Parameter anpassen. Je nach Umgebungslicht wollen wir einen Helligkeitswert zwischen 5 (sehr dunkel) und vielleicht 50 (sehr hell).

Was wir vom Lichtsensor am Analogeingang lesen, sind jedoch Werte im Bereich von 0 bis 4095. Das hängt vom Grundwiderstand des LDR, der Einstellung des Potentiometers und den Lichtverhältnissen ab.

Der folgende Testcode hilft dir, die richtige Zuordnung zwischen den LDR-Sensorwerten und den gewünschten Helligkeitswerten zu finden.

#define LDRPIN 0

void setup() {
  Serial.begin(112500);
  pinMode(LDRPIN, INPUT);
}

void loop() {
  int ldrValue = analogRead(LDRPIN);
  Serial.print(ldrValue);

  int brightness = map(ldrValue, 1400, 3000, 5, 50);
  Serial.print(" -> ");
  Serial.println(brightness);

  delay(1000);
}

Er liest den LDR-Wert ( ldrValue ), gibt ihn aus, ordnet ihn einem Helligkeitswert zu ( brightness ) und gibt auch diesen aus. Wenn du den Code hochlädst und ausführst, solltest du im Serial Monitor etwa Folgendes sehen:

Serial output of test code to adjust brightness
Serielle Ausgabe des Testcodes zur Helligkeitsanpassung

Wenn du den Lichtsensor abdeckst (Dunkelheit), solltest du einen Helligkeitswert nahe 5 sehen, und wenn du ihn sehr hellem Licht (z.B. Sonnenlicht) aussetzt, einen Wert nahe 50. Um das zu erreichen, musst du die fromLow – und fromHigh -Parameter der map-Funktion anpassen:

map(value, fromLow, fromHigh, toLow, toHigh)

Für meinen LDR-Sensor, die Potentiometereinstellung und die Lichtverhältnisse habe ich fromLow=1400 und fromHigh=3000 verwendet, wie du im Code oben siehst. Deine Werte werden anders sein, aber du kannst meine als Ausgangspunkt nehmen.

Wenn du einen anderen Helligkeitsbereich als 5..50 möchtest, kannst du andere Werte für toLow und toHigh wählen. Du könntest die Uhr nachts sogar ganz ausschalten, indem du toLow=0 wählst.

Automatische Dimmfunktion hinzufügen

Sobald du passende Parameter gefunden hast, kannst du die folgende automatische Dimmfunktion in den Uhren-Code einbauen.

void updateBrightness() {
  if (millis() % 1000 < 100) {
    int ldrValue = analogRead(LDRPIN);
    int brightness = map(ldrValue, 1400, 3000, 5, 50);
    pixels.setBrightness(constrain(brightness, 5, 50));
  }
}

Sie stellt die Helligkeit des gesamten LED-Rings basierend auf dem Wert ein, den wir am LDRPIN auslesen. Wir wollen die Helligkeit aber nicht bei jedem Update der Uhr (alle 100ms) anpassen, da das zu störendem Flackern führen könnte, wenn sich das Umgebungslicht leicht ändert.

Deshalb aktualisieren wir die Helligkeit nur einmal pro Sekunde, was durch millis() % 1000 < 100 erreicht wird. Wenn du seltener oder öfter aktualisieren möchtest, ändere die 1000 einfach auf deinen Wunschwert. Beachte, dass der Schwellwert < 100 mit der 100ms-Verzögerung in der Hauptschleife gekoppelt ist.

Beachte auch den zusätzlichen Aufruf von onstrain(brightness, 5, 50), , der sicherstellt, dass die Helligkeit im Bereich 5..50 liegt, was von der map() -Funktion nicht garantiert wird.

Um die Dimmung in den Uhren-Code zu integrieren, müssen wir im Wesentlichen nur einen Aufruf der updateBrightness()-Funktion in die Hauptschleife einfügen:

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateBrightness();  
  updateDisplay();
  delay(100);
}

Natürlich gibt es auch die Definition der LDRPIN-Konstanten und das Setzen von LDRPIN als Eingang. Der komplette Code für die LED-Ring-Uhr mit automatischer Dimmung ist unten zu sehen:

#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "Adafruit_NeoPixel.h"

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"


#define LDRPIN 0
#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
StaticJsonDocument<2048> doc;

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

void updateBrightness() {
  if (millis() % 1000 < 100) {
    int ldrValue = analogRead(LDRPIN);
    int brightness = map(ldrValue, 1400, 3000, 5, 50);
    pixels.setBrightness(constrain(brightness, 5, 50));
  }
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pinMode(LDRPIN, INPUT);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateBrightness();
  updateDisplay();
  delay(100);
}

Puh, fast geschafft. Als letzten Feinschliff schalten wir die Uhr automatisch ein, wenn eine Bewegung erkannt wird.

Automatische Aktivierung der LED-Ring-Uhr

Die LED-Ring-Uhr verbraucht ziemlich viel Strom und es besteht die Gefahr, dass die LEDs durchbrennen, wenn die Uhr ständig läuft. Es macht aber keinen Sinn, die Zeit anzuzeigen, wenn niemand da ist, um sie zu sehen. Deshalb fügen wir der Schaltung einen Bewegungssensor (PIR) hinzu. Er schaltet die Uhr für eine bestimmte Zeit (z.B. 10 Sekunden) ein, wenn Bewegung erkannt wird, und danach wieder aus.

Das folgende Bild zeigt, wie du den PIR-Bewegungssensor an die bestehende Schaltung anschließt. Verbinde einfach 5V und Masse mit den entsprechenden Pins des PIR-Sensors (rote und blaue Kabel). Der Ausgang des Sensors (lila Kabel) wird mit GPIO10 des ESP32 verbunden.

Wiring of PIR sensor with LED Ring Clock
Verkabelung des PIR-Sensors mit der LED-Ring-Uhr

Hier siehst du ein Bild der fertigen Schaltung auf dem Breadboard. Wie du siehst, konnte ich die LED-Ring-Uhr zum Testen sogar mit einer 9V-Batterie (und einem 5V-Spannungsregler) betreiben.

LED Ring Clock with PIR sensor and Battery
LED-Ring-Uhr mit PIR-Sensor und Batterie

Wenn du die Uhr wirklich mit Batteriebetrieb nutzen willst, empfehle ich dir stattdessen eine USB-Powerbank. Du kannst den ESP32 auch in den deep-sleep versetzen, solange die Uhr inaktiv ist. Mehr dazu findest du in unserem Tutorial zu How to Build a Motion Activated Night Light .

Bevor wir die automatische Aktivierung in den Uhren-Code integrieren, schreiben wir erst einen Testcode für den PIR-Sensor.

Testcode für den PIR-Sensor

Der folgende Code liest das Signal vom PIR-Sensor aus und gibt „motion detected“ aus, wenn eine Bewegung erkannt wurde, oder „nothing“ sonst.

#define PIRPIN 10

unsigned long lastMotion= 0;

bool motionDetected() {
  if (digitalRead(PIRPIN))
    lastMotion = millis();
  unsigned long diff = millis() - lastMotion;
  return diff < 10000 && diff >= 0;
}

void setup() {
  Serial.begin(115200);
  pinMode(PIRPIN, INPUT);
}

void loop() {
  if (motionDetected()) {
    Serial.println("motion detected");
  } else {
    Serial.println("nothing");
  }
  delay(100);
}

Der Bewegungssensor, den ich hier verwende, ist der AM312 , der nach der ersten Bewegung nur etwa 2 Sekunden lang ein High-Signal ausgibt. Mehr Details zum AM312 und wie man ihn verwendet, findest du in unserem Tutorial How to Build a Motion Activated Night Light .

Wenn wir das Signal des AM312 direkt im Code verwenden, würde unsere Uhr nur für 2 Sekunden aktiviert und dann wieder ausgeschaltet, wenn keine weitere Bewegung erkannt wird. Das führt zu häufigem Ein- und Ausschalten, was nicht schön aussieht. Der Code oben fügt daher eine Timer-Komponente hinzu, die motionDetected() mindestens 10000ms = 10 Sekunden lang true zurückgibt. Du kannst diesen Zeitraum nach Belieben verlängern oder verkürzen.

Wenn du die Schaltung aufbaust, den Code hochlädst und ausführst und alles wie gewünscht funktioniert, bist du bereit, die automatische Aktivierung in den Uhren-Code zu integrieren.

Automatische Aktivierungscode hinzufügen

Für die Integration fügen wir zwei Funktionen hinzu und ändern die Hauptschleife ein klein wenig.

motionDetected-Funktion

Die motionDetected() Funktion basiert auf dem obigen Testcode und gibt mindestens 10 Sekunden lang true zurück, nachdem die erste Bewegung erkannt wurde. Der 10-Sekunden-Timer wird jedes Mal zurückgesetzt, wenn während dieser Zeit neue Bewegung erkannt wird. So bleibt die Uhr dauerhaft an, solange sich jemand im Raum bewegt.

bool motionDetected() {
  if (digitalRead(PIRPIN))
    lastMotion = millis();
  unsigned long diff = millis() - lastMotion;
  return diff < 10000 && diff >= 0;
}

Beachte die diff >= 0 Bedingung im return-Statement. Sie ist nötig, da der von millis() zurückgegebene Timerwert wrap nach einigen Tagen (49 beim Arduino UNO) überläuft und die Zeitdifferenz diff negativ wäre.

clearDisplay-Funktion

Wenn keine Bewegung erkannt wurde, schalten wir die Uhr-LEDs aus, indem wir clearDisplay() aufrufen. Diese Funktion löscht einfach alle gesetzten Pixelwerte und aktualisiert dann die LED-Zustände mit show() .

void clearDisplay() {
  pixels.clear();
  pixels.show();
}

loop-Funktion

Zum Schluss müssen wir die loop-Funktion so anpassen, dass sie auf den Bewegungssensor reagiert. Wenn wir Bewegung über motionDetected() erkennen, führen wir das übliche Update der Uhr aus. Wenn nicht, schalten wir die Anzeige über clearDisplay() aus. Das passiert alles alle 1/10 Sekunde durch das delay(100) .

void loop() {
  if (motionDetected()) {
    if (shouldSyncTime())
      syncTime();
    updateBrightness();
    updateDisplay();
  } else {
    clearDisplay();
  }
  delay(100);
}

Und das war’s. Damit haben wir alle Bausteine zusammen. Unten findest du den kompletten Code für die bewegungsaktivierte Uhr.

Kompletter Code für die bewegungsaktivierte Uhr

#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "Adafruit_NeoPixel.h"

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"

#define PIRPIN 10
#define LDRPIN 0
#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
StaticJsonDocument<2048> doc;
unsigned long lastMotion = 0;

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

void updateBrightness() {
  if (millis() % 1000 < 100) {
    int ldrValue = analogRead(LDRPIN);
    int brightness = map(ldrValue, 1400, 3000, 5, 50);
    pixels.setBrightness(constrain(brightness, 5, 50));
  }
}

bool motionDetected() {
  if (digitalRead(PIRPIN))
    lastMotion = millis();
  unsigned long diff = millis() - lastMotion;
  return diff < 10000 && diff >= 0;
}

void clearDisplay() {
  pixels.clear();
  pixels.show();
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pinMode(PIRPIN, INPUT);
  pinMode(LDRPIN, INPUT);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  if (motionDetected()) {
    if (shouldSyncTime())
      syncTime();
    updateBrightness();
    updateDisplay();
  } else {
    clearDisplay();
  }
  delay(100);
}

Fazit

In diesem Tutorial hast du gelernt, wie du eine bewegungsaktivierte LED-Ring-Uhr mit automatischer Dimmung und Internet-Zeitsynchronisation baust. Sie hat den Vorteil, dass sie immer genau und vollautomatisch ist. Du brauchst keine Tasten oder Regler, um sie ein- oder auszuschalten, die Zeit einzustellen oder die Helligkeit zu regeln.

Abgesehen davon, dass du ein besseres Uhrengehäuse bauen und die LED-Farben nach deinem Geschmack anpassen kannst, gibt es noch ein paar weitere Verbesserungen, die du hinzufügen könntest. Zum Beispiel könntest du einen Temperatursensor einbauen, der die LED-Farbe je nach Umgebungstemperatur anpasst – z.B. bläulicher bei Kälte und rötlicher bei Wärme.

Da die Uhr bewegungsaktiviert ist, wäre es möglich, sie mit Batteriebetrieb zu betreiben, besonders wenn du den ESP32 in den deep-sleep versetzt, solange die Anzeige inaktiv ist. Sieh dir dazu unser Tutorial zu How to Build a Motion Activated Night Light als Beispiel an.

Schließlich könntest du auch alle paar Sekunden von der Zeitanzeige auf eine Art Datumsanzeige umschalten, da wir die Datumsinformation ohnehin schon vom Internet-Zeitdienst herunterladen. Anstelle von worldtimeapi.org könntest du auch einen SNTP-Server verwenden, der eine häufigere Synchronisierung erlaubt. Siehe unser Tutorial zu How to synchronize ESP32 clock with SNTP server .

Und falls dir die Ring-Uhr gar nicht gefällt, schau dir dieses Tutorial Digital Clock with CrowPanel 3.5″ ESP32 Display an, das erklärt, wie man Zeit und Datum auf einem schönen Display anzeigt – wie bei einer klassischen Digitaluhr.

Es gibt jede Menge zum Ausprobieren!

Wenn du Fragen hast, poste sie gerne. Ich helfe gerne weiter ; )