Neste tutorial, vais aprender como sincronizar o relógio do ESP32 com um servidor SNTP para ter sempre a hora correta e nunca mais precisares de ajustar o relógio.
Ao sincronizar o relógio via internet com um servidor de tempo SNTP, garantimos que o nosso relógio mostra sempre a hora exata, independentemente da precisão do relógio interno do ESP32 ou das alterações do horário de verão.
Vamos começar com as peças necessárias.
Peças Necessárias
Só precisas de um ESP32, um LCD e alguns cabos. Em vez da placa de desenvolvimento ESP32-C3 Mini listada abaixo, podes usar qualquer outro ESP32.

ESP32-C3 Mini

Cabo USB C

LCD 16×2

Conjunto de fios Dupont

Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Servidor de Tempo NTP
NTP significa Network Time Protocol, e um servidor NTP é um sistema informático especializado que fornece informação de tempo precisa para outros dispositivos na internet.
Um cliente NTP interroga regularmente um ou mais servidores NTP para receber a hora atual e sincroniza o seu relógio interno com base no timestamp recebido. No caso do ESP32 como cliente, o ESP32 conecta-se via router e Wi-Fi à internet e inicia uma ligação a um pool de servidores NTP. Um dos servidores responde com um timestamp que é recebido pelo ESP32 e usado para ajustar o seu relógio interno.

Os próprios servidores NTP estão organizados numa hierarquia em camadas com dispositivos de cronometragem de alta precisão, como relógios atómicos, GNSS (incluindo GPS) ou outros relógios de rádio como origem (fonte de relógio autoritativa). As camadas individuais são chamadas Stratum.

Normalmente, não te ligas a um servidor NTP específico (endereço IP), mas sim a um pool de muitos servidores NTP. Mais sobre isso na próxima secção.
Pool NTP
O NTP Pool é uma coleção dinâmica de servidores NTP em rede. A tabela seguinte mostra o número de servidores NTP no pool ntp.org em diferentes localizações geográficas.

