Dans ce tutoriel, vous allez apprendre à fabriquer une horloge à LED circulaire avec un ruban LED WS2812 et un ESP32. Nous ajouterons une fonction de variation automatique de la luminosité et utiliserons un fournisseur de temps via Internet pour garantir une précision constante. Enfin, l’horloge sera activée par détection de mouvement grâce à un capteur PIR, afin d’éviter toute consommation d’énergie inutile.
Le court extrait ci-dessous montre l’horloge en fonctionnement. Vous pouvez voir les repères des heures (points blancs) et les secondes qui défilent (point blanc en mouvement).

L’heure actuelle est indiquée par le point orange et les minutes par les points jaunes. Donc, sur la photo ci-dessus, l’horloge affiche 11:36.
En synchronisant l’horloge via Wi-Fi avec un fournisseur de temps en ligne, on s’assure qu’elle affiche toujours l’heure exacte, même en cas de changement d’heure d’été, de coupure de courant ou d’horloge interne imprécise.
Commençons par la liste des composants nécessaires.
Composants nécessaires
Voici les composants nécessaires pour ce projet. À la place de la carte de développement ESP32-C3 Mini listée ci-dessous, j’ai utilisé une carte très similaire appelée ESP32-C3 SuperMini d’AliExpress. La mienne avait seulement une LED intégrée monochrome, alors que la carte ci-dessous possède une LED RGB. Mais à part cela, elles sont quasiment identiques et devraient toutes deux fonctionner. Tout autre ESP32 ou ESP8266 conviendra également. Cependant, si vous souhaitez utiliser un Arduino, il vous faudra un shield Wi-Fi.

ESP32-C3 Mini

Câble USB C

Anneau LED RGB

Jeu de fils Dupont

Breadboard

Kit de résistances & LED

Jeu de potentiomètres

Capteur de mouvement

Jeu de photorésistances
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.
Principe du ruban LED RGB WS2812
Nous allons utiliser un anneau LED RGB WS2812 pour notre horloge. Le WS2812 est un type particulier de LED RGB basé sur le format 5050. La LED RGB 5050 fait référence à la taille du boîtier, soit 5,0 mm x 5,0 mm. L’image ci-dessous montre une LED 5050 avec son circuit de contrôle et les trois LED (vert, rouge, bleu).

Notez qu’il existe d’autres pilotes de rubans LED RGB similaires comme les WS2811, SK6812 et WS2815. Ce quick guide contient une bonne comparaison des différents types. Ici, nous allons utiliser le WS2812.
Le WS2812 fonctionne en 5V, possède un contrôleur PWM intégré pour le mélange des couleurs, et peut être chaîné en série, utilisant une seule ligne de communication pour créer des rubans LED plus longs. On trouve le plus souvent le WS2812 dans des rubans LED RGB flexibles comme celui illustré ci-dessous.

Ces rubans LED existent avec différents nombres de LED et densités, et peuvent être coupés à la longueur souhaitée. En plus des rubans flexibles, on les trouve aussi sous forme d’anneaux rigides :

Les petits anneaux sont d’un seul tenant, mais le grand anneau de 60 LED que nous allons utiliser pour notre horloge est composé de quatre parties qu’il faudra souder ensemble. La photo ci-dessous montre l’arrière des quatre segments :

Comment assembler les quatre segments pour former un grand anneau est le sujet de la prochaine section.
Construction de l’horloge à LED circulaire
Puisqu’une heure compte 60 minutes et une minute 60 secondes, nous voulons utiliser un anneau de 60 LED. Comme mentionné plus haut, à cause de sa taille, il est livré en 4 segments (chacun avec 15 LED) qu’il faut souder ensemble.
Notez que les rubans et anneaux LED WS2812 possèdent un côté entrée (DIN) et un côté sortie (DO), comme illustré ci-dessous :

Commencez par souder le fil de signal (jaune) sur le pad DIN d’un des segments de l’anneau LED. Ensuite, soudez un fil de masse (noir) sur le pad GND et un fil d’alimentation (rouge) sur le pad 5V. Cela devrait ressembler à ceci :

Enfin, il faut connecter les quatre segments entre eux. Reliez GND à GND, 5V à 5V et DOUT à DIN pour chaque segment. Une connexion entre segments doit ressembler à ceci.

