Hora NTP no ESP32: relógio pela internet com fuso horário, OLED e RTC de reserva
Como obter a hora NTP no ESP32: configTzTime e fusos horários, getLocalTime, strftime, relógio OLED, DS3231 de reserva, tarefa diária agendada e ntptime no Pico W.
Obter a hora com NTP no ESP32 significa conectar a placa a servidores de hora na internet e obter a data e a hora com precisão de algumas dezenas de milissegundos, sem nenhum módulo de relógio. Depois que o ESP32 se conecta ao Wi-Fi, uma única chamada a configTzTime() obtém a hora, aplica seu fuso horário e a placa continua contando por conta própria. Relógios de mesa, irrigação programada, marcações de data e hora para registro de dados, um alarme matinal ou luzes que acendem em um horário definido deixam de precisar de um módulo RTC separado.
Neste guia, vamos ver passo a passo como o NTP funciona, configurar o fuso horário correto, escrever a data em qualquer formato com getLocalTime e strftime, mostrar a hora em um display OLED, manter a hora com um módulo DS3231 quando o Wi-Fi cai ou falta energia e executar uma tarefa em um horário específico todos os dias. Na última seção, faremos o mesmo no Raspberry Pi Pico W com o módulo ntptime do MicroPython.

O que é NTP e como o ESP32 obtém a hora?
NTP (Network Time Protocol) é um protocolo usado desde a década de 1980 para sincronizar os relógios de computadores e dispositivos pela internet. Os servidores são organizados em camadas chamadas estratos: os servidores de estrato 1 estão conectados diretamente a um relógio atômico ou GPS, os de estrato 2 obtêm a hora deles, e assim por diante. O ESP32 solicita a hora com um pequeno pacote pela porta UDP 123; o servidor responde com uma marcação de data e hora que contém os segundos desde 1900 e uma fração de segundo. O ESP32 considera o tempo de ida e volta do pacote e ajusta seu próprio relógio. Em uma conexão doméstica, a precisão obtida assim costuma ficar entre 10 e 50 milissegundos, mais do que suficiente para um projeto de relógio.
A pilha de rede lwIP do ESP32 usa SNTP, uma versão simplificada do NTP. Depois de receber a hora, o ESP32 a mantém como hora do sistema e continua contando com um temporizador derivado de seu cristal; ele não acessa a internet a cada segundo. Em segundo plano, consulta o servidor novamente em intervalos regulares (por padrão, a cada 3 horas no core Arduino ESP32) e corrige pequenos desvios. A hora do sistema é mantida em UTC; a hora local que você vê é calculada a partir dela, aplicando a regra do fuso horário.
| Conceito | Valor / explicação |
|---|---|
| Protocolo e porta | SNTP, UDP 123 |
| Marcação de data e hora | Segundos desde 1900 (o ESP32 converte isso para o tempo Unix, baseado em 1970) |
| Precisão típica | 10–50 ms em uma conexão doméstica |
| Ressincronização | Padrão de 3 horas no Arduino ESP32, alterado com sntp_set_sync_interval() (mínimo de 15 s) |
| Máximo de servidores | 3 (os nomes de servidores passados a configTzTime) |
| Tráfego de internet | A solicitação e a resposta são pacotes NTP de 48 bytes cada; no máximo alguns quilobytes por dia |
| O que você precisa | ESP32, uma rede Wi-Fi de 2,4 GHz e uma conexão com a internet |
configTime e configTzTime: configurando o fuso horário
Há duas formas de iniciar o NTP no core Arduino ESP32. configTime(gmtOffset_sec, daylightOffset_sec, server1, server2, server3) recebe a diferença em relação ao UTC e o ajuste do horário de verão em segundos. Para a Turquia, por exemplo, você escreve configTime(10800, 0, "pool.ntp.org"), porque 3 horas equivalem a 10.800 segundos e a Turquia permanece em UTC+3 o ano todo, sem horário de verão, desde 2016. Esse método é simples, mas não conhece as datas do horário de verão da sua região; em lugares que o adotam, como a Europa ou os EUA, a mudança ocorre no dia errado, então use configTzTime nesses casos.
A melhor forma é configTzTime(TZ, server1, server2, server3). Essa função recebe uma string de fuso horário no formato POSIX, que também contém as regras do horário de verão, e o ESP32 faz a mudança sozinho a cada ano. Para a Turquia, a string é "<+03>-3": o +03 entre os sinais de menor e maior é a abreviação exibida na tela, e -3 significa que você subtrai 3 horas da hora local para chegar ao UTC. A tabela abaixo lista as strings de regiões comuns.
| Região | String TZ (configTzTime) | Equivalente com configTime |
|---|---|---|
| Turquia (UTC+3 o ano todo) | <+03>-3 | configTime(10800, 0, ...) |
| Alemanha, França, Itália, Espanha | CET-1CEST,M3.5.0,M10.5.0/3 | Não é adequado, use configTzTime |
| Reino Unido, Portugal | GMT0BST,M3.5.0/1,M10.5.0 | Não é adequado, use configTzTime |
| Leste dos EUA (Nova York) | EST5EDT,M3.2.0,M11.1.0 | Não é adequado, use configTzTime |
| Azerbaijão, Geórgia (UTC+4) | <+04>-4 | configTime(14400, 0, ...) |
| UTC (para marcações de data e hora) | UTC0 | configTime(0, 0, ...) |
Componentes necessários e ligações
Para ver a hora no Monitor Serial, você só precisa de um ESP32 e uma rede Wi-Fi. Mais adiante no guia, usamos um display OLED SSD1306 de 0,96 polegada para mostrar a hora, um módulo RTC DS3231 para mantê-la durante quedas de energia e um módulo relé para o exemplo de tarefa agendada. O OLED e o DS3231 compartilham o mesmo barramento I2C, então ambos se conectam aos mesmos dois pinos. Você pode consultar a pinagem do ESP32 na ferramenta Pinagem.
| Pino do componente | ESP32 | Raspberry Pi Pico W | Observação |
|---|---|---|---|
| OLED / DS3231 VCC | 3V3 | 3V3 (OUT) | Ambos funcionam com 3,3 V |
| OLED / DS3231 GND | GND | GND | GND comum |
| OLED / DS3231 SDA | GPIO 21 | GP4 | Linha de dados I2C |
| OLED / DS3231 SCL | GPIO 22 | GP5 | Linha de clock I2C |
| IN do módulo relé | GPIO 26 | GP15 | Exemplo de tarefa agendada |
| VCC do módulo relé | 5V (VIN) | VBUS | A maioria das bobinas de relé precisa de 5 V |
Passo a passo: seu primeiro sketch NTP no ESP32
- Verifique se o pacote de placas ESP32 está instalado na Arduino IDE e selecione ESP32 Dev Module em Ferramentas. Se não souber como, veja o artigo O que é ESP32.
- Nenhuma biblioteca extra é necessária: WiFi.h e time.h vêm com o core do ESP32.
- Substitua os valores de WIFI_SSID e WIFI_PASS pelos da sua rede, e TZ_INFO pelo seu fuso horário da tabela acima.
- Carregue o sketch e abra o Monitor Serial a 115200 baud.
- Você deverá ver primeiro os pontos da conexão Wi-Fi e, depois de alguns segundos, a data e a hora sendo atualizadas a cada segundo.
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| Linha | O que faz? |
|---|---|
| configTzTime(TZ_INFO, ...) | Inicia o cliente SNTP e define os servidores e o fuso horário; retorna imediatamente, e a hora chega em segundo plano |
| getLocalTime(&t) | Preenche uma struct tm com a hora local; se ainda não houver hora, espera até 5 segundos e retorna false |
| Serial.println(&t, "...") | A classe Print do ESP32 pode imprimir uma struct tm diretamente com um formato strftime |
| time(nullptr) | Retorna os segundos desde 1970 (tempo Unix, UTC) |
Há um detalhe importante aqui: getLocalTime() decide se a hora é “válida” verificando o ano. Quando o ESP32 inicia, a hora do sistema é 1º de janeiro de 1970, e a função retorna false até que o ano seja posterior a 2016. Assim, o código que registra dados ou executa tarefas antes de o NTP chegar não produzirá acidentalmente registros datados de 1970, desde que você verifique o valor de retorno. O segundo parâmetro é o tempo limite em milissegundos; se não quiser bloquear a execução dentro de loop(), escreva getLocalTime(&t, 0) para que faça apenas uma tentativa.
struct tm: as partes da hora
getLocalTime() grava a hora em uma struct tm, dividida em partes. Você usa esses campos para comparar horas, dias ou meses ao criar tarefas agendadas. Dois campos costumam confundir iniciantes: o mês é contado a partir de 0 e o ano a partir de 1900.
| Campo | Intervalo | Exemplo (segunda-feira, 16 de novembro de 2026, 14:05:09) |
|---|---|---|
| tm_sec | 0–59 (60 em um segundo intercalar) | 9 |
| tm_min | 0–59 | 5 |
| tm_hour | 0–23 | 14 |
| tm_mday | 1–31 | 16 |
| tm_mon | 0–11 (0 = janeiro) | 10 |
| tm_year | Anos desde 1900 | 126 (1900 + 126 = 2026) |
| tm_wday | 0–6 (0 = domingo) | 1 |
| tm_yday | 0–365 (dia do ano, a partir de 0) | 319 |
Formatando data e hora com strftime
strftime() grava a hora de uma struct tm em um array de caracteres de acordo com um padrão. Os códigos com porcentagem no padrão são substituídos pelo valor correspondente, e todos os outros caracteres permanecem como estão. Dimensione o array para a maior saída mais um (para o zero terminador): “14:05:09” tem 8 caracteres, então o array deve ter pelo menos 9 bytes. Os códigos mais comuns:
| Código | Significado | Exemplo de saída |
|---|---|---|
| %H / %M / %S | Hora (00–23) / minuto / segundo | 14 / 05 / 09 |
| %I e %p | Relógio de 12 horas e AM/PM | 02 PM |
| %d / %m / %Y | Dia / mês (01–12) / ano com quatro dígitos | 16 / 11 / 2026 |
| %y | Ano com dois dígitos | 26 |
| %A / %a | Nome do dia da semana, completo / abreviado (inglês) | Monday / Mon |
| %B / %b | Nome do mês, completo / abreviado (inglês) | November / Nov |
| %j | Dia do ano (001–366) | 320 |
| %F / %T | Atalhos para %Y-%m-%d / %H:%M:%S | 2026-11-16 / 14:05:09 |
| %Z | Abreviação do fuso horário | +03 |
A biblioteca C do ESP32 só conhece os nomes dos dias e meses em inglês; %A sempre imprime “Monday”. Para datas em outro idioma, você mantém os nomes dos dias e meses em seus próprios arrays e usa tm_wday e tm_mon como índices dos arrays. O exemplo abaixo faz isso com nomes em turco; basta substituí-los pelos do seu idioma. Como o Monitor Serial aceita UTF-8, as letras acentuadas aparecem corretamente nele. O sketch produz tanto uma data legível quanto o formato ISO ordenável (2026-11-16 14:05:09) para registro de dados.
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}
34Mostrando a hora e a data em um display OLED
Para transformar a saída do Monitor Serial em um relógio de mesa de verdade, o display OLED SSD1306 de 0,96 polegada é ideal: é barato, conecta-se com dois fios e mostra dígitos grandes com nitidez. O sketch abaixo mostra o dia da semana e a data no topo, a hora em uma fonte grande no meio e o estado do Wi-Fi na parte inferior. Como só redesenha quando o segundo muda, e não a cada ciclo, ele não ocupa o barramento I2C sem necessidade. Instale as bibliotecas Adafruit SSD1306 e Adafruit GFX pelo Gerenciador de Bibliotecas; o display é abordado no artigo display OLED de 0,96 polegada (SSD1306).
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}
66Observe que os nomes dos dias são escritos sem letras acentuadas: a fonte padrão da Adafruit GFX não tem caracteres como ş, ç ou é e mostra símbolos sem sentido no lugar deles. No tamanho de texto 2, cada caractere tem 12 pixels de largura; “14:05:09”, com 8 caracteres, ocupa 96 pixels, então começar no pixel 16 o centraliza exatamente na tela de 128 pixels. Se preferir um LCD de 16x2 caracteres, basta imprimir a mesma saída de strftime com lcd.print(); as ligações são explicadas no artigo Display LCD I2C.
O que acontece com a hora quando o Wi-Fi cai? RTC DS3231 de reserva
A pergunta mais comum é: se o Wi-Fi cair, a hora também se perde? Não. Depois que o ESP32 obtém a hora pelo NTP, continua contando com seu próprio temporizador; perder o Wi-Fi não para o relógio. Como esse temporizador usa o cristal de 40 MHz, ele apresenta um desvio de cerca de um ou dois segundos por dia, corrigido na próxima sincronização quando o Wi-Fi volta. O problema real é uma queda de energia ou uma reinicialização pelo botão EN: nesse caso, a hora do sistema volta a 1970, e sem Wi-Fi o ESP32 não tem como saber a hora.
| Situação | A hora é mantida? | O que fazer |
|---|---|---|
| O Wi-Fi cai por um tempo | Sim, o ESP32 continua contando | Nada; o NTP corrige a hora quando o Wi-Fi volta |
| Deep sleep | Sim, o temporizador RTC continua contando | Em períodos longos de deep sleep, ressincronize com NTP em alguns despertares |
| Queda de energia ou botão EN | Não, o relógio volta a 1970 | Adicione um módulo RTC com bateria de reserva, como o DS3231 |
| Sem internet (campo, depósito) | O NTP nunca chega | Obtenha a hora apenas de um módulo RTC |
Durante o deep sleep, a hora é contada por um oscilador RC menos preciso dentro do chip, e não pelo cristal principal. Esse oscilador é afetado pela temperatura, então, em períodos longos de deep sleep, o relógio pode desviar alguns segundos ou mais; em projetos alimentados por bateria que dormem por horas, é uma boa prática sincronizar com NTP a cada poucos despertares. Os detalhes estão no artigo Deep sleep no ESP32.