Solicitas informação de tempo a um servidor NTP do pool usando o nome “pool.ntp.org”. O sistema tenta encontrar o servidor disponível mais próximo para ti. Esta é a forma recomendada.
No entanto, também podes solicitar a hora a servidores NTP em localizações ou países específicos. Por exemplo, o endereço “de.pool.ntp.org” refere-se a um pool de servidores NTP na Alemanha (DE). Também podes usar continental zones, como europe, north-america, oceania ou asia.pool.ntp.org. Finalmente, podes adicionar um prefixo numérico, se precisares de múltiplos nomes de servidor, por exemplo “0.de.pool.ntp.org”, “1.de.pool.ntp.org”.
STNP vs NTP
Existem dois protocolos para sincronização de tempo. NTP (Network Time Protocol) e SNTP (Simple Network Time Protocol). O NTP é mais preciso e complexo, enquanto o SNTP é uma versão simplificada do NTP.
O SNTP foi desenvolvido especificamente para computadores pequenos e microcontroladores. Foi desenhado para requisitos menores de memória e poder de processamento do que o NTP. Embora menos preciso que o NTP, o SNTP ainda fornece a hora com uma precisão dentro de 100 milissegundos.
Normalmente, é usado por dispositivos de rede pequenos, como câmaras IP, DVRs, telefones IP, routers, dispositivos de consumo e também por microcontroladores como o ESP32.
Sincronizar o relógio do ESP32 com um servidor SNTP
O código seguinte mostra como sincronizar o relógio interno de um ESP32 com a hora recebida de um servidor SNTP. Isto permite que o ESP32 mantenha a hora correta atualizando-a periodicamente a partir do servidor. Dá uma vista rápida ao código completo primeiro, depois discutiremos os detalhes.
// makerguides.com: synchronizing ESP32 time with SNTP server
#include "WiFi.h"
#include "esp_sntp.h"
const char* ssid = "SSID";
const char* password = "PASSWORD";
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
void notify(struct timeval* t) {
Serial.println("synchronized");
}
void initSNTP() {
sntp_set_sync_interval(1 * 60 * 60 * 1000UL); // 1 hour
sntp_set_time_sync_notification_cb(notify);
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setTimezone();
}
void wait4SNTP() {
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
delay(100);
Serial.println("waiting ...");
}
}
void setTimezone() {
setenv("TZ", "AEST-10AEDT,M10.1.0,M4.1.0/3", 1);
tzset();
}
void printTime() {
struct tm timeinfo;
getLocalTime(&timeinfo);
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}
void setup() {
Serial.begin(115200);
initWiFi();
initSNTP();
wait4SNTP();
}
void loop() {
printTime();
delay(10000);
}
Bibliotecas
Começamos por incluir as bibliotecas necessárias. WiFi.h para conectar à internet, e esp_sntp.h para comunicação com um servidor SNTP. Ambas são bibliotecas padrão do core do ESP32 e não precisam de ser instaladas.
#include "WiFi.h" #include "esp_sntp.h"
Constantes e Variáveis
De seguida definimos as constantes e variáveis necessárias para o programa. Aqui especificamos o SSID e a password da rede Wi-Fi à qual o ESP32 vai conectar-se. Obviamente, terás de substituir pelos teus próprios dados de Wi-Fi.
const char* ssid = "SSID"; const char* password = "PASSWORD";
Inicialização do WiFi
A função initWiFi() é responsável por conectar o ESP32 à rede WiFi especificada. Verifica continuamente o estado da ligação até estar conectada com sucesso.
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
Inicialização do SNTP
Na função initSNTP(), configuramos as definições do SNTP para sincronização do tempo. Isto inclui definir o intervalo de sincronização, nome do servidor, modo de operação e fuso horário.
void notify(struct timeval* t) {
Serial.println("synchronized");
}
void initSNTP() {
sntp_set_sync_interval(60 * 60 * 1000UL); // 1 hour
sntp_set_time_sync_notification_cb(notify);
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setTimezone();
}
Vamos analisar mais de perto as chamadas de função individuais e o que fazem.
sntp_set_sync_interval(interval_ms) determina com que frequência sincronizamos o relógio interno do ESP32 com o servidor SNTP. O intervalo de tempo é especificado em microssegundos e um intervalo de 60 * 60 * 1000UL microssegundos significa que sincronizamos a cada hora. Não envies pedidos com muita frequência. Intervalos razoáveis são tipicamente de uma a duas vezes por dia até 5 vezes por hora.
sntp_set_time_sync_notification_cb(callback) permite especificar uma função de notificação (callback) que é chamada sempre que ocorre uma sincronização. No código de exemplo, definimos a função notify() para este propósito, que apenas imprime “synchronized“. Na maioria das aplicações não precisas disto, mas é ótimo para testes e depuração!
esp_sntp_setoperatingmode(operating_mode) define o operating mode. Normalmente é ESP_SNTP_OPMODE_POLL – estamos a interrogar o servidor SNTP. Mas existe também ESP_SNTP_OPMODE_LISTENONLY.
esp_sntp_setservername(idx, server) é usado para definir o nome/endereço do servidor. Podes especificar múltiplos servidores, se quiseres. Por exemplo:
esp_sntp_setservername(0, "pool.ntp.org"); esp_sntp_setservername(1, "de.pool.ntp.org"); esp_sntp_setservername(2, "time.nist.gov");
esp_sntp_init() inicia o serviço SNTP com os parâmetros acima. Existe também uma função para parar: esp_sntp_stop() para parar o serviço.
Finalmente, precisamos definir o fuso horário, pois o servidor SNTP retorna a hora em UTC. Essa parte é implementada numa função separada que discutiremos na próxima secção.
Se quiseres aprender mais sobre funções de temporizador do ESP, dá uma vista de olhos ao ESP32 Programming Guide for System Time. Note que existe também uma função configTime() como parte do core do Arduino que faz essencialmente o mesmo que a nossa função initSNTP(). No entanto, ela recebe um gmtOffset_sec e daylightOffset_sec como parâmetros, mas não consegui fazê-la funcionar corretamente com o fuso horário australiano e não permite definir o intervalo de sincronização.
Configuração do Fuso Horário
A função setTimezone() define o fuso horário para o ESP32 usando a variável de ambiente TZ. Como atualmente vivo na Austrália, defini para “AEST-10AEDT,M10.1.0,M4.1.0/3”, que corresponde ao Australian Eastern Standard Time (AEST) com ajustes de horário de verão.
void setTimezone() {
setenv("TZ", "AEST-10AEDT,M10.1.0,M4.1.0/3", 1);
tzset();
}
As partes desta definição de fuso horário são as seguintes
- AEST: Australian Eastern Standard Time
- -10: Deslocamento UTC de 10 horas à frente do Tempo Universal Coordenado (UTC)
- AEDT: Australian Eastern Daylight Time
- M10.1.0: A transição para o horário de verão ocorre no 1º domingo de outubro
- M4.1.0/3: A transição de volta para o horário padrão ocorre no 1º domingo de abril, com uma diferença de 3 horas em relação ao UTC.
Para outras definições de fuso horário, consulta o Posix Timezones Database. Basta copiar e colar a string que encontrares para o teu fuso horário na função setenv().
Aguardar pela Sincronização
A função wait4SNTP() verifica continuamente o estado da sincronização com o servidor SNTP até estar concluída. Imprime uma mensagem enquanto espera pela sincronização.
void wait4SNTP() {
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
delay(100);
Serial.println("waiting ...");
}
}
Imprimir a Hora
A função printTime() obtém a informação atual da hora e imprime-a de forma formatada usando a estrutura de dados struct tm.
void printTime() {
struct tm timeinfo;
getLocalTime(&timeinfo);
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}
"%A, %B %d %Y %H:%M:%S" são format specifiers que determinam como o timeinfo é formatado e os membros da tm struct são os seguintes:
Member Type Meaning Range tm_sec int seconds after the minute 0-61* tm_min int minutes after the hour 0-59 tm_hour int hours since midnight 0-23 tm_mday int day of the month 1-31 tm_mon int months since January 0-11 tm_year int years since 1900 tm_wday int days since Sunday 0-6 tm_yday int days since January 1 0-365 tm_isdst int Daylight Saving Time flag
Função Setup
Na função setup(), inicializamos a comunicação serial, conectamos ao Wi-Fi, inicializamos o SNTP e esperamos que a sincronização do tempo seja concluída.
void setup() {
Serial.begin(115200);
initWiFi();
initSNTP();
wait4SNTP();
}
Note que não podes desligar a ligação Wi-Fi após o setup estar completo. Alguns tutoriais erram nisso. Sem Wi-Fi, o ESP32 não consegue sincronizar com o servidor SNTP. Desligar o Wi-Fi só faz sentido se colocares o ESP32 em deep sleep e reconectares a um servidor SNTP ao arrancar.
Função Loop
A função loop() imprime repetidamente a hora atual a cada 10 segundos.
void loop() {
printTime();
delay(10000);
}
Note que a sincronização ocorre em segundo plano (thread separada). Não existe nenhuma função que tenhas de chamar regularmente na função loop para interrogar o servidor SNTP.
Exemplo de saída
Para testar o código, sugiro que alteres temporariamente o intervalo de sincronização para 30 segundos (30 * 1000UL). No Monitor Serial deverás ver a seguinte sequência de saídas:

Primeiro uma mensagem “waiting” até a ligação ao servidor SNTP ser estabelecida. Depois impressões da hora a cada 10 segundos, seguidas de uma notificação “synchronized” a cada 30 segundos.
Na próxima secção, usamos essencialmente o mesmo código para construir um relógio sincronizado com a internet que mostra a hora e a data num LCD.
Mostrar a Hora da Internet num LCD
Ligar um LCD com interface I2C ao ESP32 para mostrar a hora é fácil. Primeiro, liga 5V ao VCC e G ao GND. Depois liga SDA ao pino 8 (fio amarelo) e SCL ao pino 9 (fio laranja) no ESP32.

Se precisares de mais informações detalhadas sobre displays LCD, consulta os nossos tutoriais sobre How to use a 16×2 character LCD with Arduino e Character I2C LCD with Arduino Tutorial (8 Examples).
Certifica-te de ligar corretamente o SDA e o SCL. Se usares uma placa diferente, podes querer mudar para os pinos que suportam a interface I2C na tua placa. Basta procurar os pinos marcados com SDA e SCL no pinout. Aqui está o pinout para o ESP32-C3 Supermini, que estou a usar neste projeto:
Embora os pinos GPIO8 e GPIO9 sejam supostamente os pinos nativos para a interface I2C, tive de defini-los explicitamente no código (Wire.begin(sda, scl)) para funcionar. Para mais informações sobre a placa SuperMini, consulta o tutorial ESP32-C3 SuperMini Board.
A imagem seguinte mostra a ligação finalizada e o relógio a funcionar no LCD. Como podes ver, são apenas alguns fios e o ESP32-C3 Supermini é realmente “mini” comparado com o LCD.

