Guides · 15 min de lecture

ESP32 NTP : l'heure d'internet avec fuseau horaire, OLED et RTC

Obtenir l'heure NTP sur l'ESP32 : configTzTime et fuseau horaire, getLocalTime, strftime, horloge OLED, secours DS3231, tâche quotidienne et ntptime sur Pico W.

Récupérer l'heure avec ESP32 NTP, c'est relier la carte aux serveurs de temps d'internet pour connaître la date et l'heure à quelques dizaines de millisecondes près, sans aucun module horloge. Une fois l'ESP32 connecté au Wi-Fi, un seul appel à configTzTime() récupère l'heure, applique ton fuseau horaire, puis la carte continue de compter toute seule. Horloges de bureau, arrosage programmé, horodatage des données, réveil du matin ou éclairage qui s'allume à heure fixe : plus besoin d'un module RTC séparé.

Dans ce guide, on voit pas à pas comment fonctionne NTP, comment régler le bon fuseau horaire (France, Belgique, Suisse, Québec), comment écrire la date dans n'importe quel format avec getLocalTime et strftime, comment afficher l'heure sur un écran OLED, comment la conserver avec un module DS3231 quand le Wi-Fi ou le courant coupe, et comment lancer une tâche à heure fixe chaque jour. Dans la dernière partie, on fait la même chose sur le Raspberry Pi Pico W avec le module MicroPython ntptime.

Carte de développement ESP32
ESP32 DevKit : grâce à son Wi-Fi intégré, il récupère l'heure sur les serveurs NTP et garde la date et l'heure sans module horloge séparé.

Qu'est-ce que NTP et comment l'ESP32 obtient-il l'heure ?

NTP (Network Time Protocol) est un protocole utilisé depuis les années 1980 pour synchroniser les horloges des ordinateurs et des appareils via internet. Les serveurs sont organisés en couches appelées strates : les serveurs de strate 1 sont reliés directement à une horloge atomique ou au GPS, ceux de strate 2 prennent leur heure chez eux, et ainsi de suite. L'ESP32 demande l'heure avec un petit paquet sur le port UDP 123 ; le serveur répond avec un horodatage contenant les secondes écoulées depuis 1900 et une fraction de seconde. L'ESP32 tient compte du temps d'aller-retour du paquet et règle sa propre horloge. Sur une connexion domestique, la précision obtenue se situe en général entre 10 et 50 millisecondes, largement suffisant pour une horloge.

La pile réseau lwIP de l'ESP32 utilise SNTP, une version simplifiée de NTP. Une fois l'heure reçue, l'ESP32 la garde comme heure système et continue de compter avec un compteur dérivé de son quartz ; il ne va pas sur internet à chaque seconde. En arrière-plan, il interroge de nouveau le serveur à intervalles réguliers (par défaut toutes les 3 heures dans le cœur Arduino ESP32) et corrige les petites dérives. L'heure système est stockée en UTC ; l'heure locale que tu vois est calculée en lui appliquant la règle du fuseau horaire.