A melhor forma de ter um relógio que resista a quedas de energia é combinar os dois: na inicialização, obtenha imediatamente a hora do módulo DS3231, corrija-a com NTP assim que o Wi-Fi estiver ativo e atualize o DS3231 a cada sincronização NTP. No sketch abaixo, o DS3231 armazena UTC em vez da hora local; assim, a regra do fuso horário fica em um único lugar (a string TZ), e as mudanças de horário de verão nunca corrompem o RTC. Para detectar a sincronização NTP, um callback é registrado com sntp_set_time_sync_notification_cb(). Como esse callback roda dentro da tarefa de rede, nenhuma operação I2C é feita nele; ele apenas ativa uma flag, e loop() cuida da gravação no DS3231. O sketch usa a biblioteca RTClib.
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}
53Executando uma tarefa em um horário definido todos os dias
O uso mais comum da hora NTP é uma tarefa agendada: ligar a bomba de irrigação por 10 minutos às 07:30, acender as luzes do jardim às 19:00, enviar uma leitura a cada hora cheia. O método é simples: ler a hora em loop() e iniciar a tarefa quando a hora e o minuto corresponderem ao horário desejado. O cuidado principal é impedir que a tarefa seja disparada centenas de vezes no mesmo minuto. Para isso, você armazena em uma variável o dia em que a tarefa rodou (tm_yday), para que ela nunca rode duas vezes no mesmo dia. O sketch abaixo liga o relé no GPIO 26 por 10 minutos todas as manhãs de segunda a sexta, às 07:30; como mede a duração com millis(), o código nunca bloqueia.
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- Comparação pelos segundos: uma condição como t.tm_sec == 0 pula a tarefa se o ciclo perder aquele segundo (durante a reconexão do Wi-Fi ou um delay longo). Verificar o minuto e o dia é mais seguro.
- Verificação de uma hora válida: não execute tarefas enquanto getLocalTime() retornar false, ou você tomará decisões erradas com base em 1970 logo após a inicialização.
- Tarefas perdidas: se a energia cair às 07:29 e voltar às 07:35, a tarefa não rodará naquele dia. Se isso for importante, escreva a condição como “já passou das 07:30 e ainda não rodou hoje” e salve sonGun na memória flash com Preferences.
- Várias tarefas: mantenha a hora, o minuto e a duração em um array e use uma variável separada para o último dia de execução de cada tarefa.
- Cargas de alta corrente: acione uma bomba ou lâmpada por um módulo relé ou um MOSFET, nunca diretamente por um pino.
Alterando o intervalo de sincronização e monitorando o NTP
O intervalo padrão de sincronização de 3 horas atende à maioria dos projetos. Para sincronizar com mais frequência (por exemplo, ao ar livre, com grandes variações de temperatura) ou reduzir o tráfego, passe o novo intervalo em milissegundos para sntp_set_sync_interval(). Chame essa função antes de configTzTime(), pois o novo intervalo entra em vigor na próxima inicialização ou quando o intervalo atual termina. Pelas regras do protocolo, o intervalo não pode ser menor que 15 segundos. sntp_get_sync_status() retorna SNTP_SYNC_STATUS_COMPLETED uma vez após uma sincronização terminar e depois se redefine, permitindo detectar cada nova sincronização.
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}
22Qual servidor NTP usar?
A escolha mais comum é pool.ntp.org: um conjunto de milhares de servidores mantidos por voluntários no mundo todo que fornece o endereço de um servidor próximo a você a cada solicitação. Os subdomínios de países (uk.pool.ntp.org, de.pool.ntp.org) aproximam ainda mais o servidor. configTzTime() aceita até três nomes de servidores; se o primeiro não responder, passa para o próximo.
| Servidor | Característica | Observação |
|---|---|---|
| pool.ntp.org (endereço de servidor NTP) | Conjunto de servidores voluntários, servidor mais próximo | Primeira escolha para uso geral |
| uk.pool.ntp.org / europe.pool.ntp.org (endereços de servidores NTP por país ou continente) | Conjunto de servidores de um país ou continente | Latência um pouco menor |
| time.cloudflare.com (servidor NTP da Cloudflare) | Infraestrutura de uma grande empresa | Bom como servidor de reserva |
| time.google.com (servidor NTP do Google) | Distribui os segundos intercalares ao longo do tempo (leap smear) | Não misture com outros servidores |
| Endereço IP do seu roteador | Alguns roteadores oferecem NTP local | Útil em uma rede local sem internet |
NTP no Raspberry Pi Pico W: ntptime
No Raspberry Pi Pico W (e no Pico 2 W), o mesmo trabalho é feito com o módulo ntptime do MicroPython. ntptime.settime() consulta o servidor NTP e ajusta o RTC interno do Pico para UTC. O MicroPython não tem uma string de fuso horário como o ESP32; para a hora local, você soma sua diferença em segundos ao UTC (3 × 3600 para UTC+3 no exemplo). ntptime obtém a hora apenas uma vez e não ressincroniza automaticamente; em projetos que ficam ligados por muito tempo, chamar settime() uma ou duas vezes por dia elimina o desvio. O RTC do Pico W também é zerado quando falta energia, então a mesma reserva com DS3231 se aplica aqui. A conexão do Pico W ao Wi-Fi é explicada em detalhes no artigo Wi-Fi e servidor web no Pico W.
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)
36No MicroPython, time.localtime() retorna uma tupla com oito elementos: ano, mês (1–12), dia, hora, minuto, segundo, dia da semana e dia do ano. Ela difere da struct tm do ESP32 em dois pontos: o mês começa em 1 e a contagem do dia da semana usa 0 = segunda-feira. ntptime.settime() gera OSError quando o tempo limite da rede é atingido, por isso o sketch tem um laço de cinco tentativas.
Ideias de projetos de relógio NTP com ESP32
- Relógio de mesa Wi-Fi: mostre a hora, a data e a temperatura ambiente em um único display OLED e reduza o brilho à noite.
- Relógio gigante de LEDs: mostre a hora NTP como texto rolante em uma matriz de LEDs MAX7219.
- Irrigação programada de plantas: verifique a umidade do solo todas as manhãs e acione uma minibomba de água por um tempo definido, se necessário.
- Registro de dados com data e hora: grave leituras de sensores com datas ISO em um cartão SD.
- Relógio com previsão do tempo: adicione ao lado da hora a previsão do tempo obtida da internet; o artigo sobre previsão do tempo com ESP32 continua a partir daqui.
- Relatório matinal: todos os dias às 08:00, envie para seu celular a temperatura da casa e os movimentos detectados durante a noite pelo Telegram.
Erros comuns e soluções
| Sintoma | Causa provável | Solução |
|---|---|---|
| A hora mostra 1970 ou nunca chega | O Wi-Fi não está conectado ou a porta NTP (UDP 123) está bloqueada | Verifique WiFi.status() primeiro; em redes de escolas e escritórios, tente outra rede ou um hotspot do celular |
| A hora difere exatamente pelo seu deslocamento em relação ao UTC | Nenhum fuso horário configurado, a hora é exibida em UTC | configTzTime("<+03>-3", ...) ou a string do seu fuso |
| A hora difere pelo dobro do deslocamento | Sinal invertido na string TZ ("UTC+3") | Use "<+03>-3" ou "UTC-3" para UTC+3 |
| O mês está uma unidade abaixo e o ano é 126 | tm_mon conta a partir de 0, tm_year a partir de 1900 | Use tm_mon + 1, tm_year + 1900 ou strftime |
| A tarefa dispara várias vezes no mesmo minuto | Falta proteção contra repetição | Armazene o dia em que ela rodou (tm_yday) e verifique esse valor |
| O laço trava por 5 segundos a cada ciclo | Sem uma hora válida, getLocalTime() espera 5.000 ms por padrão | Use getLocalTime(&t, 0) dentro de loop() |
| Símbolos estranhos no nome do dia no OLED | A fonte não tem letras acentuadas | Escreva os nomes dos dias em ASCII simples ou mude para a biblioteca U8g2 |
NTP no ESP32 é a forma mais barata e simples de adicionar a hora exata a um projeto: com poucas linhas de código, ele obtém a hora com precisão de segundos, cuida sozinho do fuso horário e do horário de verão e, com um DS3231, também resiste a quedas de energia. No app BlueGrays, você encontra projetos passo a passo com esquemas do circuito e código para relógio de tempo real via NTP no ESP32, relógio digital com RTC DS1307, display de previsão do tempo em OLED e registro de temperatura com data e hora.
Perguntas frequentes
O ESP32 consegue manter a hora sem internet?
Sim, depois de receber a hora pelo NTP: o ESP32 continua contando a hora do sistema com seu próprio temporizador, e perder o Wi-Fi não para o relógio. Porém, se faltar energia ou a placa reiniciar, a hora volta a 1970. Em locais sem internet, ou para um relógio que resista a quedas de energia, adicione um módulo RTC com bateria de reserva, como o DS3231.
Por que a hora NTP do meu ESP32 está com algumas horas de diferença?
Se nenhum fuso horário estiver configurado, o ESP32 mostra UTC, então o relógio fica com uma diferença igual ao seu deslocamento em relação ao UTC. Use configTzTime com a string POSIX correta, por exemplo "CET-1CEST,M3.5.0,M10.5.0/3" para a Europa Central ou "GMT0BST,M3.5.0/1,M10.5.0" para o Reino Unido. Se a diferença for o dobro do deslocamento, o sinal na sua string TZ está invertido.
Qual é a diferença entre configTime e configTzTime?
configTime recebe a diferença em relação ao UTC e um ajuste do horário de verão em segundos, mas não sabe quando o horário de verão começa e termina na sua região. configTzTime recebe uma string de fuso horário POSIX que inclui essas regras, então o ESP32 alterna automaticamente entre o horário de verão e o de inverno. Para qualquer região com horário de verão, configTzTime é a escolha certa.
Com que frequência o ESP32 sincroniza com NTP?
No core Arduino ESP32, o SNTP consulta o servidor novamente a cada 3 horas por padrão e mantém a hora com seu próprio temporizador entre as consultas. Você pode alterar o intervalo em milissegundos com sntp_set_sync_interval(), mas, pelas regras do protocolo, ele não pode ser menor que 15 segundos.
Preciso de um módulo RTC com o ESP32?
Não, se o dispositivo estiver sempre alimentado e tiver acesso ao Wi-Fi; o NTP fornece uma hora mais precisa. Se você precisar da hora correta após uma queda de energia, antes que a internet volte, ou se o dispositivo for funcionar em um local sem internet, adicione um módulo RTC com bateria de reserva, como o DS3231.
Como obter a hora NTP no Raspberry Pi Pico W?
No MicroPython, depois de conectar ao Wi-Fi, basta executar import ntptime e ntptime.settime(); isso ajusta o RTC interno do Pico para UTC. Para a hora local, some sua diferença em segundos, por exemplo time.localtime(time.time() + 3600) para UTC+1. ntptime não ressincroniza automaticamente, então chame settime() novamente uma ou duas vezes por dia.
Projetos para montar depois desta leitura
Ver todos os projetos
Hora exata pela internet com ESP32 e NTP
O ESP32 se conecta ao Wi-Fi e obtém a hora exata com precisão de milissegundos de servidores NTP (Network Time Protocol). Com a função configTzTime(), o fuso horário configurado (TRT, UTC+3 neste exemplo) é aplicado automaticamente, e as mudanças nos países que adotam horário de verão são gerenciadas automaticamente por uma string POSIX TZ. A hora é exibida tanto no Monitor Serial quanto em um LCD I2C opcional.