En principe, il est impossible de se tromper dans le branchement des pads, sinon vous n’obtiendrez pas un bel anneau :

Notez que les longs rubans LED peuvent subir une chute de tension, ce qui rend les LED en bout de ruban moins lumineuses que celles du début. Il arrive donc que certains rubans soient alimentés aux deux extrémités.
Avec l’anneau de 60 LED, je n’ai pas constaté de différence de luminosité entre la première et la dernière LED. Cependant, si c’est le cas chez vous, vous pouvez relier les pads GND et 5V entre le dernier et le premier segment. Je n’en ai pas eu besoin.
La soudure peut aussi être un peu délicate car les pads sont très petits. Il est utile de placer les segments de l’anneau LED dans un support pour les maintenir en place. J’ai utilisé une imprimante 3D pour créer le support ou cadre de l’horloge présenté dans la section suivante.
Cadre de l’horloge à LED
Le cadre de l’horloge à LED se compose de trois parties : l’arrière, où se trouve l’anneau LED, une face avant transparente et un pied qui permet de faire tenir l’anneau LED debout.

Le cadre complet de l’horloge à LED ressemble à ceci et vous pouvez trouver le STL files here :

La prochaine étape consiste à connecter l’anneau LED à l’ESP32.
Câblage de l’horloge à LED circulaire
Connecter l’anneau LED WS2812 à l’ESP32 est simple. Commencez par relier le GND de l’ESP32 au GND de l’anneau LED (fil bleu). Ensuite, connectez le 5V de l’ESP32 à l’entrée 5V de l’anneau LED WS2812. Enfin, reliez le GPIO3 de l’ESP32 via une résistance de 220Ω au DIN du WS2812.

Vous pouvez essayer sans la résistance de 220Ω pour tester, mais il est plus sûr de la mettre. Elle protège le GPIO de l’ESP32 contre les surintensités. À la place du GPIO3, vous pouvez utiliser n’importe quel autre GPIO tant qu’il supporte le PWM.
Attention, l’anneau LED peut consommer beaucoup de courant ! Une seule LED RGB est composée de trois LED (rouge, verte, bleue) et chacune consomme jusqu’à 20mA lorsqu’elle est allumée à fond. Cela signifie que si une LED RGB est entièrement activée (lumière blanche), elle consomme jusqu’à 60mA (j’ai mesuré 45mA). Multipliez 60mA par 60 LED et on obtient un courant de 3,6A si toutes les LED de l’anneau sont allumées à fond !
C’est généralement trop pour être alimenté par l’ESP32 ou le port USB. Le code de test dans la section suivante règle donc la luminosité de l’anneau LED à une valeur basse de 10, ce qui est largement suffisant pour les essais.
Au fait, si vous voulez plus d’informations sur la carte SuperMini que j’utilise ici, jetez un œil au ESP32-C3 SuperMini Board tutoriel.
Code de test pour l’anneau LED
Avant de se lancer dans quelque chose de complexe, il vaut mieux tester le fonctionnement de l’anneau LED avec un code simple. Le code ci-dessous allume successivement les 60 LED, puis les éteint toutes. Cela permet de vérifier que toutes les LED fonctionnent et que les segments de l’anneau sont correctement câblés.
#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);
}
}
Dans l’extrait de code ci-dessus, nous utilisons la Adafruit NeoPixel library . Si ce n’est pas déjà fait, vous devrez install the library d’abord, avant de pouvoir utiliser ce code.
Analysons le code de test pour mieux comprendre son fonctionnement.
Constantes et variables
On commence par définir la constante DINPIN qui indique la broche de données à laquelle l’anneau LED est connecté, et NUMPIXELS qui définit le nombre total de pixels/LED du ruban. Dans notre cas, nous avons 60 LED et utilisons le GPIO 3.
#define DINPIN 3 #define NUMPIXELS 60
Fonction setup
Dans la fonction setup() , on initialise l’anneau LED en appelant pixels.begin() , on efface les couleurs existantes avec pixels.clear() , on règle la luminosité à 10 avec pixels.setBrightness(10) , puis on affiche l’état des pixels avec pixels.show() .
void setup() {
pixels.begin();
pixels.clear();
pixels.setBrightness(10);
pixels.show();
}
Comme je l’ai mentionné plus haut, faites attention en changeant la luminosité ! À pleine puissance, l’anneau LED peut consommer trop de courant pour votre ESP32, port USB ou alimentation.
Fonction loop
La fonction loop() contient une boucle qui allume d’abord toutes les LED en blanc en parcourant chaque pixel avec une boucle for. La fonction pixels.setPixelColor() sert à définir la couleur de chaque pixel sur blanc (255, 255, 255) puis pixels.show() est appelée pour afficher l’état des LED. Un délai de 200ms est ajouté entre chaque mise à jour.
Après avoir allumé tous les pixels en blanc, une autre boucle sert à éteindre toutes les LED en les passant au noir (0, 0, 0) et en mettant à jour le ruban avec pixels.show() à nouveau, avec un délai de 200ms entre chaque mise à jour.
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);
}
}
Si tout fonctionne, félicitations ! Dans la section suivante, on va essayer quelque chose d’un peu plus complexe en simulant l’affichage de l’heure sur l’anneau LED et en améliorant un peu notre circuit.
Câblage amélioré de l’horloge à LED circulaire
Comme mentionné plus haut, l’anneau LED peut consommer jusqu’à 3,6A si toutes les LED sont allumées à fond. On ne veut pas tirer autant de courant de l’ESP32. Il faut donc connecter l’anneau LED à une alimentation externe. Le schéma ci-dessous montre comment faire :