Código para o relógio sincronizado com a internet
Aqui está o código completo para sincronizar o relógio interno de um ESP32 com um servidor SNTP e mostrar a hora e data atuais num LCD.
// makerguides.com: synchronizing ESP32 time with SNTP server
#include "WiFi.h"
#include "esp_sntp.h"
#include "LiquidCrystal_I2C.h"
const int sdaPin = 8;
const int sclPin = 9;
const char* ssid = "SSID";
const char* password = "PASSWORD";
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
const char *timezone = "AEST-10AEDT,M10.1.0,M4.1.0/3"
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x3F, 16, 2);
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
void initSNTP() {
sntp_set_sync_interval(60 * 60 * 1000UL); // 1 h
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setenv("TZ", timezone, 1);
tzset();
}
void wait4SNTP() {
lcd.setCursor(0, 0);
lcd.print("synchronizing...");
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
delay(100);
}
lcd.clear();
}
void initLCD() {
Wire.begin(sdaPin, sclPin); // I2C for ESP32-C3 Supermini
lcd.init();
lcd.backlight();
lcd.clear();
}
void lcdPrintTime() {
// https://cplusplus.com/reference/ctime/tm/
char buff[24];
struct tm t;
getLocalTime(&t);
sprintf(buff, "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
lcd.setCursor(4, 0);
lcd.print(buff);
sprintf(buff, "%02d-%02d-%04d ", t.tm_mday, t.tm_mon+1, t.tm_year+1900);
lcd.setCursor(3, 1);
lcd.print(buff);
}
void setup() {
initLCD();
initWiFi();
initSNTP();
wait4SNTP();
}
void loop() {
lcdPrintTime();
delay(1000);
}
Como podes ver, é essencialmente o mesmo código de antes com a adição da função initLCD() que prepara o LCD e da função lcdPrintTime() que mostra a hora e data no LCD.
void lcdPrintTime() {
char buff[24];
struct tm t;
getLocalTime(&t);
sprintf(buff, "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
lcd.setCursor(4, 0);
lcd.print(buff);
sprintf(buff, "%02d-%02d-%04d ", t.tm_mday, t.tm_mon, t.tm_year+1900);
lcd.setCursor(3, 1);
lcd.print(buff);
}
A função lcdPrintTime() chama primeiro getLocalTime() para obter a hora local do relógio interno do ESP32, que sincronizámos com o servidor SNTP. A partir de struct tm, extraímos as partes necessárias da informação de tempo e data (hora, minutos, …, ano) e escrevemo-las formatadas no buffer de string buff. Finalmente, definimos o cursor do LCD e mostramos o conteúdo do buffer no LCD. A tua saída no LCD deverá parecer-se com isto:

E é tudo!
Agora tens um relógio sempre preciso num ESP32 que sincroniza a sua hora com um servidor SNTP.
Conclusões
Neste tutorial aprendeste como sincronizar o relógio interno de um ESP32 com o sinal de tempo de um servidor SNTP. Isto garante que o relógio do ESP32 está sempre preciso e tem a vantagem adicional de nunca precisares de ajustar o relógio.
Usámos funções específicas do ESP32 como sntp_set_sync_interval, esp_sntp_setoperatingmode e esp_sntp_setservername para configurar a comunicação com um servidor SNTP.
É útil saber, no entanto, que a conexão Arduino core also provides two functions to set up an SNTP server. Estas são configTime e configTzTime. São wrappers mais abstratos em torno das mesmas funções que usamos em initSNTP, mas funcionam sem alterações para ESP32 ou ESP8266, por exemplo. Portanto, poderias substituir initSNTP por chamar configTzTime(timezone, "pool.ntp.org"). Mas initSNTP dá-te um pouco mais de controlo, como definir o intervalo de sincronização.
Finalmente, se quiseres usar o ESP32 em modo deep sleep e sincronizar o relógio quando acorda, ou precisares de informação de tempo adicional, podes preferir usar um fornecedor de tempo pela internet como WorldTimeAPI. Para mais informações, consulta o Automatic Daylight Savings Time Clock, o Digital Clock with CrowPanel 3.5″ ESP32 Display ou o tutorial LED Ring Clock with WS2812.
Boas experiências a fazer tinkering ; )


