Guías · 15 min de lectura

ESP32 NTP: obtener la hora de internet con zona horaria y OLED

Cómo obtener la hora NTP en el ESP32: configTzTime y zona horaria, getLocalTime, strftime, reloj en OLED, respaldo con DS3231, tarea diaria y ntptime en Pico W.

Obtener la hora con ESP32 NTP significa conectar la placa a servidores de tiempo en internet y conocer la fecha y la hora con una precisión de unas decenas de milisegundos, sin ningún módulo de reloj. Cuando el ESP32 está conectado al Wi-Fi, una sola llamada a configTzTime() obtiene la hora, aplica tu zona horaria y después la placa sigue contando por sí misma. Relojes de escritorio, riego programado, marcas de tiempo para registrar datos, una alarma por la mañana o luces que se encienden a una hora fija ya no necesitan un módulo RTC aparte.

En esta guía veremos paso a paso cómo funciona NTP, cómo configurar la zona horaria correcta (España, México, Argentina y otras), cómo escribir la fecha en cualquier formato con getLocalTime y strftime, cómo mostrar la hora en una pantalla OLED, cómo conservarla con un módulo DS3231 cuando cae el Wi-Fi o se va la luz y cómo ejecutar una tarea a una hora concreta cada día. En la última sección haremos lo mismo en la Raspberry Pi Pico W con el módulo ntptime de MicroPython.

Placa de desarrollo ESP32
ESP32 DevKit: gracias a su Wi-Fi integrado obtiene la hora de los servidores NTP y mantiene fecha y hora sin un módulo de reloj aparte.

¿Qué es NTP y cómo obtiene la hora el ESP32?

NTP (Network Time Protocol) es un protocolo que se usa desde los años 80 para sincronizar los relojes de ordenadores y dispositivos a través de internet. Los servidores se organizan en capas llamadas estratos: los servidores de estrato 1 están conectados directamente a un reloj atómico o a GPS, los de estrato 2 toman la hora de ellos, y así sucesivamente. El ESP32 pide la hora con un pequeño paquete por el puerto UDP 123; el servidor responde con una marca de tiempo que contiene los segundos desde 1900 más una fracción de segundo. El ESP32 tiene en cuenta el tiempo de ida y vuelta del paquete y ajusta su propio reloj. Con una conexión doméstica la precisión suele estar entre 10 y 50 milisegundos, más que suficiente para un proyecto de reloj.

La pila de red lwIP del ESP32 usa SNTP, una versión simplificada de NTP. Una vez recibida la hora, el ESP32 la guarda como hora del sistema y sigue contando con un temporizador derivado de su cristal; no consulta internet cada segundo. En segundo plano vuelve a preguntar al servidor a intervalos regulares (por defecto cada 3 horas en el núcleo Arduino ESP32) y corrige pequeñas derivas. La hora del sistema se guarda en UTC; la hora local que ves se calcula a partir de ella aplicando la regla de la zona horaria.

ConceptoValor / explicación
Protocolo y puertoSNTP, UDP 123
Marca de tiempoSegundos desde 1900 (el ESP32 los convierte a tiempo Unix desde 1970)
Precisión típica10–50 ms con una conexión doméstica
ResincronizaciónPor defecto 3 horas en Arduino ESP32, se cambia con sntp_set_sync_interval() (mínimo 15 s)
Máximo de servidores3 (los nombres pasados a configTzTime)
Tráfico de internetPetición y respuesta son paquetes NTP de 48 bytes; unos pocos kilobytes al día como mucho
Qué necesitasESP32, una red Wi-Fi de 2,4 GHz y conexión a internet

configTime y configTzTime: configurar la zona horaria

En el núcleo Arduino ESP32 hay dos formas de iniciar NTP. configTime(gmtOffset_sec, daylightOffset_sec, servidor1, servidor2, servidor3) recibe el desfase respecto a UTC y el desfase del horario de verano en segundos. Para Turquía, por ejemplo, escribes configTime(10800, 0, "pool.ntp.org"), porque 3 horas son 10 800 segundos y Turquía sigue en UTC+3 todo el año sin horario de verano desde 2016. Este método es sencillo, pero no conoce las fechas de cambio de hora de tu región; en España el cambio se haría el día equivocado, así que allí conviene configTzTime.