Le WS2812 fonctionne en 5V, donc il vous faudra une alimentation 5V capable de fournir assez de courant pour l’anneau LED à la luminosité maximale. À une luminosité de 5, j’ai mesuré un courant de 80mA et à une luminosité de 50, le courant était de 400mA.
La faible luminosité de 5 est suffisante dans une pièce sombre et 50 est largement suffisant en plein jour. À des valeurs plus élevées, l’horloge peut carrément éclairer une pièce, ce qui n’est pas le but. Dans mon cas, une alimentation de 500mA suffit.
Sur le schéma ci-dessus, j’ai aussi ajouté un condensateur recommandé de 100μ à 1000μ sur la ligne d’alimentation. Cela permet de stabiliser l’alimentation en cas de fluctuations de courant dues à l’allumage ou l’extinction simultanée de nombreuses LED.
Dans la section suivante, on passe du code de test à une horloge simulée qui permet d’essayer différentes façons et couleurs d’afficher l’heure sur un anneau LED.
Code pour une horloge à LED circulaire simulée
Il existe de nombreuses façons d’afficher l’heure sur un anneau LED. L’image ci-dessous montre la version que j’ai choisie :

L’heure actuelle (point orange) est indiquée par une seule LED à la position correspondante sur l’horloge. Les minutes (points jaunes) sont affichées en allumant toutes les LED jusqu’à la minute en cours. Sur la photo ci-dessus, il est 11:32, donc la LED orange à la position 11h est allumée, ainsi que les 32 LED jaunes pour les minutes.
Les repères du cadran sont indiqués par des LED blanches peu lumineuses et la seconde en cours est affichée en augmentant la luminosité de la LED à la position correspondante. C’est un peu difficile à voir sur la photo, mais la LED à la position 3 secondes est légèrement plus brillante. La façon de faire est expliquée dans le code suivant.
Notez que ce code ne montre pas l’heure réelle mais simule simplement une heure accélérée pour tester l’affichage sur l’anneau LED. Jetez un œil au code complet avant qu’on en discute les détails.
#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();
}
Dans le code ci-dessus, on utilise la bibliothèque Adafruit NeoPixel pour simuler une horloge avec un ruban LED de 60 pixels. L’horloge affiche les heures, minutes et secondes en changeant la couleur de certains pixels du ruban.
Constantes et variables
Comme précédemment, on commence par définir les constantes et variables nécessaires à la simulation de l’horloge. On précise la broche de données du ruban LED, le nombre total de pixels et une valeur d’offset pour l’indexation des pixels.
#include "Adafruit_NeoPixel.h" #define DINPIN 3 #define NPIXELS 60 #define OFFSET 29 Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
Parlons un peu de la constante OFFSET et pourquoi elle est nécessaire. Si vous assemblez l’horloge à LED dans son cadre, la première LED de l’anneau (là où les fils sont connectés) sera située un pixel à gauche de la position 6h (index 0).

