Skip to Content

How to synchronize ESP32 clock with SNTP server

How to synchronize ESP32 clock with SNTP server

In this tutorial you will learn how to synchronize the ESP32 clock with an SNTP server to always have accurate time and never have to set a clock again.

By synchronizing the clock via internet with an SNTP time server we can ensure that our clock always displays the accurate time, regardless of the accuracy of the internal clock of the ESP32 or changes in daylight saving time.

Let’s begin with the required parts.

Required Parts

You will need only an ESP32, an LCD and some cables. Instead of the ESP32-C3 Mini Development Board listed below, you could use any other ESP32 as well.

ESP32-C3 Mini

USB C Cable

LCD 16×2

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

NTP Time Server

NTP stands for Network Time Protocol, and an NTP server is a specialized computer system that provides accurate time information for other internet devices.

An NTP client regularly polls one or more NTP servers to receive the current time and synchronizes its internal clock based on the received timestamp. In case of an ESP32 as client, the ESP32 connects via a router and Wi-Fi to the internet and initiates a connection to a pool of NTP Server. One of the NTP servers responds with a time stamp that is received by the ESP32 and used to adjust its internal clock.

ESP32 polls NTP Servers
ESP32 polls NTP Servers

The NTP servers themselves are organized in a hierarchical, layered system with high-precision timekeeping devices such as atomic clocks, GNSS (including GPS) or other radio clocks their origin (authoritative clock source). The individual layers are called Stratum.

Hierarchy of the NTP server service
Hierarchy of the NTP server service (source)

Typically, you do not connect to a specific NTP server (IP address) but the a pool of many NTP servers. More about that in the next section.

NTP Pool

The NTP Pool is a dynamic collection of networked NTP Servers. The following table shows the number of NTP Servers in the ntp.org pool in different geographic locations.

Active NTP servers as of Aug 2024
Active NTP servers as of Aug 2024 (source)

You request time information from an NTP Server in the pool by using the name “pool.ntp.org”. The system will try finding the closest available server for you. This is the recommended way.

However, you can also request the time from NTP servers in specific location or countries. For instance, the address “de.pool.ntp.org” refers to a pool of NTP Servers in Germany (DE). You can also use continental zones, such as europenorth-americaoceania or asia.pool.ntp.org. Finally, you can add a numerical prefix, if you need multiple server names, e.g. “0.de.pool.ntp.org”, “1.de.pool.ntp.org”.

STNP vs NTP

There are two protocols for time synchronization. NTP (Network Time Protocol) and SNTP (Simple Network Time Protocol). NTP is more accurate and complex, while SNTP is a simplified version of NTP.

SNTP was specifically developed for small computers and micro-controllers. It was designed for lower memory requirements and processing power than NTP. While much less precise than NTP, SNTP still provides time within 100 milliseconds of the accurate time.

Typically, it is used by small network devices, such as IP cameras, DVR’s, IP phones, routers, consumer devices and also by microcontroller such as the ESP32.

Synchronizing ESP32 clock with SNTP server

The following code shows you how to synchronize the internal clock of an ESP32 with the time received from an SNTP server. This allows the ESP32 to keep accurate time by periodically updating it from the server. Have a quick look at the complete code first, and then we will discuss the details.

//  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);
}

Libraries

We start by including the required libraries. WiFi.h for connecting to the internet, and esp_sntp.h for communication with an SNTP server. Both libraries are default libraries of the ESP32 core and do not need to be installed.

#include "WiFi.h"
#include "esp_sntp.h"

Constants and Variables

Next we define the constants and variables needed for the program. Here, we specify the SSID and password for the Wi-Fi network to which the ESP32 will connect. Obviously, you will have to replace those by your own Wi-Fi credentials.

const char* ssid = "SSID";
const char* password = "PASSWORD";

WiFi Initialization

The initWiFi() function is responsible for connecting the ESP32 to the specified WiFi network. It continuously checks the connection status until it is successfully connected.

void initWiFi() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
}

SNTP Initialization

In the initSNTP() function, we configure the SNTP settings for time synchronization. This includes setting the synchronization interval, server name, operating mode, and time zone.

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();
}

Let’s have a closer look at the individual function calls and what they do.

sntp_set_sync_interval(interval_ms) determines how frequently we synchronize the internal clock of the ESP32 with the SNTP server. The time interval is specified in microseconds and an interval of 60 * 60 * 1000UL microseconds means we are synchronizing every hour. Don’t send excessively frequent queries. Reasonable query intervals are typically from once or twice a day to 5 times an hour. 

sntp_set_time_sync_notification_cb(callback) allows you to specify a notification function (callback) that is called every time a synchronization occurs. In the example code, we define the function notify() for this purpose, which just prints “synchronized“. In most applications you won’t need this but it is great for testing and debugging!