La forma correcta es configTzTime(TZ, servidor1, servidor2, servidor3). Esta función recibe una cadena de zona horaria en formato POSIX que también incluye las reglas del horario de verano, y el ESP32 hace el cambio cada año por sí mismo. Para la España peninsular la cadena es "CET-1CEST,M3.5.0,M10.5.0/3": CET es la abreviatura del horario de invierno, -1 significa que hay que restar una hora a la hora local para obtener UTC, CEST es el horario de verano, M3.5.0 significa «último domingo de marzo» y M10.5.0/3 «último domingo de octubre a las 3:00». Los ejemplos de abajo usan la zona de Turquía "<+03>-3"; sustituye TZ_INFO por el valor de la tabla.

RegiónCadena TZ (configTzTime)Equivalente con configTime
Turquía (UTC+3 todo el año)<+03>-3configTime(10800, 0, ...)
España peninsular, Francia, ItaliaCET-1CEST,M3.5.0,M10.5.0/3No adecuado, usa configTzTime
Islas CanariasWET0WEST,M3.5.0/1,M10.5.0No adecuado, usa configTzTime
México (centro, sin horario de verano)CST6configTime(-21600, 0, ...)
Argentina (UTC-3)<-03>3configTime(-10800, 0, ...)
UTC (para marcas de tiempo)UTC0configTime(0, 0, ...)
El signo en una cadena de zona horaria POSIX va al revés: si para Argentina escribes "UTC-3" o para España "CET+1", el ESP32 lo interpreta en sentido contrario y el reloj sale desplazado 6 o 2 horas. Lo correcto es "<-03>3" y "CET-1...". Si tu reloj se desvía exactamente el doble de tu desfase, el problema casi siempre es el signo.

Qué necesitas y conexiones

Para ver la hora en el Monitor Serie basta con un ESP32 y una red Wi-Fi. Más adelante usamos una pantalla OLED SSD1306 de 0,96 pulgadas para mostrar la hora, un módulo RTC DS3231 para conservarla en los cortes de luz y un módulo de relé para el ejemplo de tarea programada. La OLED y el DS3231 comparten el mismo bus I2C, así que ambos van a los mismos dos pines. Puedes consultar los pines del ESP32 con la herramienta Pinout.

Pin del componenteESP32Raspberry Pi Pico WNota
OLED / DS3231 VCC3V33V3 (OUT)Ambos funcionan a 3,3 V
OLED / DS3231 GNDGNDGNDTierra común
OLED / DS3231 SDAGPIO 21GP4Línea de datos I2C
OLED / DS3231 SCLGPIO 22GP5Línea de reloj I2C
Módulo de relé INGPIO 26GP15Ejemplo de tarea programada
Módulo de relé VCC5V (VIN)VBUSLa mayoría de bobinas de relé necesitan 5 V
El ESP32 solo se conecta a redes Wi-Fi de 2,4 GHz. Si tu router usa el mismo nombre para 5 GHz y 2,4 GHz, o el nombre de la red tiene caracteres especiales como la ñ, la conexión puede fallar. Si la hora nunca llega, comprueba primero en el Monitor Serie que el Wi-Fi se ha conectado; NTP solo funciona después.

Paso a paso: tu primer código ESP32 NTP

  1. Asegúrate de tener instalado el paquete de placas ESP32 en el Arduino IDE y elige ESP32 Dev Module en Herramientas. Si no sabes cómo, mira el artículo Qué es el ESP32.
  2. No hace falta ninguna librería extra: WiFi.h y time.h vienen con el núcleo ESP32.
  3. Cambia WIFI_SSID y WIFI_PASS por los de tu red y TZ_INFO por tu zona horaria de la tabla.
  4. Sube el código y abre el Monitor Serie a 115200 baudios.
  5. Primero verás los puntos del Wi-Fi y, tras unos segundos, la fecha y la hora actualizándose cada segundo.
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
Línea¿Qué hace?
configTzTime(TZ_INFO, ...)Inicia el cliente SNTP y fija servidores y zona horaria; vuelve enseguida, la hora llega en segundo plano
getLocalTime(&t)Rellena una struct tm con la hora local; si aún no hay hora, espera hasta 5 segundos y devuelve false
Serial.println(&t, "...")La clase Print del ESP32 puede imprimir una struct tm directamente con un formato de strftime
time(nullptr)Devuelve los segundos desde 1970 (tiempo Unix, UTC)