Donc, si l’heure est 12h (= 0h), on ne peut pas allumer la LED/pixel à l’index 0, car elle est en bas. Il faut allumer la LED à l’index 29, qui correspond à la position 12h. Cela signifie qu’il faut toujours ajouter un offset de 29 à tout index de pixel pour allumer la LED à la bonne position sur l’horloge.
C’est à cela que sert la constante OFFSET . Selon votre cadre et la position mécanique de la première LED, il faudra peut-être utiliser un autre OFFSET .
Fonction d’index
La constante OFFSET est utilisée dans la fonction index() pour mapper un index de temps i à une position LED sur l’anneau. En plus de l’offset, il faut aussi calculer le modulo (%) pour s’assurer que l’index reste entre 0 et 59, puisqu’on a 60 LED.
int index(int i) {
return (i + OFFSET) % NPIXELS;
}
Par exemple, pour 36 minutes, la LED à allumer pour afficher la minute sera à l’index : 36 + 29 % 60 = 5.
Fonctions setPixel et getPixel
Les fonctions setPixel() et getPixel() utilisent la fonction index() pour définir ou obtenir la couleur d’un index i sur l’anneau LED :
void setPixel(int i, uint32_t color) {
pixels.setPixelColor(index(i), color);
}
uint32_t getPixel(int i) {
return pixels.getPixelColor(index(i));
}
Fonction setTick
La fonction setTick() marque les repères horaires sur l’anneau. Comme une horloge a 12 heures mais qu’on a 60 LED, il faut allumer chaque 5ème LED pour marquer les heures.
void setTicks(int m) {
uint32_t tickColor = pixels.Color(50, 50, 50);
for (int h = 0; h < 12; h++) {
setPixel(h * 5, tickColor);
}
}
Si vous regardez la fonction, elle parcourt les 12 heures, multiplie chaque heure h par 5 puis utilise setPixel() et donc index() pour convertir l’heure en index LED, où la couleur est définie.
J’utilise une couleur blanche faible (50, 50, 50) mais vous pouvez choisir la couleur que vous voulez. Assurez-vous juste que la valeur ne dépasse pas 200, à cause de la façon dont les secondes sont affichées (on en reparle plus loin).
Fonction setHours
La fonction setHours() fonctionne comme la fonction setTicks() , mais affiche une heure spécifique au lieu de toutes les heures et utilise une couleur différente. J’ai choisi un orange-rouge chaud pour l’heure.
void setHours(int h) {
uint32_t hourColor = pixels.Color(200, 50, 0);
setPixel(h * 5, hourColor); // h: 0..11 = 12 Hours
}
Fonction setMinutes
Alors que la fonction setHours() n’allume qu’une seule LED pour l’heure, la fonction setMinutes() allume toutes les LED jusqu’à la minute en cours. C’est pour cela qu’il y a la boucle for .
void setMinutes(int m) {
uint32_t minColor = pixels.Color(100, 100, 0);
for (int i = 0; i <= m; i++) {
setPixel(i, minColor);
}
}
Fonction setSeconds
Enfin, on veut afficher la seconde en cours. Je ne voulais pas afficher la seconde par-dessus les heures et minutes, j’ai donc choisi de rendre la LED de la seconde un peu plus lumineuse – quelle que soit sa couleur actuelle.
void setSeconds(int s) {
uint32_t color = getPixel(s);
setPixel(s, color + 0x373737);
}
La fonction commence par récupérer la couleur de la LED à la seconde courante s en appelant getPixel() . Elle rend ensuite la couleur plus lumineuse en ajoutant 55 à chaque composante (rouge, vert, bleu). 55 en hexadécimal, c’est 0x37 . C’est de là que vient cette valeur de 0x373737 . Et c’est la raison pour laquelle la couleur de base ne doit pas dépasser 200 , puisque 200 + 55 = 255 , qui est la valeur maximale.
Fonction showTime
Pour afficher une heure ( h, m, s ) sur l’horloge, il suffit d’appeler les fonctions ci-dessus dans l’ordre suivant et de mettre à jour l’état des LED via pixels.show() à la fin.
void showTime(int h, int m, int s) {
pixels.clear();
setMinutes(m);
setTicks(m);
setHours(h);
setSeconds(s);
pixels.show();
}
Fonction simulateClock
Pour tester l’affichage de l’heure, j’utilise une horloge simulée qui fait défiler les heures, minutes et secondes 10 fois plus vite ( 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);
}
}
}
}
Fonctions setup et loop
Les fonctions setup et loop sont maintenant très simples. Dans setup, on initialise l’anneau LED. Dans loop, on simule le fonctionnement de l’horloge.
void setup() {
pixels.begin();
pixels.clear();
pixels.setBrightness(10);
pixels.show();
}
void loop() {
simulateClock();
}
À part l’affichage de l’heure réelle, on a maintenant tout ce qu’il faut pour afficher l’heure sur notre anneau LED. Vous pouvez utiliser ce code simulateur pour choisir les couleurs et visualisations qui vous plaisent, sans vous soucier de l’heure réelle à afficher.
Pour plus d’informations sur les LED WS2812 et les effets possibles, consultez notre tutoriel How To Control WS2812B Individually Addressable LEDs using Arduino . Notez cependant qu’en mai 2024, je n’ai pas réussi à faire fonctionner le FastLED library , utilisé dans ce tutoriel, avec un ESP32.
Dans la section suivante, nous allons récupérer l’heure réelle depuis un fournisseur de temps en ligne et utiliser le code ci-dessus pour l’afficher sur notre horloge.
Code pour une horloge à LED circulaire synchronisée via Internet
On pourrait utiliser l’horloge temps réel de l’ESP32 pour obtenir l’heure. Mais cela voudrait dire qu’à chaque coupure de courant, il faudrait régler l’heure manuellement. Il faudrait aussi ajuster l’heure deux fois par an lors du passage à l’heure d’été/hiver.
Cela impliquerait d’ajouter des boutons ou un logiciel supplémentaire (interface web, appli mobile) pour pouvoir régler l’heure. Ce n’est pas très pratique. Je préfère donc une horloge totalement automatique qui récupère l’heure exacte en permanence via Internet.
Si vous voulez savoir comment cela fonctionne en détail, consultez notre tutoriel Automatic Daylight Savings Time Clock . J’ai essentiellement copié/collé le code de là dans le code ci-dessous.
#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);
}
Avant de pouvoir utiliser ce code, vous devrez installer deux bibliothèques supplémentaires, à savoir » ArduinoJson.h » et » TimeLib.h « . » WiFi.h » et » HTTPClient.h » font partie de la bibliothèque standard ESP32/Arduino et n’ont pas besoin d’être installées séparément.
Vous devrez aussi renseigner le SSID et le mot de passe de votre réseau WiFi. Cela permettra à l’ESP32 de se connecter au fournisseur de temps à l’URL indiquée :
#define WIFI_SSID "YOUR_SSID" #define WIFI_PASSPHRASE "YOUR_PASSWORD" #define URL "http://worldtimeapi.org/api/ip"
Ceci étant fait, regardons de plus près les nouvelles fonctions ajoutées au code.
Fonction syncTime
La fonction syncTime() se connecte au fournisseur de temps en ligne, lit les données au format JSON, extrait les informations de temps utiles (h, m, s, D, M, Y) et règle l’horloge interne de l’ESP32 en conséquence.
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();
}
Fonction shouldSyncTime
On ne veut pas interroger le fournisseur de temps trop souvent, au risque d’être bloqué. Je limite donc la synchronisation de l’horloge interne de l’ESP32 à une fois par heure. Plus précisément, on synchronise à la 3ème seconde de chaque heure. La fonction shouldSyncTime() nous indique quand il est temps de le faire.
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;
}
Fonction updateDisplay
La fonction updateDisplay() remplace la fonction simulateClock() qu’on utilisait avant. Elle lit l’horloge interne de l’ESP32, synchronisée avec l’heure Internet, et appelle showTime() pour l’afficher sur l’anneau LED.
void updateDisplay() {
time_t t = now();
showTime(hourFormat12(t), minute(t), second(t));
}
Fonction setup
Dans la fonction setup() , on ajoute la connexion au réseau WiFi mais on initialise l’anneau LED comme avant.
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();
}
Fonction loop
Dans la fonction loop() , on vérifie d’abord s’il faut synchroniser l’heure. Si oui, on appelle syncTime() pour le faire. Mais dans tous les cas, on appelle updateDisplay() pour afficher l’heure actuelle sur l’anneau LED.
void loop() {
if (shouldSyncTime())
syncTime();
updateDisplay();
delay(100);
}
Comme il y a un delay(100) dans la boucle, cette mise à jour a lieu toutes les 100 ms – soit 10 fois par seconde.
Et voilà ! Vous avez maintenant une horloge totalement automatique et toujours à l’heure, qui affiche l’heure sur un anneau LED. Dans les prochaines sections, nous ajouterons la variation automatique de la luminosité et l’activation par détection de mouvement pour la rendre encore meilleure.
Variation automatique de la luminosité de l’horloge à LED circulaire
Les LED WS2812 sont très lumineuses. En général, on ne veut pas qu’elles soient aussi brillantes la nuit. À l’inverse, si on règle la luminosité pour une pièce sombre, l’horloge sera difficile à lire en plein jour. Ce serait pratique d’ajuster automatiquement la luminosité des LED en fonction de la lumière ambiante.
Pour cela, on ajoute une photorésistance (LDR) à notre circuit, on lit sa valeur via une entrée analogique de l’ESP32 et on appelle setBrightness() pour ajuster la luminosité globale de l’anneau LED.
Schéma de l’horloge à LED circulaire avec LDR
L’image ci-dessous montre comment connecter le capteur de lumière (LDR) au circuit existant. Une patte du LDR est reliée au 5V et l’autre à un potentiomètre de 10KΩ, qui est lui-même relié à la masse.