Relógio digital com ESP32 e RTC DS1307 (I2C + LCD)
Neste projeto, exibimos a hora e a data em um display LCD I2C 16x2 usando o módulo de relógio de tempo real (RTC) DS1307. O DS1307 é um RTC com bateria de reserva que mantém a hora mesmo quando o ESP32 é desligado. Os dois módulos compartilham o mesmo barramento I2C (SDA/SCL), permitindo aprender a controlar vários dispositivos com apenas dois fios de sinal. Você aprenderá na prática o protocolo de comunicação I2C, o funcionamento dos módulos RTC e a exibição de dados dinâmicos em um LCD.

Conexão Wi-Fi STA com ESP32 e display LCD
Neste projeto, conectamos o ESP32 à rede Wi-Fi de casa no modo estação (STA), de forma segura e tolerante a falhas. Guardamos o SSID e a senha em um arquivo secrets.h separado para evitar que sejam expostos no Git, configuramos um mecanismo de reconexão automática e exibimos o nível de sinal RSSI e o endereço IP em um display LCD I2C. O LED integrado funciona como indicador de status. É um gerenciador de conexão pronto para uso em produção.

ESP32 HTTPClient + API de clima (com display OLED SSD1306)
Neste projeto, usamos o ESP32 para fazer uma chamada REST à API de clima wttr.in pela internet com HTTPClient.h. Diferentemente da versão anterior com LCD I2C, aqui usamos um display OLED SSD1306 (128×64), que permite usar fontes grandes, ícones vetoriais e uma apresentação visual rica em uma única tela. Sete ícones vetoriais de condições do tempo (ensolarado, parcialmente nublado, nublado, chuvoso, com neve, com tempestade e com neblina) são desenhados durante a execução, sem arquivos bitmap.