Hay un detalle importante: getLocalTime() decide si la hora es «válida» mirando el año. Al arrancar, la hora del sistema es el 1 de enero de 1970 y la función devuelve false hasta que el año es posterior a 2016. Así, el código que registra datos o ejecuta tareas antes de que llegue NTP no genera por error entradas con fecha de 1970, siempre que compruebes el valor devuelto. El segundo parámetro es el tiempo de espera en milisegundos; si no quieres bloquear loop(), escribe getLocalTime(&t, 0) para que lo intente una sola vez.

struct tm: las partes de la hora

getLocalTime() escribe la hora desglosada en una struct tm. Con estos campos comparas horas, días o meses al montar tareas programadas. Dos campos confunden a menudo a los principiantes: el mes empieza en 0 y el año se cuenta desde 1900.

CampoRangoEjemplo (lunes 16 de noviembre de 2026, 14:05:09)
tm_sec0–59 (60 en un segundo intercalar)9
tm_min0–595
tm_hour0–2314
tm_mday1–3116
tm_mon0–11 (0 = enero)10
tm_yearAños desde 1900126 (1900 + 126 = 2026)
tm_wday0–6 (0 = domingo)1
tm_yday0–365 (día del año, desde 0)319
Si la pantalla muestra «16/10/2026» pero estamos en noviembre, olvidaste sumar 1 a tm_mon. Si ves el año 126, necesitas tm_year + 1900. Con strftime estas conversiones se hacen solas.

Formatear fecha y hora con strftime

strftime() escribe la hora de una struct tm en un array de caracteres según un patrón. Los códigos con signo de porcentaje se sustituyen por el valor correspondiente y el resto de caracteres se quedan igual. Reserva el array con el tamaño de la salida más larga más uno (para el cero final): «14:05:09» tiene 8 caracteres, así que el array debe tener al menos 9 bytes. Los códigos más usados:

CódigoSignificadoEjemplo de salida
%H / %M / %SHora (00–23) / minuto / segundo14 / 05 / 09
%I y %pHora en formato de 12 horas y AM/PM02 PM
%d / %m / %YDía / mes (01–12) / año de cuatro cifras16 / 11 / 2026
%yAño de dos cifras26
%A / %aDía de la semana largo / corto (en inglés)Monday / Mon
%B / %bMes largo / corto (en inglés)November / Nov
%jDía del año (001–366)320
%F / %TAtajos de %Y-%m-%d / %H:%M:%S2026-11-16 / 14:05:09
%ZAbreviatura de la zona horaria+03 (o CET / CEST)

La librería C del ESP32 solo conoce los nombres de días y meses en inglés; %A siempre escribe «Monday». Para una fecha en español guardas los nombres en tus propios arrays y usas tm_wday y tm_mon como índice. El ejemplo de abajo lo hace con nombres en turco; cámbialos por «domingo, lunes, …» y «enero, febrero, …». Como el Monitor Serie admite UTF-8, las tildes se ven bien allí. El código genera una fecha legible y el formato ISO ordenable (2026-11-16 14:05:09) para registros.

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
Al registrar datos (tarjeta SD, Google Sheets, un servidor), escribe siempre la fecha como %Y-%m-%d %H:%M:%S o directamente como tiempo Unix. Ordenado alfabéticamente, este formato también queda ordenado cronológicamente, y Excel y las bases de datos lo reconocen como fecha. En pantalla puedes seguir usando el formato local.

Mostrar la hora y la fecha en una pantalla OLED