esp_sntp_setoperatingmode(operating_mode) sets the operating mode. This is typically ESP_SNTP_OPMODE_POLL – we are polling the SNTP server. But there is also ESP_SNTP_OPMODE_LISTENONLY.

esp_sntp_setservername(idx, server) is used to set the name/address of the server. You can specify multiple servers, if you want to. For instance:

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() starts the SNTP service with the above parameters. There is also a stop function: esp_sntp_stop() to stop the service.

Finally, we need to set the time zone since the SNTP server returns the time in UTC. That bit is implemented in a separate function we will discuss in the next section.

If you want to learn more about ESP timer functions have a look at the ESP32 Programming Guide for System Time. Note that there is also a configTime() function as part of the Arduino core that essentially does the same as our initSNTP() function. However, it takes a gmtOffset_sec and daylightOffset_sec as parameter but I could not get it to work properly with the Australian Time zone and it doesn’t allow you to set the synchronization interval.

Timezone Configuration

The setTimezone() function sets the timezone for the ESP32 using the TZ environment variable. Since, I am currently living in Australia, I set it to “AEST-10AEDT,M10.1.0,M4.1.0/3”, which corresponds to the Australian Eastern Standard Time (AEST) with daylight saving time adjustments.

void setTimezone() {
  setenv("TZ", "AEST-10AEDT,M10.1.0,M4.1.0/3", 1);
  tzset();
}

The parts of this time zone definition are as follows

  • AEST: Australian Eastern Standard Time
  • -10: UTC offset of 10 hours ahead of Coordinated Universal Time (UTC)
  • AEDT: Australian Eastern Daylight Time
  • M10.1.0: Transition to daylight saving time occurs on the 1st Sunday of October
  • M4.1.0/3: Transition back to standard time occurs on the 1st Sunday of April, with a 3-hour difference from UTC.

For other time zone definitions have a look at the Posix Timezones Database. Just copy and paste the string you find there for your time zone into the setenv() function.

Waiting for Synchronization

The wait4SNTP() function continuously checks the synchronization status with the SNTP server until it is completed. It prints a message while waiting for synchronization.

void wait4SNTP() {
  while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
    delay(100);
    Serial.println("waiting ...");
  }
}

Printing Time

The printTime() function retrieves the current time information and prints it in a formatted manner using the struct tm data structure.

void printTime() {
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}

"%A, %B %d %Y %H:%M:%S" are format specifiers that determine how the timeinfo is formatted and the members of the tm struct are as follows:

  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

Setup Function

In the setup() function, we initialize the serial communication, connect to Wi-Fi, initialize SNTP, and wait for the time synchronization to complete.

void setup() {
  Serial.begin(115200);
  initWiFi();
  initSNTP();
  wait4SNTP();
}

Note that you cannot switch off the Wi-Fi connection after the setup is complete. Some tutorials get this wrong. Without Wi-Fi the ESP32 cannot synchronize with the SNTP server. Switching off the Wi-Fi makes only sense if you put the ESP32 into deep sleep and reconnect to an SNTP server at start-up.

Loop Function

The loop() function repeatedly prints the current time every 10 seconds.

void loop() {
  printTime();
  delay(10000);
}

Note that the synchronization occurs in the background (separate thread). There is no function you have to regularly call in the loop function to poll the SNTP server.

Output example

To try out the code, I suggest you temporarily change the synchronization interval to 30 seconds (30 * 1000UL). On the Serial Monitor you then should see the following sequence of outputs:

SNTP synchronized time output on Serial Monitor
Time synchronization output on Serial Monitor

First a “waiting” message until the connection to the SNTP server is established. Then time printouts every 10 seconds, followed be a “synchronized” notification every 30 seconds.

In the next section we essential use the same code to build an internet synchronized clock that displays the time and date on an LCD.

Showing Internet Time on an LCD

Connecting an LCD with I2C interface to the ESP32 to display the time is easy. First, connect 5V to VCC and G to Ground. Then connect SDA to pin 8 (yellow wire) and SCL to pin 9 (orange wire) on the ESP32.

Connecting LCD display via I2C to ESP32
Connecting LCD display via I2C to ESP32

If you need more detailed information on LCD displays have a look at our tutorials on How to use a 16×2 character LCD with Arduino and Character I2C LCD with Arduino Tutorial (8 Examples).

Make sure to connect SDA and SCL correctly. If you use a different board you may want to change to the pins that support the I2C interface on your board. Just look for the pins marked with SDA and SCL in the pinout. Here is the pinout for the ESP32-C3 Supermini, I am using in this project:

Pinout for the ESP32-C3 Supermini
Pinout for the ESP32-C3 Supermini

While the pins GPIO8 and GPIO9 are supposed to be the native pins for the I2C interface, I had to set them explicitly in the code (Wire.begin(sda, scl)) to get it working.

Here a picture of the actual, completed wiring and the clock running on the LCD. As you can see it is only a few wires and the ESP32-C3 Supermini is indeed “mini” compared to the LCD.

ESP32 synchronized clock on LCD
Internet synchronized clock on LCD

Code for the internet synchronized clock

Here is the complete code for synchronizing the internal clock of an ESP32 with an SNTP server and displaying current time and date on an 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);
}

As you can see, this is essentially the same code as before with the addition of the initLCD() function that prepares the LCD and the lcdPrintTime() function that displays time and date on the 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);
}

The lcdPrintTime() function first calls getLocalTime() to get the local time of the ESP32 internal clock, which we synchronized with the SNTP server. From struct tm, we then extract the needed pieces of time and date information (hour, minutes, …, year) and write them formatted to the string buffer buff. Finally, we set the cursor for the LCD and display the contents of the buffer on the LCD. Your output on the LCD should look like this:

Time output on LCD
Time output on LCD

And that’s it!
Now you have an always accurate clock on an ESP32 that synchronizes its time with an SNTP server.

Conclusions

In this tutorial you learned how to synchronize the internal clock of an ESP32 clock with the time signal of an SNTP server. This ensure that the ESP32 clock is always accurate and has the additional advantage that you will never have to set the clock.

We used ESP32 specific functions such as sntp_set_sync_interval, esp_sntp_setoperatingmode, and esp_sntp_setservername to configure the communication with an SNTP server.

It is worthwhile knowing, however, that the Arduino core also provides two functions to set up an SNTP server connection. Those are configTime and configTzTime. They are more abstract wrappers around the same functions we used in initSNTP but will work without change for ESP32 or ESP8266, for instance. So, you could replace initSNTP be calling configTzTime(timezone, "pool.ntp.org"). But initSNTP gives you a bit more control such as setting the synchronization interval.

Finally, if you want to run the ESP32 in deep sleep mode and synchronize its clock when it wakes up, or you need additional time information, you may prefer by using an internet time provider such as WorldTimeAPI. For more information, have a look at the Automatic Daylight Savings Time Clock, the Digital Clock with CrowPanel 3.5″ ESP32 Display or the LED Ring Clock with WS2812 tutorial.

Happy tinkering ; )

Ambrogio

Sunday 1st of September 2024

Stefan, >I can get the SNTP synch at the desired interval > I do synchronize the ESP32 > I can display the ESP (sync data) and DS3231 RTC ( not sync ) data as follow: 17:53:13.082 -> synchronized *********** 17:53:13.082 -> sNTP data received OK ! 17:53:14.209 -> 17:53:14.209 -> ESP32 data >>> :Sunday, September 01 2024 17:53:15 17:53:14.209 -> from rtc.now(): 2024/9/1 (Sunday) 0:39:27 I would ask the final direction: How could I synchronize the DS3231 RTC using the ESP32 ( sync ) data please? ( ESP32 data >>> :Sunday, September 01 2024 17:53:15) How to "pass" the data to RTC ? rtc.adjust( y, m, d, h,min, sec ); >>> how to retrieve y, m, d, h,min, sec ? I am so sorry but I am not skill ... Thanks for the great help. Regards, Ambrogio

Ambrogio

Tuesday 3rd of September 2024

@Stefan Maetschke, @Stefan Maetschke, thanks a lot for your useful directions: it is working very well. All the best to you. regards, Ambrogio

Stefan Maetschke

Monday 2nd of September 2024

Hi, struct tm has what you need. https://cplusplus.com/reference/ctime/tm/

struct tm t; getLocalTime(&t);

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

so something like rtc.adjust(t.tm_year+1900, t,tm_mon, ..., t.tm_sec);

but you will have to watch out for the ranges, e.g. tm_mon is from 0..11 and not from 1..12.

Ambrogtio

Thursday 29th of August 2024

congratulation for the very clear instructions for using the SNTP: that is great. I would like to use the SNTP to synchronize the DS3231 chip let me say once a day or once an hour. Could you please give some directions to follow or any sample code ? Thanks in advance for your assistance. Best regards, Ambrogio North_Italy

Ambrogtio

Friday 30th of August 2024

Hi Stefan thanks so much for having reply to me . I am using the ESP32 connected to the DS3231. Ambrogio

Stefan Maetschke

Friday 30th of August 2024

Hi Ambrogtio, What microcontroller are you using?