La sortie du LDR (fil vert) est connectée au GPIO0 de l’ESP32. N’importe quel autre GPIO capable de lire un signal analogique conviendra aussi.
Le LDR et le potentiomètre de 10KΩ forment un pont diviseur de tension. Pour plus de détails sur ce principe, consultez notre tutoriel How to detect light using an Arduino , d’où provient ce circuit.
Selon la luminosité ambiante, le circuit LDR produira une tension proportionnelle entre 0V et 5V. En pratique, on n’atteint jamais toute la plage, car il n’y a jamais d’obscurité ou de lumière totale. Le potentiomètre de 10KΩ permet d’ajuster le point de fonctionnement de la tension de sortie. Nous allons adapter les variations de tension à une plage adaptée dans le code ci-dessous.
Code de test pour ajuster la plage de luminosité
Avant d’ajouter la variation automatique à notre horloge à LED, il faut d’abord ajuster les paramètres. Selon la lumière ambiante, on souhaite une luminosité entre 5 (très sombre) et 50 (très lumineux).
La valeur lue par le capteur de lumière sur l’entrée analogique sera cependant comprise entre 0 et 4095. Cela dépendra de la résistance du LDR, du réglage du potentiomètre et des conditions de lumière.
Le code de test suivant vous permet de trouver la bonne correspondance entre les valeurs du capteur LDR et les valeurs de luminosité à appliquer.
#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);
}
Il lit la valeur du LDR ( ldrValue ), l’affiche, la convertit en valeur de luminosité ( brightness ) et affiche aussi ce résultat. Si vous téléversez et exécutez ce code, vous devriez voir quelque chose comme ceci dans le Moniteur Série.