Para convertir la salida del Monitor Serie en un reloj de escritorio de verdad, la pantalla OLED SSD1306 de 0,96 pulgadas es ideal: es barata, se conecta con dos cables y muestra dígitos grandes muy nítidos. El código de abajo muestra arriba el día de la semana y la fecha, en el centro la hora en letra grande y abajo el estado del Wi-Fi. Como solo redibuja cuando cambia el segundo, y no en cada vuelta del bucle, no ocupa el bus I2C sin motivo. Instala las librerías Adafruit SSD1306 y Adafruit GFX desde el Gestor de Librerías; la pantalla en sí se explica en el artículo Pantalla 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

Fíjate en que los nombres de los días van sin caracteres especiales: la fuente por defecto de Adafruit GFX no tiene letras como á, é o ñ y en su lugar muestra símbolos sin sentido. Con tamaño de texto 2 cada carácter mide 12 píxeles de ancho; «14:05:09», de 8 caracteres, ocupa 96 píxeles, y empezando en el píxel 16 queda exactamente centrado en la pantalla de 128 píxeles. Si prefieres una LCD de caracteres 16x2, imprime la misma salida de strftime con lcd.print(); la conexión se explica en el artículo Pantalla LCD I2C.

¿Qué pasa con la hora si cae el Wi-Fi? Respaldo RTC con DS3231

La pregunta más frecuente es: si se va el Wi-Fi, ¿se va también la hora? No. Una vez que el ESP32 tiene la hora por NTP, sigue contando con su propio temporizador; perder el Wi-Fi no detiene el reloj. Como este temporizador funciona con el cristal de 40 MHz, deriva más o menos uno o dos segundos al día, y la siguiente sincronización lo corrige cuando vuelve el Wi-Fi. El verdadero problema es un corte de luz o un reinicio con el botón EN: entonces la hora del sistema vuelve a 1970 y, sin Wi-Fi, el ESP32 no tiene forma de saber la hora.

Situación¿Se conserva la hora?Qué hacer
El Wi-Fi cae durante un ratoSí, el ESP32 sigue contandoNada; NTP la corrige al volver el Wi-Fi
Deep sleep (sueño profundo)Sí, el temporizador RTC sigue contandoSi duerme mucho, sincroniza con NTP en algunos despertares
Corte de luz o botón ENNo, el reloj vuelve a 1970Añade un módulo RTC con pila como el DS3231
Sin internet (campo, almacén)NTP nunca llegaToma la hora solo de un módulo RTC

Durante el sueño profundo la hora no la cuenta el cristal principal, sino un oscilador RC interno menos preciso. Este oscilador se ve afectado por la temperatura, así que en sueños largos el reloj puede desviarse unos segundos o más; en proyectos a pilas que duermen horas, es buena costumbre sincronizar con NTP cada pocos despertares. Los detalles están en el artículo ESP32 Deep Sleep.

Módulo de reloj RTC
Módulo RTC con pila: sigue contando aunque el ESP32 se quede sin alimentación, así que puedes tomar la hora de él hasta que vuelva el Wi-Fi.

La mejor forma de tener un reloj que aguante los cortes de luz es combinar ambos: al arrancar, toma la hora del módulo DS3231 de inmediato, corrígela con NTP cuando haya Wi-Fi y actualiza el DS3231 en cada sincronización NTP. En el código de abajo el DS3231 guarda UTC en lugar de la hora local; así la regla de zona horaria vive en un solo sitio (la cadena TZ) y los cambios de horario de verano nunca estropean el RTC. Para detectar la sincronización NTP se registra una función de retorno con sntp_set_time_sync_notification_cb(). Como esa función se ejecuta dentro de la tarea de red, no se hace allí ningún trabajo I2C; solo levanta una bandera y loop() se encarga de escribir en el DS3231. El código usa la librería 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
El DS1307 funciona con el mismo código (escribe RTC_DS1307 y usa isrunning en lugar de lostPower), pero puede desviarse uno o dos minutos al mes. Gracias a su cristal compensado en temperatura, el DS3231 se mantiene en torno a un minuto al año. Las diferencias entre módulos y la trampa del circuito de carga de la pila están en el artículo Módulo RTC para Arduino.