NotionValeur / explication
Protocole et portSNTP, UDP 123
HorodatageSecondes depuis 1900 (l'ESP32 les convertit en temps Unix depuis 1970)
Précision typique10–50 ms sur une connexion domestique
Resynchronisation3 heures par défaut dans Arduino ESP32, modifiable avec sntp_set_sync_interval() (minimum 15 s)
Nombre maximal de serveurs3 (les noms passés à configTzTime)
Trafic internetRequête et réponse sont des paquets NTP de 48 octets ; quelques kilo-octets par jour au plus
Ce qu'il fautUn ESP32, un réseau Wi-Fi 2,4 GHz et une connexion internet

configTime et configTzTime : régler le fuseau horaire

Le cœur Arduino ESP32 propose deux façons de démarrer NTP. configTime(gmtOffset_sec, daylightOffset_sec, serveur1, serveur2, serveur3) prend le décalage par rapport à UTC et le décalage d'heure d'été en secondes. Pour la Turquie, par exemple, on écrit configTime(10800, 0, "pool.ntp.org"), car 3 heures font 10 800 secondes et la Turquie reste à UTC+3 toute l'année sans heure d'été depuis 2016. Cette méthode est simple, mais elle ne connaît pas les dates de changement d'heure de ta région ; en France, le passage à l'heure d'été tomberait le mauvais jour, il vaut donc mieux utiliser configTzTime.

La bonne méthode est configTzTime(TZ, serveur1, serveur2, serveur3). Cette fonction prend une chaîne de fuseau horaire au format POSIX qui contient aussi les règles d'heure d'été, et l'ESP32 fait le changement tout seul chaque année. Pour la France, la Belgique et la Suisse, la chaîne est "CET-1CEST,M3.5.0,M10.5.0/3" : CET est l'abréviation de l'heure d'hiver, -1 signifie qu'il faut retirer une heure à l'heure locale pour obtenir UTC, CEST est l'heure d'été, M3.5.0 veut dire « dernier dimanche de mars » et M10.5.0/3 « dernier dimanche d'octobre à 3 h ». Les exemples ci-dessous utilisent le fuseau turc "<+03>-3" ; remplace simplement TZ_INFO par la valeur du tableau.

RégionChaîne TZ (configTzTime)Équivalent avec configTime
Turquie (UTC+3 toute l'année)<+03>-3configTime(10800, 0, ...)
France, Belgique, SuisseCET-1CEST,M3.5.0,M10.5.0/3Pas adapté, utilise configTzTime
Royaume-Uni, PortugalGMT0BST,M3.5.0/1,M10.5.0Pas adapté, utilise configTzTime
Québec (Montréal)EST5EDT,M3.2.0,M11.1.0Pas adapté, utilise configTzTime
La Réunion (UTC+4)<+04>-4configTime(14400, 0, ...)
UTC (pour l'horodatage)UTC0configTime(0, 0, ...)
Le signe d'une chaîne de fuseau POSIX est inversé : si tu écris "CET+1" pour la France ou "UTC+3" pour la Turquie, l'ESP32 le lit comme « en retard sur UTC » et l'horloge est décalée de 2 ou 6 heures. Les bonnes chaînes sont "CET-1..." et "<+03>-3". Si ton horloge est décalée d'exactement le double de ton décalage, c'est presque toujours le signe.

Matériel et câblage

Pour voir l'heure dans le moniteur série, un ESP32 et un réseau Wi-Fi suffisent. Plus loin dans le guide, on utilise un écran OLED SSD1306 de 0,96 pouce pour afficher l'heure, un module RTC DS3231 pour la conserver pendant les coupures de courant et un module relais pour l'exemple de tâche programmée. L'OLED et le DS3231 partagent le même bus I2C, donc ils se branchent sur les deux mêmes broches. Tu peux consulter le brochage de l'ESP32 avec l'outil Pinout.

Broche du composantESP32Raspberry Pi Pico WRemarque
OLED / DS3231 VCC3V33V3 (OUT)Les deux fonctionnent en 3,3 V
OLED / DS3231 GNDGNDGNDMasse commune
OLED / DS3231 SDAGPIO 21GP4Ligne de données I2C
OLED / DS3231 SCLGPIO 22GP5Ligne d'horloge I2C
Module relais INGPIO 26GP15Exemple de tâche programmée
Module relais VCC5V (VIN)VBUSLa plupart des bobines de relais demandent 5 V
L'ESP32 ne se connecte qu'aux réseaux Wi-Fi 2,4 GHz. Si ta box utilise le même nom pour le 5 GHz et le 2,4 GHz, ou si le nom du réseau contient des caractères accentués, la connexion peut échouer. Si l'heure n'arrive jamais, vérifie d'abord dans le moniteur série que le Wi-Fi est connecté ; NTP ne fonctionne qu'après.

Pas à pas : ton premier code ESP32 NTP

  1. Vérifie que le paquet de cartes ESP32 est installé dans l'Arduino IDE et choisis ESP32 Dev Module dans Outils. Si tu ne sais pas comment faire, lis l'article Qu'est-ce que l'ESP32.
  2. Aucune bibliothèque supplémentaire n'est nécessaire : WiFi.h et time.h sont fournis avec le cœur ESP32.
  3. Remplace WIFI_SSID et WIFI_PASS par ceux de ton réseau, et TZ_INFO par "CET-1CEST,M3.5.0,M10.5.0/3".
  4. Téléverse le code et ouvre le moniteur série à 115200 bauds.
  5. Tu dois d'abord voir les points du Wi-Fi, puis après quelques secondes la date et l'heure mises à jour chaque seconde.
C++ (Arduino IDE)
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";       // Wi-Fi adı / Wi-Fi name
5const char* WIFI_PASS = "Sifre123";    // Wi-Fi şifresi / Wi-Fi password
6const char* TZ_INFO   = "<+03>-3";     // Türkiye: UTC+3, yaz saati yok / Turkey: UTC+3, no DST
7
8void setup() {
9  Serial.begin(115200);
10  WiFi.begin(WIFI_SSID, WIFI_PASS);
11  Serial.print("Wi-Fi");
12  while (WiFi.status() != WL_CONNECTED) {    // bağlanana kadar bekle / wait until connected
13    delay(500);
14    Serial.print(".");
15  }
16  Serial.println(" OK");
17
18  // NTP sunucuları + saat dilimi / NTP servers + time zone
19  configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
20
21  struct tm t;
22  while (!getLocalTime(&t)) {                // ilk eşitlemeyi bekle (5 sn deneme) / wait for the first sync (5 s per try)
23    Serial.println("NTP bekleniyor / waiting for NTP");
24  }
25  Serial.println("Saat alindi / time received");
26}
27
28void loop() {
29  struct tm t;
30  if (getLocalTime(&t)) {
31    Serial.println(&t, "%d.%m.%Y %H:%M:%S");  // ör. 16.11.2026 14:05:09 / e.g. 16.11.2026 14:05:09
32  }
33  delay(1000);
34}
35
LigneQue fait-elle ?
configTzTime(TZ_INFO, ...)Démarre le client SNTP, règle serveurs et fuseau ; rend la main tout de suite, l'heure arrive en arrière-plan
getLocalTime(&t)Remplit une struct tm avec l'heure locale ; sans heure valide, attend jusqu'à 5 secondes puis renvoie false
Serial.println(&t, "...")La classe Print de l'ESP32 peut afficher une struct tm directement avec un format strftime
time(nullptr)Renvoie les secondes depuis 1970 (temps Unix, UTC)

Un détail important : getLocalTime() décide si l'heure est « valide » en regardant l'année. Au démarrage, l'heure système est le 1er janvier 1970 et la fonction renvoie false tant que l'année n'est pas postérieure à 2016. Ainsi, un code qui enregistre des données ou lance des tâches avant l'arrivée de NTP ne produit pas par erreur des entrées datées de 1970, à condition de tester la valeur renvoyée. Le second paramètre est le délai d'attente en millisecondes ; pour ne pas bloquer loop(), écris getLocalTime(&t, 0) afin qu'elle n'essaie qu'une fois.

struct tm : les morceaux de l'heure

getLocalTime() écrit l'heure découpée dans une struct tm. Avec ces champs, tu compares heures, jours ou mois pour construire des tâches programmées. Deux champs piègent souvent les débutants : le mois commence à 0 et l'année se compte à partir de 1900.

ChampPlageExemple (lundi 16 novembre 2026, 14:05:09)
tm_sec0–59 (60 lors d'une seconde intercalaire)9
tm_min0–595
tm_hour0–2314
tm_mday1–3116
tm_mon0–11 (0 = janvier)10
tm_yearAnnées depuis 1900126 (1900 + 126 = 2026)
tm_wday0–6 (0 = dimanche)1
tm_yday0–365 (jour de l'année, à partir de 0)319
Si l'écran affiche « 16/10/2026 » alors qu'on est en novembre, tu as oublié d'ajouter 1 à tm_mon. Si l'année affichée est 126, il faut tm_year + 1900. Avec strftime, ces conversions sont faites pour toi.

Formater la date et l'heure avec strftime

strftime() écrit l'heure d'une struct tm dans un tableau de caractères selon un modèle. Les codes précédés d'un signe pourcentage sont remplacés par la valeur correspondante, les autres caractères restent tels quels. Dimensionne le tableau à la sortie la plus longue plus un (pour le zéro final) : « 14:05:09 » fait 8 caractères, le tableau doit donc faire au moins 9 octets. Les codes les plus utilisés :

CodeSignificationExemple de sortie
%H / %M / %SHeure (00–23) / minute / seconde14 / 05 / 09
%I et %pHeure sur 12 heures et AM/PM02 PM
%d / %m / %YJour / mois (01–12) / année sur quatre chiffres16 / 11 / 2026
%yAnnée sur deux chiffres26
%A / %aJour de la semaine long / court (en anglais)Monday / Mon
%B / %bMois long / court (en anglais)November / Nov
%jJour de l'année (001–366)320
%F / %TRaccourcis de %Y-%m-%d / %H:%M:%S2026-11-16 / 14:05:09
%ZAbréviation du fuseau+03 (ou CET / CEST)

La bibliothèque C de l'ESP32 ne connaît que les noms anglais des jours et des mois ; %A écrit toujours « Monday ». Pour une date en français, tu ranges les noms dans tes propres tableaux et tu utilises tm_wday et tm_mon comme indices. L'exemple ci-dessous le fait avec des noms turcs ; remplace-les par « dimanche, lundi, … » et « janvier, février, … ». Comme le moniteur série gère l'UTF-8, les accents s'y affichent correctement. Le code produit une date lisible et le format ISO triable (2026-11-16 14:05:09) pour l'enregistrement.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO   = "<+03>-3";
7
8// tm_wday: 0 = Pazar ... 6 = Cumartesi / 0 = Sunday ... 6 = Saturday
9const char* GUNLER[] = {"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"};
10const char* AYLAR[]  = {"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran",
11                        "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"};
12
13void setup() {
14  Serial.begin(115200);
15  WiFi.begin(WIFI_SSID, WIFI_PASS);
16  while (WiFi.status() != WL_CONNECTED) delay(500);
17  configTzTime(TZ_INFO, "pool.ntp.org");
18}
19
20void loop() {
21  struct tm t;
22  if (!getLocalTime(&t)) return;             // saat henüz yok / time not set yet
23
24  char saat[9];                              // "HH:MM:SS" + '\0'
25  char iso[20];                              // "YYYY-MM-DD HH:MM:SS" + '\0'
26  strftime(saat, sizeof(saat), "%H:%M:%S", &t);
27  strftime(iso, sizeof(iso), "%Y-%m-%d %H:%M:%S", &t);
28
29  // Türkçe tarih: 16 Kasım 2026 Pazartesi / Turkish date
30  Serial.printf("%d %s %d %s  %s\n", t.tm_mday, AYLAR[t.tm_mon], t.tm_year + 1900, GUNLER[t.tm_wday], saat);
31  Serial.printf("Kayit / log: %s  epoch: %ld\n", iso, (long)time(nullptr));
32  delay(1000);
33}
34
Pour enregistrer des données (carte SD, Google Sheets, serveur), écris toujours la date au format %Y-%m-%d %H:%M:%S ou directement en temps Unix. Trié par ordre alphabétique, ce format est aussi trié chronologiquement, et Excel comme les bases de données le reconnaissent comme une date. À l'écran, tu peux garder le format français.

Afficher l'heure et la date sur un écran OLED

Pour transformer la sortie du moniteur série en vraie horloge de bureau, l'écran OLED SSD1306 de 0,96 pouce est idéal : bon marché, branché avec deux fils, il affiche de grands chiffres bien nets. Le code ci-dessous affiche en haut le jour et la date, au centre l'heure en grand et en bas l'état du Wi-Fi. Comme il ne redessine que lorsque la seconde change, et pas à chaque tour de boucle, il n'occupe pas le bus I2C pour rien. Installe les bibliothèques Adafruit SSD1306 et Adafruit GFX depuis le gestionnaire de bibliothèques ; l'écran lui-même est présenté dans l'article Écran OLED (SSD1306).

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <Wire.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO   = "<+03>-3";
10// OLED yazı tipinde Türkçe harf yok / the OLED font has no Turkish letters
11const char* GUNLER[] = {"Pazar", "Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi"};
12
13Adafruit_SSD1306 display(128, 64, &Wire, -1);
14int sonSaniye = -1;                          // son çizilen saniye / last drawn second
15
16void yaz(const char* mesaj) {                // ekrana tek satır / one line on screen
17  display.clearDisplay();
18  display.setTextSize(1);
19  display.setCursor(0, 0);
20  display.print(mesaj);
21  display.display();
22}
23
24void setup() {
25  Wire.begin(21, 22);                        // SDA = 21, SCL = 22
26  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
27  display.setTextColor(SSD1306_WHITE);
28  yaz("Wi-Fi baglaniyor...");
29
30  WiFi.begin(WIFI_SSID, WIFI_PASS);
31  while (WiFi.status() != WL_CONNECTED) delay(300);
32  configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
33  yaz("NTP bekleniyor...");
34}
35
36void loop() {
37  struct tm t;
38  if (!getLocalTime(&t, 0)) {                // 0 = beklemeden dene / try without waiting
39    delay(200);
40    return;
41  }
42  if (t.tm_sec == sonSaniye) {               // saniye değişmediyse çizme / redraw only on a new second
43    delay(20);
44    return;
45  }
46  sonSaniye = t.tm_sec;
47
48  char saat[9], tarih[11];
49  strftime(saat, sizeof(saat), "%H:%M:%S", &t);
50  strftime(tarih, sizeof(tarih), "%d.%m.%Y", &t);
51
52  display.clearDisplay();
53  display.setTextSize(1);
54  display.setCursor(0, 0);
55  display.print(GUNLER[t.tm_wday]);          // gün adı / day name
56  display.setCursor(68, 0);
57  display.print(tarih);                      // 10 karakter = 60 piksel / 10 chars = 60 px
58  display.setTextSize(2);
59  display.setCursor(16, 26);
60  display.print(saat);                       // 8 x 12 = 96 piksel genişlik / 96 px wide
61  display.setTextSize(1);
62  display.setCursor(0, 56);
63  display.print(WiFi.status() == WL_CONNECTED ? "Wi-Fi: var" : "Wi-Fi: yok");
64  display.display();
65}
66

Remarque que les noms des jours sont écrits sans accents : la police par défaut d'Adafruit GFX n'a pas de caractères comme é, è ou ç et affiche à la place des symboles sans signification. En taille de texte 2, chaque caractère fait 12 pixels de large ; « 14:05:09 » et ses 8 caractères occupent 96 pixels, et en partant du pixel 16 ils sont parfaitement centrés sur l'écran de 128 pixels. Si tu préfères un écran LCD 16x2, affiche les mêmes sorties strftime avec lcd.print() ; le câblage est expliqué dans l'article Écran LCD I2C.

Que devient l'heure quand le Wi-Fi coupe ? Secours RTC avec DS3231

La question la plus fréquente : si le Wi-Fi tombe, l'heure tombe-t-elle aussi ? Non. Une fois que l'ESP32 a reçu l'heure par NTP, il continue de compter avec son propre compteur ; la perte du Wi-Fi n'arrête pas l'horloge. Comme ce compteur tourne sur le quartz de 40 MHz, il dérive d'environ une ou deux secondes par jour, et la synchronisation suivante corrige l'écart au retour du Wi-Fi. Le vrai problème, c'est une coupure de courant ou un redémarrage par le bouton EN : l'heure système revient alors à 1970 et, sans Wi-Fi, l'ESP32 n'a aucun moyen de connaître l'heure.

SituationL'heure est-elle conservée ?Que faire ?
Le Wi-Fi coupe un momentOui, l'ESP32 continue de compterRien ; NTP corrige au retour du Wi-Fi
Deep sleep (sommeil profond)Oui, le compteur RTC continuePour de longs sommeils, resynchronise avec NTP à certains réveils
Coupure de courant ou bouton ENNon, l'horloge revient à 1970Ajoute un module RTC sur pile comme le DS3231
Pas d'internet du tout (champ, entrepôt)NTP n'arrive jamaisPrends l'heure uniquement d'un module RTC

Pendant le sommeil profond, l'heure n'est pas comptée par le quartz principal mais par un oscillateur RC interne moins précis. Cet oscillateur est sensible à la température, si bien que lors de longs sommeils l'horloge peut dériver de quelques secondes, voire plus ; pour les projets sur batterie qui dorment des heures, prends l'habitude de resynchroniser avec NTP tous les quelques réveils. Les détails sont dans l'article ESP32 Deep Sleep.

Module horloge RTC
Module RTC sur pile : il continue de compter même quand l'ESP32 n'est plus alimenté ; tu peux y lire l'heure en attendant le retour du Wi-Fi.

La meilleure façon d'avoir une horloge qui résiste aux coupures est de combiner les deux : au démarrage, prendre tout de suite l'heure du module DS3231, la corriger par NTP dès que le Wi-Fi est là, et mettre à jour le DS3231 à chaque synchronisation NTP. Dans le code ci-dessous, le DS3231 stocke l'heure UTC plutôt que l'heure locale ; ainsi, la règle de fuseau reste à un seul endroit (la chaîne TZ) et les changements d'heure n'abîment jamais le RTC. Pour détecter la synchronisation NTP, on enregistre une fonction de rappel avec sntp_set_time_sync_notification_cb(). Comme ce rappel s'exécute dans la tâche réseau, aucun travail I2C n'y est fait ; il lève seulement un drapeau et c'est loop() qui écrit dans le DS3231. Le code utilise la bibliothèque RTClib.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <Wire.h>
3#include <RTClib.h>
4#include <sys/time.h>
5#include "esp_sntp.h"
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO   = "<+03>-3";
10
11RTC_DS3231 rtc;
12volatile bool ntpGeldi = false;              // NTP eşitlemesi oldu mu? / did an NTP sync happen?
13
14void ntpCallback(struct timeval* tv) {       // her NTP eşitlemesinde çağrılır / called on every NTP sync
15  ntpGeldi = true;                           // I2C işini loop() yapsın / let loop() do the I2C work
16}
17
18void setup() {
19  Serial.begin(115200);
20  Wire.begin(21, 22);
21  setenv("TZ", TZ_INFO, 1);                  // NTP gelmese de saat dilimi geçerli / TZ valid even without NTP
22  tzset();
23
24  // 1) Sistem saatini önce DS3231'den kur (DS3231'de UTC saklanır) / set system time from the DS3231 (stores UTC)
25  if (rtc.begin() && !rtc.lostPower()) {
26    struct timeval tv = { (time_t)rtc.now().unixtime(), 0 };
27    settimeofday(&tv, nullptr);
28    Serial.println("Saat DS3231'den / time from DS3231");
29  }
30
31  // 2) Wi-Fi gelince NTP saati düzeltir; bağlantı sonra gelse de SNTP yeniden dener
32  //    NTP corrects the clock once Wi-Fi is up; SNTP keeps retrying if the link comes later
33  sntp_set_time_sync_notification_cb(ntpCallback);
34  WiFi.begin(WIFI_SSID, WIFI_PASS);
35  configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
36}
37
38void loop() {
39  if (ntpGeldi) {
40    ntpGeldi = false;
41    rtc.adjust(DateTime((uint32_t)time(nullptr)));   // DS3231'e UTC yaz / write UTC to the DS3231
42    Serial.println("DS3231 NTP ile guncellendi / DS3231 updated from NTP");
43  }
44
45  struct tm t;
46  if (getLocalTime(&t, 0)) {
47    Serial.println(&t, "%d.%m.%Y %H:%M:%S");
48  } else {
49    Serial.println("Saat yok: NTP ve DS3231 bekleniyor / no time yet");
50  }
51  delay(1000);
52}
53
Le DS1307 fonctionne avec le même code (écris RTC_DS1307 et utilise isrunning au lieu de lostPower), mais il peut dériver d'une ou deux minutes par mois. Grâce à son quartz compensé en température, le DS3231 reste autour d'une minute par an. Les différences entre modules et le piège du circuit de charge de la pile sont expliqués dans l'article Module RTC Arduino.

Lancer une tâche à heure fixe chaque jour

L'usage le plus courant de l'heure NTP, c'est la tâche programmée : faire tourner la pompe d'arrosage 10 minutes à 07:30, allumer l'éclairage du jardin à 19:00, envoyer une mesure à chaque heure pile. La méthode est simple : lire l'heure dans loop() et lancer la tâche quand l'heure et la minute correspondent à la cible. La vraie subtilité consiste à empêcher la tâche de se déclencher des centaines de fois dans la même minute. Pour cela, tu mémorises dans une variable le jour où elle a tourné (tm_yday), ainsi elle ne se relance jamais le même jour. Le code ci-dessous active le relais du GPIO 26 pendant 10 minutes chaque matin de semaine à 07:30 ; comme il mesure la durée avec millis(), il ne bloque jamais.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO   = "<+03>-3";
7
8const int ROLE_PIN = 26;                     // röle modülü IN / relay module IN
9const int GOREV_SAAT = 7;                    // her gün 07:30 / every day at 07:30
10const int GOREV_DAKIKA = 30;
11const unsigned long SURE_MS = 10UL * 60 * 1000;   // 10 dakika çalış / run for 10 minutes
12
13int sonGun = -1;                             // görevin çalıştığı son gün (tm_yday) / last day the task ran
14bool roleAcik = false;
15unsigned long acilisMs = 0;
16
17void setup() {
18  Serial.begin(115200);
19  pinMode(ROLE_PIN, OUTPUT);
20  digitalWrite(ROLE_PIN, LOW);
21  WiFi.begin(WIFI_SSID, WIFI_PASS);
22  while (WiFi.status() != WL_CONNECTED) delay(500);
23  configTzTime(TZ_INFO, "pool.ntp.org");
24}
25
26void loop() {
27  struct tm t;
28  if (getLocalTime(&t, 0)) {                 // saat geçerliyse / only when the time is valid
29    bool haftaici = (t.tm_wday >= 1 && t.tm_wday <= 5);   // Pzt-Cuma / Mon-Fri
30    if (haftaici && t.tm_hour == GOREV_SAAT && t.tm_min == GOREV_DAKIKA && t.tm_yday != sonGun) {
31      sonGun = t.tm_yday;                    // bugün bir kez / once per day
32      digitalWrite(ROLE_PIN, HIGH);
33      roleAcik = true;
34      acilisMs = millis();
35      Serial.println(&t, "%H:%M gorev basladi / task started");
36    }
37  }
38  if (roleAcik && millis() - acilisMs >= SURE_MS) {   // süre dolunca kapat / switch off after the period
39    digitalWrite(ROLE_PIN, LOW);
40    roleAcik = false;
41    Serial.println("Gorev bitti / task finished");
42  }
43  delay(200);
44}
45
  • Tester les secondes : une condition comme t.tm_sec == 0 saute la tâche si la boucle rate cette seconde (pendant une reconnexion Wi-Fi ou un long delay). Tester la minute et le jour est plus sûr.
  • Vérifier que l'heure est valide : ne lance pas de tâche tant que getLocalTime() renvoie false, sinon tu prends de mauvaises décisions basées sur 1970 juste après le démarrage.
  • Tâche manquée : si le courant coupe à 07:29 et revient à 07:35, la tâche ne tourne pas ce jour-là. Si c'est important, écris la condition « il est plus de 07:30 et elle n'a pas encore tourné aujourd'hui » et enregistre sonGun en flash avec Preferences.
  • Plusieurs tâches : range heure, minute et durée dans un tableau et utilise une variable de dernier jour séparée pour chaque tâche.
  • Charges à fort courant : pilote une pompe ou une lampe via un module relais ou un MOSFET, jamais directement depuis une broche.
Les broches GPIO de l'ESP32 délivrent 3,3 V et seulement quelques dizaines de milliampères. L'entrée IN d'un module relais se déclenche généralement en 3,3 V, mais certaines cartes relais 5 V ne commutent pas de façon fiable ; utilise alors une carte à commande 3,3 V ou un étage à transistor. Si tu travailles côté 230 V, ne touche jamais au câblage tant que le montage est branché.

Modifier l'intervalle de synchronisation et surveiller NTP

L'intervalle par défaut de 3 heures convient à la plupart des projets. Pour synchroniser plus souvent (par exemple en extérieur avec de fortes variations de température) ou réduire le trafic, passe le nouvel intervalle en millisecondes à sntp_set_sync_interval(). Appelle-la avant configTzTime(), car le nouvel intervalle s'applique au démarrage suivant ou à l'expiration de l'intervalle en cours. D'après les règles du protocole, il ne peut pas être inférieur à 15 secondes. sntp_get_sync_status() renvoie SNTP_SYNC_STATUS_COMPLETED une fois après chaque synchronisation terminée, puis se remet à zéro ; tu peux ainsi repérer chaque nouvelle synchronisation.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <time.h>
3#include "esp_sntp.h"
4
5const char* TZ_INFO = "<+03>-3";
6
7void setup() {
8  Serial.begin(115200);
9  WiFi.begin("AgAdi", "Sifre123");
10  while (WiFi.status() != WL_CONNECTED) delay(500);
11
12  sntp_set_sync_interval(6UL * 60 * 60 * 1000);   // 6 saatte bir eşitle (en az 15 sn) / sync every 6 hours (min 15 s)
13  configTzTime(TZ_INFO, "pool.ntp.org");          // ayar sntp_init() öncesi verilir / set before sntp_init()
14}
15
16void loop() {
17  if (sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
18    Serial.println("Yeni NTP esitlemesi / new NTP sync");
19  }
20  delay(1000);
21}
22

Quel serveur NTP choisir ?

Le choix le plus courant est pool.ntp.org : un ensemble de milliers de serveurs gérés par des bénévoles du monde entier, qui te donne à chaque requête l'adresse d'un serveur proche. Les sous-domaines par pays (fr.pool.ntp.org, be.pool.ntp.org, ch.pool.ntp.org, ca.pool.ntp.org) rapprochent encore le serveur. configTzTime() accepte jusqu'à trois noms de serveur ; si le premier ne répond pas, il passe au suivant.

ServeurParticularitéRemarque
pool.ntp.orgPool de bénévoles, serveur le plus prochePremier choix pour un usage général
fr.pool.ntp.org / europe.pool.ntp.orgPool par pays ou continentLatence un peu plus faible
time.cloudflare.comInfrastructure d'une grande entrepriseBon serveur de secours
time.google.comÉtale les secondes intercalaires (leap smear)Ne pas le mélanger avec d'autres serveurs
Adresse IP de ta boxCertaines box proposent un NTP localUtile sur un réseau local sans internet
Si tu dois diffuser ton propre produit sur des centaines d'appareils, la bonne pratique est de demander un pool à ton nom (vendor zone) au lieu de pool.ntp.org, ou de répartir au hasard l'heure de démarrage des appareils. Des milliers d'appareils qui interrogent les serveurs à la même seconde surchargent les serveurs des bénévoles.

NTP sur le Raspberry Pi Pico W : ntptime

Sur le Raspberry Pi Pico W (et le Pico 2 W), c'est le module MicroPython ntptime qui fait le travail. ntptime.settime() interroge le serveur NTP et règle le RTC interne du Pico sur l'heure UTC. MicroPython n'a pas de chaîne de fuseau comme l'ESP32 ; pour l'heure locale, tu ajoutes ton décalage en secondes à UTC (3 × 3600 pour la Turquie dans l'exemple, 3600 pour la France en hiver et 7200 en été). ntptime ne récupère l'heure qu'une fois et ne resynchronise pas automatiquement ; pour les projets qui tournent longtemps, appeler settime() une ou deux fois par jour supprime la dérive. Le RTC du Pico W est lui aussi remis à zéro à la coupure de courant, donc le secours DS3231 s'applique ici aussi. La connexion du Pico W au Wi-Fi est détaillée dans l'article Pico W Wi-Fi et serveur web.

Python
1import network
2import ntptime
3import time
4
5SSID = "AgAdi"
6PASS = "Sifre123"
7UTC_OFFSET = 3 * 3600          # Türkiye UTC+3 / Turkey UTC+3
8# localtime(): 0 = Pazartesi / 0 = Monday
9GUNLER = ("Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi", "Pazar")
10
11wlan = network.WLAN(network.STA_IF)
12wlan.active(True)
13wlan.connect(SSID, PASS)
14while not wlan.isconnected():  # bağlanana kadar bekle / wait until connected
15    time.sleep(0.5)
16
17ntptime.host = "pool.ntp.org"
18for deneme in range(5):        # NTP bazen zaman aşımına uğrar / NTP may time out
19    try:
20        ntptime.settime()      # dahili RTC'yi UTC'ye ayarlar / sets the internal RTC to UTC
21        break
22    except OSError:
23        time.sleep(2)
24
25def yerel_saat():              # UTC + 3 saat / UTC + 3 hours
26    return time.localtime(time.time() + UTC_OFFSET)
27
28son_gun = -1
29while True:
30    yil, ay, gun, saat, dk, sn, hgun, ygun = yerel_saat()
31    print("{:02d}.{:02d}.{} {} {:02d}:{:02d}:{:02d}".format(gun, ay, yil, GUNLER[hgun], saat, dk, sn))
32    if saat == 7 and dk == 30 and ygun != son_gun:   # her gün 07:30'da bir kez / once a day at 07:30
33        son_gun = ygun
34        print("Gorev calisti / task ran")
35    time.sleep(1)
36

En MicroPython, time.localtime() renvoie un tuple de huit éléments : année, mois (1–12), jour, heure, minute, seconde, jour de la semaine et jour de l'année. Il diffère de la struct tm de l'ESP32 sur deux points : le mois commence à 1 et le jour de la semaine compte 0 = lundi. ntptime.settime() lève une OSError quand le réseau dépasse le délai ; c'est pour cela que le code comporte une boucle de cinq tentatives.

Idées de projets avec l'horloge ESP32 NTP

  • Horloge de bureau Wi-Fi : affiche l'heure, la date et la température de la pièce sur un écran OLED et baisse la luminosité la nuit.
  • Horloge LED géante : affiche l'heure NTP en texte défilant sur une matrice LED MAX7219.
  • Arrosage programmé : chaque matin, mesure l'humidité du sol et, si besoin, fais tourner une mini pompe à eau pendant une durée fixe.
  • Enregistrement horodaté : écris les mesures des capteurs avec une date ISO sur une carte SD.
  • Horloge météo : ajoute à côté de l'heure la météo récupérée sur internet ; l'article ESP32 météo prend la suite.
  • Rapport du matin : chaque jour à 08:00, envoie sur ton téléphone via Telegram la température de la maison et les mouvements détectés pendant la nuit.

Erreurs fréquentes et solutions

SymptômeCause probableSolution
L'heure affiche 1970 ou n'arrive jamaisWi-Fi non connecté ou port NTP (UDP 123) bloquéVérifie d'abord WiFi.status() ; sur un réseau d'école ou d'entreprise, essaie un autre réseau ou le partage de connexion du téléphone
L'heure est décalée d'exactement 1 ou 2 heuresPas de fuseau défini, l'heure UTC est affichéeconfigTzTime("CET-1CEST,M3.5.0,M10.5.0/3", ...)
L'heure est décalée du double du décalageSigne inversé dans la chaîne TZ ("CET+1")Écris "CET-1CEST,..." ou "<+03>-3"
Le mois a un de moins, l'année vaut 126tm_mon compte à partir de 0, tm_year à partir de 1900Utilise tm_mon + 1, tm_year + 1900 ou strftime
La tâche se déclenche plusieurs fois dans la même minutePas de protection contre la répétitionMémorise le jour d'exécution (tm_yday) et teste-le
La boucle reste bloquée 5 secondes à chaque tourSans heure valide, getLocalTime() attend 5000 ms par défautUtilise getLocalTime(&t, 0) dans loop()
Symboles bizarres dans le nom du jour sur l'OLEDLa police n'a pas d'accentsÉcris les jours en ASCII ou passe à la bibliothèque U8g2
Pour isoler un problème, lance d'abord le premier code, qui affiche seulement l'heure dans le moniteur série. Si l'heure y est juste, le problème vient de l'écran, du RTC ou de la logique de la tâche ; si elle est fausse aussi, regarde le Wi-Fi et le réglage du fuseau horaire.

ESP32 NTP est le moyen le moins cher et le plus simple d'ajouter une heure exacte à un projet : en quelques lignes de code, il récupère l'heure à la seconde près, gère tout seul le fuseau et l'heure d'été et, avec un DS3231 en plus, résiste aussi aux coupures de courant. Dans l'application BlueGrays, tu trouveras des projets d'heure réelle par ESP32 NTP, d'horloge numérique avec RTC DS1307, d'affichage météo sur OLED et d'enregistreur de température horodaté, pas à pas avec schéma et code.

Questions fréquentes

L'ESP32 peut-il garder l'heure sans internet ?

Oui, une fois qu'il l'a reçue par NTP : l'ESP32 continue de compter l'heure système avec son propre compteur, et la perte du Wi-Fi n'arrête pas l'horloge. En revanche, si le courant coupe ou si la carte redémarre, l'heure revient à 1970. Pour un lieu sans internet ou une horloge qui résiste aux coupures, ajoute un module RTC sur pile comme le DS3231.

Pourquoi l'heure NTP de mon ESP32 est-elle décalée d'une ou deux heures ?

Sans fuseau horaire, l'ESP32 affiche l'heure UTC, soit une heure de moins qu'en France en hiver et deux en été. Utilise configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"). Si le décalage vaut le double, le signe de ta chaîne TZ est inversé.

Comment gérer l'heure d'été sur l'ESP32 ?

Le plus simple est configTzTime avec un fuseau POSIX qui contient les règles de changement. Pour la France, la Belgique et la Suisse, c'est "CET-1CEST,M3.5.0,M10.5.0/3" ; l'ESP32 change alors tout seul le dernier dimanche de mars et d'octobre. configTime avec un décalage d'été fixe ne connaît pas ces dates.

À quelle fréquence l'ESP32 se synchronise-t-il avec NTP ?

Dans le cœur Arduino ESP32, SNTP interroge de nouveau le serveur toutes les 3 heures par défaut et garde l'heure avec son propre compteur entre deux. Tu peux changer l'intervalle en millisecondes avec sntp_set_sync_interval(), mais le protocole impose au moins 15 secondes.

Faut-il un module RTC avec l'ESP32 ?

Pas si l'appareil est toujours alimenté et accède au Wi-Fi ; NTP donne une heure plus précise. Si tu as besoin de la bonne heure après une coupure avant le retour d'internet, ou si l'appareil fonctionne sans internet, ajoute un module RTC sur pile comme le DS3231.

Comment obtenir l'heure NTP sur le Raspberry Pi Pico W ?

En MicroPython, après la connexion Wi-Fi, il suffit de import ntptime puis ntptime.settime() ; cela règle le RTC interne du Pico sur UTC. Pour l'heure française, utilise time.localtime(time.time() + 3600) en hiver et + 7200 en été. ntptime ne resynchronise pas tout seul, donc relance settime() une ou deux fois par jour.

Voir tous les projets
ESP32 : heure réelle depuis Internet via NTP
#128Avancé
Premium

ESP32 : heure réelle depuis Internet via NTP

L'ESP32 se connecte au Wi-Fi et récupère l'heure réelle avec une précision de la milliseconde depuis des serveurs NTP (Network Time Protocol). Grâce à configTzTime(), le fuseau horaire de la Turquie (TRT, UTC+3) est appliqué automatiquement et le passage DST est géré via la chaîne POSIX TZ. L'heure est affichée sur le moniteur série et sur un écran LCD I2C en option. configTzTime() démarre le service SNTP ; des resynchronisations périodiques ont lieu tant que l'accès au réseau est disponible

4.5(13)
21
Horloge numérique avec ESP32 et RTC DS1307 (I2C + LCD)
#120Intermédiaire
Premium

Horloge numérique avec ESP32 et RTC DS1307 (I2C + LCD)

Dans ce projet, nous affichons l'heure et la date sur un écran LCD 16x2 I2C à l'aide du module d'horloge en temps réel (RTC) DS1307. Le DS1307 est une RTC avec batterie de secours qui conserve l'heure même si l'ESP32 s'éteint. Les deux modules partagent la même ligne I2C (SDA/SCL), ce qui nous permet d'apprendre à contrôler plusieurs appareils avec seulement deux fils de signal. Vous decouvrirez ainsi le protocole I2C et le fonctionnement des modules RTC en pratique.

4.6(26)
40
Connexion Wi-Fi STA ESP32 (avec écran LCD)
#127Avancé

Connexion Wi-Fi STA ESP32 (avec écran LCD)

Dans ce projet, nous connectons l'ESP32 au réseau Wi-Fi domestique en mode Station (STA) de manière sécurisée et tolérante aux pannes. Nous isolons SSID/mot de passe dans un fichier secrets.h pour empêcher leur fuite sur Git, mettons en place une reconnexion automatique, et affichons le niveau RSSI et l'adresse IP sur un écran LCD I2C. La LED intégrée sert d'indicateur d'état.

4.4(21)
34
ESP32 HTTPClient + API météo (avec écran OLED SSD1306)
#134Avancé
Premium

ESP32 HTTPClient + API météo (avec écran OLED SSD1306)

Dans ce projet, nous effectuons un appel à l'API REST de la météo (wttr.in) via Internet à l'aide de l'ESP32 et de la bibliothèque HTTPClient.h. Contrairement à la version précédente avec écran LCD I2C, nous utilisons ici un écran OLED SSD1306 (128×64) — ce qui permet d’afficher une police de grande taille, des icônes vectorielles et une présentation visuelle riche sur un seul écran. Sept icônes vectorielles différentes représentant les conditions météorologiques (ensoleillé, partiellement nua

4.3(10)
19
ESP32 HTTPClient + API météo (avec écran LCD)
#133Avancé
Premium

ESP32 HTTPClient + API météo (avec écran LCD)

Dans ce projet, nous effectuons un appel REST à l'API météo (wttr.in) via Internet à l'aide de l'ESP32 et de la bibliothèque HTTPClient.h. À l’aide d’ArduinoJson v7, nous analysons la réponse JSON renvoyée et affichons les informations relatives à la ville, à la température, à l’humidité, à la météo et au vent sur un écran LCD I2C, en turc et en anglais. wttr.in est gratuit et ne nécessite pas de clé API : il suffit de saisir le nom d’une ville pour obtenir les conditions météo en temps réel d

4.7(13)
20
Mode veille profonde de l'ESP32 — Économie de batterie (OLED + compteur RAM RTC)
#135Avancé
Premium

Mode veille profonde de l'ESP32 — Économie de batterie (OLED + compteur RAM RTC)

Le mode « Deep Sleep » de l'ESP32 réduit la consommation de courant à environ 10 µA en désactivant le processeur et la plupart des périphériques, ce qui revêt une importance cruciale pour les appareils IoT fonctionnant sur batterie. Dans ce projet, nous utilisons trois sources de réveil différentes : une minuterie (timer, 15 s), une broche externe (EXT0 — bouton GPIO 33) et un capteur tactile (T0/GPIO 4). Les variables marquées avec RTC_DATA_ATTR sont conservées pendant le mode Deep Sleep — le

4.3(10)
16
Enregistreur de Température ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avancé
Premium

Enregistreur de Température ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Construis un enregistreur de température horodaté avec ESP32 ! Lis la température/humidité du DHT22 chaque minute, obtiens l'heure réelle du DS1307 RTC et enregistre sur carte microSD au format CSV. L'OLED affiche en direct la température (grande police), le pourcentage d'humidité, l'horodatage et le compteur. Le fichier /temp_log.csv sur la carte SD s'ouvre dans Excel, Google Sheets ou Python pandas pour analyse — parfait pour surveillance de serre, suivi de température à domicile, c

4.7(7)
12
Écran LCD I2C ESP32 (16x2)
#119Intermédiaire
Premium

Écran LCD I2C ESP32 (16x2)

Dans ce projet, nous affichons du texte en connectant un écran LCD 16x2 à l'ESP32 via le protocole I2C. Grâce à l'I2C, nous pouvons contrôler l'écran avec seulement 2 broches de données (SDA et SCL) ; cela évite le gaspillage de 6 broches ou plus inhérent à la connexion parallèle classique. Le code affiche un message fixe sur la première ligne et un compteur qui augmente chaque seconde sur la deuxième ligne. De plus, au démarrage, nous lançons un scanner I2C pour afficher l'adresse de l'écran su

4.9(23)
40