Si vous couvrez le capteur de lumière (obscurité), vous devez voir une valeur de luminosité proche de 5, et si vous l’exposez à une lumière très forte (par exemple, soleil), vous devez obtenir une valeur proche de 50. Pour cela, il faudra ajuster les paramètres fromLow et fromHigh de la fonction map :
map(value, fromLow, fromHigh, toLow, toHigh)
Pour mon capteur LDR, le réglage du potentiomètre et la lumière ambiante, j’ai fini avec fromLow=1400 et fromHigh=3000 , comme vous pouvez le voir dans le code ci-dessus. Vos valeurs seront différentes, mais vous pouvez utiliser les miennes comme point de départ.
De même, si vous souhaitez une plage de luminosité différente de 5..50 , vous pouvez choisir d’autres valeurs pour toLow et toHigh . Vous pouvez même éteindre l’horloge la nuit en choisissant toLow=0 .
Ajout du code de variation automatique
Une fois que vous avez trouvé de bons réglages, vous pouvez ajouter la fonction suivante de variation automatique au code de l’horloge.
void updateBrightness() {
if (millis() % 1000 < 100) {
int ldrValue = analogRead(LDRPIN);
int brightness = map(ldrValue, 1400, 3000, 5, 50);
pixels.setBrightness(constrain(brightness, 5, 50));
}
}
Elle règle la luminosité de tout l’anneau LED en fonction de la valeur lue sur LDRPIN . Cependant, on ne veut pas ajuster la luminosité à chaque mise à jour de l’horloge (toutes les 100 ms), car cela pourrait provoquer un effet de scintillement désagréable, chaque petite variation de lumière changeant la luminosité des LED.
On met donc à jour la luminosité seulement chaque seconde, ce qui est réalisé par millis() % 1000 < 100. Si vous souhaitez des mises à jour plus ou moins fréquentes que toutes les 1000 ms, changez simplement le 1000 par la valeur de votre choix. Notez que le seuil de < 100 est lié au délai de 100 ms dans la boucle principale.
Notez aussi l’appel supplémentaire à onstrain(brightness, 5, 50), qui garantit que la luminosité reste dans la plage 5..50 , ce qui n’est pas garanti par la fonction map() .
Pour intégrer la variation de luminosité dans le code de l’horloge, il suffit d’ajouter un appel à la fonction updateBrightness() dans la boucle principale :
void loop() {
if (shouldSyncTime())
syncTime();
updateBrightness();
updateDisplay();
delay(100);
}
Bien sûr, il faut aussi définir la constante LDRPIN et configurer LDRPIN en entrée. Le code complet de l’horloge à LED avec variation automatique est présenté ci-dessous :
#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);
}
Ouf, on y est presque ! Pour finir, on va activer automatiquement l’horloge lorsqu’un mouvement est détecté.
Activation automatique de l’horloge à LED circulaire
L’horloge à LED circulaire consomme pas mal d’énergie et il y a un risque de griller les LED si elle reste allumée en permanence. Mais cela ne sert à rien d’afficher l’heure si personne n’est là pour la voir. Nous allons donc ajouter un capteur de mouvement (PIR) au circuit. Il allumera l’horloge pendant une durée déterminée (par exemple 10 secondes) lorsqu’un mouvement est détecté, puis éteindra l’anneau LED.
L’image suivante montre comment ajouter le capteur de mouvement PIR au circuit existant. Il suffit de connecter le 5V et la masse aux broches correspondantes du capteur PIR (fils rouge et bleu). La sortie du capteur (fil violet) est reliée au GPIO10 de l’ESP32.