Ejecutar una tarea a una hora fija cada día

El uso más habitual de la hora NTP es la tarea programada: encender la bomba de riego 10 minutos a las 07:30, las luces del jardín a las 19:00 o enviar una medida cada hora en punto. El método es sencillo: lee la hora en loop() y arranca la tarea cuando la hora y el minuto coinciden con el objetivo. La verdadera sutileza es evitar que la tarea se dispare cientos de veces dentro del mismo minuto. Para ello guardas en una variable el día en que se ejecutó (tm_yday), de modo que nunca se repite el mismo día. El código de abajo activa el relé del GPIO 26 durante 10 minutos cada mañana de lunes a viernes a las 07:30; como mide la duración con millis(), el código nunca se bloquea.

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
  • Comparar segundos: una condición como t.tm_sec == 0 se salta la tarea si el bucle pierde ese segundo (mientras el Wi-Fi se reconecta o durante un delay largo). Comprobar el minuto y el día es más seguro.
  • Comprobar que la hora es válida: no ejecutes tareas mientras getLocalTime() devuelva false, o tomarás decisiones equivocadas basadas en 1970 justo después de arrancar.
  • Tarea perdida: si la luz se va a las 07:29 y vuelve a las 07:35, la tarea no se ejecuta ese día. Si es importante, plantea la condición como «son más de las 07:30 y hoy no se ha ejecutado» y guarda sonGun en la memoria flash con Preferences.
  • Varias tareas: guarda hora, minuto y duración en un array y usa una variable de último día distinta para cada tarea.
  • Cargas de alta corriente: maneja la bomba o la lámpara mediante un módulo de relé o un MOSFET, nunca directamente desde un pin.
Los pines GPIO del ESP32 dan 3,3 V y solo pueden suministrar unas decenas de miliamperios. La entrada IN de un módulo de relé suele activarse con 3,3 V, pero algunas placas de relé de 5 V no conmutan de forma fiable; en ese caso usa una placa con disparo a 3,3 V o un driver con transistor. Si trabajas en el lado de 230 V, no toques el cableado con el circuito enchufado.

Cambiar el intervalo de sincronización y vigilar NTP

El intervalo por defecto de 3 horas sirve para la mayoría de proyectos. Para sincronizar más a menudo (por ejemplo en exteriores con grandes cambios de temperatura) o para reducir el tráfico, pasa el nuevo intervalo en milisegundos a sntp_set_sync_interval(). Llámala antes de configTzTime(), porque el nuevo intervalo se aplica en el siguiente arranque o cuando vence el intervalo actual. Por las reglas del protocolo, no puede ser menor de 15 segundos. sntp_get_sync_status() devuelve SNTP_SYNC_STATUS_COMPLETED una vez tras completar una sincronización y luego se reinicia, así puedes detectar cada nueva sincronización.

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

¿Qué servidor NTP usar?

La opción más común es pool.ntp.org: un conjunto de miles de servidores gestionados por voluntarios de todo el mundo que en cada petición te da la dirección de uno cercano. Los subdominios por país (es.pool.ntp.org, mx.pool.ntp.org, ar.pool.ntp.org) acercan aún más el servidor. configTzTime() admite hasta tres nombres de servidor; si el primero no responde, pasa al siguiente.

ServidorCaracterísticaNota
pool.ntp.orgPool de voluntarios, servidor más cercanoPrimera opción para uso general
es.pool.ntp.org / europe.pool.ntp.orgPool por país o continenteLatencia algo menor
time.cloudflare.comInfraestructura de una gran empresaBuena como servidor de respaldo
time.google.comReparte los segundos intercalares (leap smear)No lo mezcles con otros servidores
La IP de tu routerAlgunos routers ofrecen NTP localÚtil en una red local sin internet
Si vas a distribuir tu propio producto en cientos de dispositivos, lo correcto es pedir un pool con tu propio nombre (vendor zone) en lugar de usar pool.ntp.org, o repartir al azar la hora de arranque de los dispositivos. Miles de dispositivos consultando en el mismo segundo sobrecargan los servidores de los voluntarios.