ESP32 HTTPClient + API de clima (com display LCD)
Neste projeto, usamos o ESP32 para fazer uma chamada REST à API de clima wttr.in pela internet com HTTPClient.h. Interpretamos a resposta JSON com ArduinoJson v7 e mostramos cidade, temperatura, umidade, condições do tempo e vento em um display LCD I2C, em turco e inglês. O wttr.in é grátis e não exige chave de API — basta informar o nome de uma cidade para consultar o clima atual. Os dados se atualizam sozinhos a cada 30 minutos, e o LED dá um retorno visual diferente para cada tela.

ESP32 em sono profundo — Economia de bateria (OLED + contador na RAM RTC)
O modo de sono profundo do ESP32 reduz o consumo de corrente para ~10 µA ao desligar a CPU e a maioria dos periféricos, sendo essencial para dispositivos IoT alimentados por bateria. Neste projeto, usamos três fontes de despertar: um temporizador (15 segundos), um pino externo (EXT0 — botão no GPIO 33) e toque (T0/GPIO 4). As variáveis marcadas com RTC_DATA_ATTR são preservadas durante o sono profundo, mantendo os contadores de inicializações e de despertares por temporizador e botão.

Registrador de temperatura com ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Monte um registrador de temperatura com data e hora usando ESP32! Leia a temperatura e a umidade do DHT22 a cada minuto, obtenha a hora real do DS1307 RTC e grave os dados em CSV no cartão microSD. O OLED exibe a temperatura atual em letras grandes, a porcentagem de umidade, a data e a hora e o contador de registros. O arquivo /temp_log.csv no cartão SD pode ser aberto no Excel, Google Sheets ou pandas do Python para análise — ideal para monitorar estufas, acompanhar a temperatura de casa ou…

Display LCD I2C com ESP32 (16x2)
Neste projeto, exibimos texto conectando um display LCD de 16x2 caracteres ao ESP32 pelo protocolo I2C. Com o I2C, controlamos o display usando apenas dois pinos de dados (SDA e SCL), dispensando o uso de seis ou mais pinos da conexão paralela tradicional. O código mostra uma mensagem fixa na primeira linha e um contador que aumenta a cada segundo na segunda linha. Na inicialização, também executamos um scanner I2C para mostrar o endereço do display no Monitor Serial (geralmente 0x27 ou 0x3F).
Mais artigos
- Módulo RTC com Arduino: DS3231 vs DS1307 vs DS1302, hora e alarmes
- O que é ESP32? Características, configuração na Arduino IDE e seu primeiro projeto com Wi-Fi
- Como usar um display OLED (SSD1306): texto, formas e gráficos com Arduino
- ESP32 deep sleep: fontes de despertar, consumo de corrente e autonomia da bateria
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