Voici une photo du circuit complet sur breadboard. Comme vous pouvez le voir, j’ai pu faire fonctionner l’horloge à LED pour des tests sur une pile 9V (avec un régulateur 5V).

Cependant, si vous voulez vraiment faire fonctionner l’horloge sur batterie, je recommande d’utiliser une batterie USB externe. Vous pouvez aussi mettre l’ESP32 en deep-sleep lorsque l’horloge est inactive. Pour plus de détails, consultez notre tutoriel sur How to Build a Motion Activated Night Light .
Avant d’intégrer l’activation automatique dans le code de l’horloge, écrivons d’abord un code de test pour le capteur PIR.
Code de test pour le capteur PIR
Le code suivant lit le signal du capteur PIR et affiche « motion detected » si un mouvement est détecté ou « nothing » sinon.
#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);
}
Le capteur de mouvement que j’utilise ici est le AM312 , qui enverra un signal haut pendant environ 2 secondes après la détection d’un mouvement. Pour plus de détails sur le AM312 et son utilisation, consultez notre tutoriel How to Build a Motion Activated Night Light .
Si on utilise directement le signal de l’AM312 dans notre code, l’horloge ne serait activée que 2 secondes, puis s’éteindrait si aucun autre mouvement n’est détecté. Cela provoquerait des allumages/extinctions fréquents, ce qui n’est pas très agréable. Le code ci-dessus ajoute donc un minuteur qui fait que motionDetected() retourne true pendant au moins 10000 ms = 10 secondes. Vous pouvez allonger ou raccourcir cette durée à votre convenance.
Si vous montez le circuit, téléversez et exécutez le code, et que tout fonctionne comme prévu, vous êtes prêt à intégrer l’activation automatique dans le code de l’horloge.
Ajout du code d’activation automatique
Pour l’intégration, nous allons ajouter deux fonctions et modifier légèrement la boucle principale.
Fonction motionDetected
La fonction motionDetected() est basée sur le code de test ci-dessus et retourne true pendant au moins 10 secondes après la détection d’un mouvement. Le minuteur de 10 secondes se réinitialise à chaque nouveau mouvement détecté pendant cette période. Cela maintient l’horloge allumée tant que quelqu’un est présent et bouge.
bool motionDetected() {
if (digitalRead(PIRPIN))
lastMotion = millis();
unsigned long diff = millis() - lastMotion;
return diff < 10000 && diff >= 0;
}
Notez la condition diff >= 0 dans le return. Elle est nécessaire car la valeur du minuteur retournée par millis() va wrap après un certain nombre de jours (49 pour un Arduino UNO) et la différence de temps diff serait négative.
Fonction clearDisplay
Si aucun mouvement n’est détecté, on éteint les LED de l’horloge en appelant clearDisplay() . Cette fonction efface simplement toutes les valeurs de pixels et met à jour l’état des LED en appelant show() .
void clearDisplay() {
pixels.clear();
pixels.show();
}
Fonction loop
Enfin, il faut modifier la fonction loop pour réagir au capteur de mouvement. Si on détecte un mouvement via motionDetected() , on effectue la mise à jour habituelle de l’horloge. Sinon, on éteint l’affichage via clearDisplay() . Tout cela se fait toutes les 1/10 de seconde grâce au delay(100) .
void loop() {
if (motionDetected()) {
if (shouldSyncTime())
syncTime();
updateBrightness();
updateDisplay();
} else {
clearDisplay();
}
delay(100);
}
Et voilà. Avec ça, on a toutes les briques nécessaires. Ci-dessous, le code complet pour l’horloge activée par mouvement.
Code complet pour l’horloge activée par mouvement
#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);
}
Conclusions
Dans ce tutoriel, vous avez appris à fabriquer une horloge à LED circulaire activée par mouvement, avec variation automatique de la luminosité et synchronisation de l’heure via Internet. Elle a l’avantage d’être toujours précise et totalement automatique. Pas besoin de boutons ou de réglages pour l’allumer, régler l’heure ou ajuster la luminosité.
En plus de fabriquer un meilleur cadre ou de changer les couleurs des LED à votre goût, il y a encore quelques améliorations possibles. On pourrait ajouter un capteur de température pour ajuster la couleur des LED en fonction de la température ambiante. Par exemple, plus bleuté quand il fait froid et plus rouge quand il fait chaud.
Comme l’horloge est activée par mouvement, il serait possible de la faire fonctionner sur batterie, surtout en mettant l’ESP32 en deep-sleep lorsque l’affichage est inactif. Consultez notre tutoriel sur How to Build a Motion Activated Night Light pour un exemple.
Enfin, vous pourriez aussi alterner toutes les quelques secondes entre l’affichage de l’heure et une représentation de la date, puisque nous téléchargeons déjà la date depuis le fournisseur de temps. Au lieu de worldtimeapi.org, vous pouvez aussi utiliser un serveur SNTP pour synchroniser l’heure plus souvent. Voir notre tutoriel sur How to synchronize ESP32 clock with SNTP server .
Et si vous n’aimez pas du tout l’horloge circulaire, jetez un œil à ce tutoriel Digital Clock with CrowPanel 3.5″ ESP32 Display , qui explique comment afficher l’heure et la date sur un bel écran, comme une horloge digitale classique.
Plein de choses à expérimenter !
Si vous avez des questions, n’hésitez pas à les poster. Je serai ravi d’aider ; )