NTP en la Raspberry Pi Pico W: ntptime

En la Raspberry Pi Pico W (y la Pico 2 W) el mismo trabajo lo hace el módulo ntptime de MicroPython. ntptime.settime() consulta al servidor NTP y pone el RTC interno de la Pico en UTC. MicroPython no tiene cadenas de zona horaria como el ESP32; para la hora local sumas el desfase en segundos a UTC (3 × 3600 para Turquía en el ejemplo, 3600 para España en invierno y 7200 en verano, -10800 para Argentina). ntptime solo obtiene la hora una vez y no resincroniza automáticamente; en proyectos que funcionan mucho tiempo, llamar a settime() una o dos veces al día elimina la deriva. El RTC de la Pico W también se reinicia cuando se corta la alimentación, así que el respaldo con DS3231 vale también aquí. La conexión de la Pico W al Wi-Fi se explica en el artículo Pico W Wi-Fi y servidor 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() devuelve una tupla de ocho elementos: año, mes (1–12), día, hora, minuto, segundo, día de la semana y día del año. Tiene dos diferencias con la struct tm del ESP32: el mes empieza en 1 y el día de la semana cuenta 0 = lunes. ntptime.settime() lanza OSError cuando la red agota el tiempo de espera; por eso el código tiene un bucle de cinco intentos.

Ideas de proyectos con el reloj ESP32 NTP

  • Reloj de escritorio Wi-Fi: muestra la hora, la fecha y la temperatura de la habitación en una pantalla OLED y baja el brillo por la noche.
  • Reloj LED gigante: muestra la hora NTP como texto desplazable en una matriz LED MAX7219.
  • Riego programado: cada mañana mide la humedad del suelo y, si hace falta, enciende una mini bomba de agua durante un tiempo fijo.
  • Registro de datos con marca de tiempo: guarda las lecturas de los sensores con fecha ISO en una tarjeta SD.
  • Reloj con el tiempo: añade junto a la hora el tiempo obtenido de internet; el artículo ESP32 tiempo meteorológico continúa desde aquí.
  • Informe matinal: cada día a las 08:00 envía a tu móvil por Telegram la temperatura de casa y los movimientos detectados durante la noche.

Errores frecuentes y soluciones

SíntomaCausa probableSolución
La hora sale en 1970 o nunca llegaEl Wi-Fi no está conectado o el puerto NTP (UDP 123) está bloqueadoComprueba primero WiFi.status(); en redes de colegio u oficina prueba otra red o el punto de acceso del móvil
La hora se desvía exactamente tu desfaseNo hay zona horaria, se muestra UTCconfigTzTime con la cadena de tu zona, p. ej. "CET-1CEST,M3.5.0,M10.5.0/3"
La hora se desvía el doble del desfaseSigno invertido en la cadena TZ ("CET+1", "UTC-3")Escribe "CET-1CEST,..." o "<-03>3"
El mes sale uno menos, el año 126tm_mon cuenta desde 0, tm_year desde 1900Usa tm_mon + 1, tm_year + 1900 o strftime
La tarea se dispara varias veces en el mismo minutoNo hay protección contra repeticiónGuarda el día en que se ejecutó (tm_yday) y compruébalo
El bucle se queda 5 segundos parado en cada vueltaSin hora válida, getLocalTime() espera 5000 ms por defectoUsa getLocalTime(&t, 0) dentro de loop()
Símbolos raros en el nombre del día en la OLEDLa fuente no tiene tildes ni ñEscribe los días en ASCII o pasa a la librería U8g2
Para aislar un problema, ejecuta primero el primer código, que solo escribe la hora en el Monitor Serie. Si ahí la hora es correcta, el problema está en la pantalla, el RTC o la lógica de la tarea; si también es incorrecta, revisa el Wi-Fi y la zona horaria.

ESP32 NTP es la forma más barata y cómoda de añadir una hora exacta a un proyecto: con pocas líneas de código obtiene la hora al segundo, gestiona por sí mismo la zona horaria y el horario de verano y, con un DS3231, aguanta también los cortes de luz. En la app BlueGrays encontrarás proyectos de hora real con ESP32 NTP, un reloj digital con RTC DS1307, una pantalla del tiempo en OLED y un registrador de temperatura con marca de tiempo, paso a paso con esquema y código.

Preguntas frecuentes

¿El ESP32 puede mantener la hora sin internet?

Sí, una vez que la ha recibido por NTP: el ESP32 sigue contando la hora del sistema con su propio temporizador y perder el Wi-Fi no detiene el reloj. Pero si se va la luz o la placa se reinicia, la hora vuelve a 1970. En lugares sin internet, o para un reloj que aguante cortes de luz, añade un módulo RTC con pila como el DS3231.

¿Por qué la hora NTP del ESP32 sale con una o dos horas de diferencia?

Sin zona horaria, el ESP32 muestra UTC, así que en España sale una hora menos en invierno y dos en verano. Usa configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"). Si la diferencia es el doble de tu desfase, el signo de la cadena TZ está invertido.

¿Cómo configuro el horario de verano en el ESP32?

Lo más sencillo es configTzTime con una zona POSIX que incluya las reglas de cambio. Para la España peninsular es "CET-1CEST,M3.5.0,M10.5.0/3"; el ESP32 cambia solo el último domingo de marzo y de octubre. configTime con un desfase de verano fijo no conoce esas fechas.

¿Cada cuánto se sincroniza el ESP32 con NTP?

En el núcleo Arduino ESP32, SNTP vuelve a consultar al servidor cada 3 horas por defecto y mantiene la hora con su propio temporizador entre medias. Puedes cambiar el intervalo en milisegundos con sntp_set_sync_interval(), aunque por protocolo no puede ser inferior a 15 segundos.

¿Necesito un módulo RTC con el ESP32?

No, si el dispositivo está siempre alimentado y llega al Wi-Fi; NTP da una hora más exacta. Si necesitas la hora correcta tras un corte de luz antes de que vuelva internet, o el dispositivo trabajará sin internet, añade un módulo RTC con pila como el DS3231.

¿Cómo obtengo la hora NTP en la Raspberry Pi Pico W?

En MicroPython, tras conectar al Wi-Fi, basta con import ntptime y ntptime.settime(); esto pone el RTC interno de la Pico en UTC. Para la hora local suma tu desfase en segundos, por ejemplo time.localtime(time.time() - 10800) para Argentina. ntptime no resincroniza solo, así que repite settime() una o dos veces al día.

Ver todos los proyectos
ESP32: hora real desde Internet con NTP
#128Avanzado
Premium

ESP32: hora real desde Internet con NTP

El ESP32 se conecta a Wi-Fi y obtiene la hora real con precisión de milisegundos desde servidores NTP (Network Time Protocol). Gracias a configTzTime(), se aplica automáticamente la zona horaria de Turquía (TRT, UTC+3) y el cambio DST se gestiona mediante la cadena POSIX TZ. La hora se muestra en el monitor serie y en una pantalla LCD I2C opcional. configTzTime() inicia el servicio SNTP; se realizan resincronizaciones periódicas mientras hay acceso a la red, y el reloj del sistema sigue avan

4.5(13)
21
Reloj digital con ESP32 y RTC DS1307 (I2C + LCD)
#120Intermedio
Premium

Reloj digital con ESP32 y RTC DS1307 (I2C + LCD)

En este proyecto, mostramos la hora y la fecha en una pantalla LCD I2C de 16x2 mediante el módulo de reloj en tiempo real (RTC) DS1307. El DS1307 es un RTC con batería de respaldo que conserva la hora incluso si el ESP32 se apaga. Ambos módulos comparten la misma línea I2C (SDA/SCL), por lo que aprendemos a controlar varios dispositivos con solo dos cables de señal. Con este proyecto conocerás el protocolo I2C, el funcionamiento de los módulos RTC y la visualización dinámica en pantallas LCD.

4.6(26)
40
Conexión Wi-Fi STA con ESP32 (con pantalla LCD)
#127Avanzado

Conexión Wi-Fi STA con ESP32 (con pantalla LCD)

En este proyecto, conectamos el ESP32 a la red Wi-Fi doméstica en modo Station (STA) de forma segura y tolerante a fallos. Separamos el SSID y la contraseña en el archivo secrets.h para evitar que se filtren a Git, configuramos un mecanismo de reconexión automática y mostramos el nivel de señal RSSI y la dirección IP en una pantalla LCD I2C. El LED interno actúa como indicador de estado.

4.4(21)
34
ESP32 HTTPClient + API del tiempo (con pantalla OLED SSD1306)
#134Avanzado
Premium

ESP32 HTTPClient + API del tiempo (con pantalla OLED SSD1306)

En este proyecto, utilizamos el ESP32 para realizar una llamada a la API REST de la información meteorológica (wttr.in) a través de Internet, utilizando HTTPClient.h. A diferencia de la versión anterior con pantalla LCD I2C, aquí utilizamos una pantalla OLED SSD1306 (128×64), lo que permite utilizar fuentes grandes, iconos vectoriales y una presentación visual muy rica en una sola pantalla. Para indicar el tiempo, se dibujan en tiempo de ejecución 7 iconos vectoriales diferentes (soleado, parc

4.3(10)
19
ESP32 HTTPClient + API del tiempo (con pantalla LCD)
#133Avanzado
Premium

ESP32 HTTPClient + API del tiempo (con pantalla LCD)

En este proyecto, utilizamos el ESP32 para realizar una llamada a la API REST de la página web de información meteorológica (wttr.in) a través de Internet, utilizando HTTPClient.h. Analizamos la respuesta JSON devuelta con ArduinoJson v7 y mostramos la información sobre la ciudad, la temperatura, la humedad, el tiempo y el viento en una pantalla LCD I2C, tanto en turco como en inglés. wttr.in es gratuito y no requiere clave API: basta con escribir el nombre de cualquier ciudad para obtener su

4.7(13)
20
Modo de suspensión profunda del ESP32 — Ahorro de batería (OLED + contador en RTC RAM)
#135Avanzado
Premium

Modo de suspensión profunda del ESP32 — Ahorro de batería (OLED + contador en RTC RAM)

El modo «Deep Sleep» del ESP32 reduce el consumo de corriente a unos 10 µA al desactivar la CPU y la mayoría de los periféricos, lo cual es de vital importancia para los dispositivos IoT que funcionan con pilas. En este proyecto utilizamos tres fuentes de activación diferentes: el temporizador (15 s), un pin externo (EXT0 — botón GPIO 33) y el sensor táctil (T0/GPIO 4). Las variables marcadas con RTC_DATA_ATTR se conservan durante el modo Deep Sleep: el contador de arranque, el contador de act

4.3(10)
16
Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avanzado
Premium

Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

¡Construye un registrador de temperatura con marca de tiempo con ESP32! Lee temperatura/humedad del DHT22 cada minuto, obtén la hora real del DS1307 RTC y registra en tarjeta microSD como CSV. El OLED muestra en vivo la temperatura (fuente grande), porcentaje de humedad, marca de tiempo y contador de registros. El archivo /temp_log.csv en la tarjeta SD se abre en Excel, Google Sheets o Python pandas para análisis — perfecto para monitoreo de invernadero, seguimiento de temperatura del hog

4.7(7)
12
Pantalla LCD I2C para ESP32 (16x2)
#119Intermedio
Premium

Pantalla LCD I2C para ESP32 (16x2)

En este proyecto, mostramos texto conectando una pantalla LCD de 16x2 caracteres al ESP32 a través del protocolo I2C. Gracias al I2C, podemos controlar la pantalla con solo 2 pines de datos (SDA y SCL); así se elimina el desperdicio de pines que supone la conexión paralela clásica, que requiere más de 6 pines. El código imprime un mensaje fijo en la primera línea y un contador que aumenta cada segundo en la segunda línea. Además, al inicio ejecutamos el escáner I2C para mostrar la dirección de l

4.9(23)
40