NetClock: NTP Synchronized OLED Clock: Update

The NetClock NTP Synchronized OLED Clock is a quick and simple project to exercise your WeMos D1 R2 ESP8266 board and get a super-accurate clock as well!

See update below for automatic WiFi connection handling!

NTP Synchronised OLED Clock

NTP Synchronized OLED Clock: using the WeMos D1 R2 ESP8266 WiFi board

According to Wikipedia, the “Network Time Protocol (NTP) is a networking protocol for clock synchronization between computer systems over packet-switched, variable-latency data networks. In operation since before 1985, NTP is one of the oldest Internet protocols in current use.” And this is a perfect way to ensure that your clock is always on time!

The software builds on work found on the Arduino Network Time Protocol (NTP) Client tutorial. I ported this code onto the ESP8266 platform and added support for the OLED and the soft RTC.

Simply put, the software connects to your local WiFi network (see update below), creates a special UDP NTP packet and sends this timestamp request to a time server. The response is then parsed to extract a timestamp that is used to synchronizes a (soft) real-time clock (RTC). The RTC is set initially on power-up and then reset (if necessary) every hour from then on.  The timestamp extracted from the NTP response consists of a 32-bit count of seconds from 1 January 1900. By subtracting the number of seconds from 1 January 1900 to 1 January 1970, this value is equivalent to a UNIX-like timestamp. The RTC library converts this UNIX timestamp to the current time and date.  The software adjusts for local time (based on your time zone) and automatically corrects for daylight savings time. The addition of an inexpensive I²C interface 0.9″ OLED, connected directly to the WeMos board, creates a convenient way to display the date and time.

If the WiFi network is not reliable, or the unit is to operated with extensive periods of power outage, you can add a battery-backed RTC module to keep time between power disruptions. Another version of the software – with just two lines of code changes – allows for this simple addition. However, there are other things to consider.

Real Time Clock

Real Time Clock

The RTC module board is based on the DS3231 RTC and has a I²C interface to read and write time and date registers. The module also features a battery holder for a 3V 2032-type battery to provide the small amount of power necessary to keep the clock running when there is no external voltage. However, to keep the battery charged, the module must be run at 5V. In this application it is therefore necessary to add level shifters to both the SDA and SCL lines of the I²C interface.

So, when using this module, there are now two devices connected to the I²C interface – the OLED, running at 3.3.V and the RTC module running at 5V. This requires the use of level shifting. Check out an earlier discussion about this level shifter design here.

I2C level shifers

Discrete level shifers allowing both 3.3V and 5V devices to connect to the I2C interface

A small piggy-back board (seen in the photo above) plugs into the WeMos board for the discrete level shifters (using 2N7000 N-Channel Enhancement Mode FETs) and has header sockets and pins for power and ground connections.

Update 160315:  To make the code truly portable, I needed a way to allow the ESP8266 to automatically connect to any local WiFi channel. However, you would need to manually edit the software to include the specific SSID and password of the local WiFi channel to be used. If you moved the clock to a new location, these new WiFi credentials would then have to be re-edited into the software. Not exactly user friendly!

To solve the problem, I found a WiFiManager library that allowed for automatic connection to the local WiFi through a simple web-server interface – accessible through your smart phone, tablet or PC. Check our the following site for more information.

This is how the WiFi Manager software operates: Initially, the software starts the ESP8266 in Station mode and tries to connect to a previously saved WiFi channel (Access Point). If it fails to connect, or no previous settings have been stored, it scans for available WiFi access points, restarts in Access Point mode and starts a web-server that presents a list of WiFi channels found. The user connects to the ESP8266 AP, accesses the web-server (at, selects the desired channel and enters its password. The SSID and password are then stored in the ESP8266 EEPROM and the chip reset. When the board starts up, the software recovers the stored information (SSID & password) and a connection attempt is made. If successful, the software continues, otherwise it starts the AP again. (Note: An optional switch can be used to force the ESP8266 to ignore the previously stored access credentials and start up the web server.)

The simple software additions are embedded into the clock software in the startup module. This allows the clock to move, scan and connect to any WiFi network automatically without having to edit the source code.  Freedom. Yippee !

Accurate, cheap and eminently hackable… And now a great platform for future clocks!

Here’s the updated code…

//                         OLED NTP Server CLOCK with AutoConect
//                      WeMos D1 R2 WiFi Board, 0.96" OLED, Soft RTC
//                             Adrian Jones, March 2016
// Build 3
//   r1 160221 - initial build with soft RTC and NTP server
//   r2 160222 - addition of WiFi scanner
//   r3 160314 - addition of WiFi AutoConnect from

#define bld  3
#define rev  3

#include <ESP8266WiFi.h>                  //

//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>                  //
#include <WiFiUdp.h>

unsigned int localPort = 2390;            // local port to listen for UDP packets
IPAddress timeServer(129, 6, 15, 28);     // NTP server
const int NTP_PACKET_SIZE = 48;           // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE];      // buffer to hold incoming and outgoing packets

WiFiUDP udp;                              // A UDP instance to let us send and receive packets over UDP
boolean doNTP=false;

#include <Wire.h>
// LCD handler
#include <OzOLED.h>                       // OLED Library
#define Brightness 255                    // display brightness [0-255]
boolean doOLED=true;
#define ops    OzOled.printString         // shortcuts for OLED
#define osc    OzOled.setCursorXY
#define opn    OzOled.printNumber
#define opc    OzOled.printChar

// RTC handler
#include <RTClib.h>                       // RTC-Library
RTC_Millis RTC;                           // RTC (soft)
DateTime now;                             // current time
int ch,cm,cs,os,cdy,cmo,cyr,cdw;          // current time & date variables
int nh,nm,ns,ndy,nmo,nyr,ndw;             // NTP-based time & date variables

#define min(a,b) ((a)<(b)?(a):(b))        // recreate the min function

#include <EEPROM.h>
String esid,epass;                        // eprom stored network

boolean doSerial=true;      
#define sb    Serial.begin(115200)        // shortcuts for serial output
#define sp    Serial.print
#define spf   Serial.printf
#define spln  Serial.println

//                                    SUBROUTINES

// sendNTPpacket(): send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address) {
  if(doSerial) spln("Sending UDP NTP packet request");         

  memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
  // Initialize values needed to form NTP request
  packetBuffer[0] = 0b11100011;             // LI, Version, Mode
  packetBuffer[1] = 0;                      // Stratum, or type of clock
  packetBuffer[2] = 6;                      // Polling Interval
  packetBuffer[3] = 0xEC;                   // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  udp.beginPacket(address, 123);             // NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);  // all NTP fields have values, send UDP packet requesting a timestamp\

//                                 TIME ROUTINES
// getTime(): get current time from RTC (soft or hard)
void getTime() { 
  now =;  
  ch = min(24,now.hour()); if(ch == 0) ch=24; // hours 1-24
  cm = min(59,now.minute()); 
  cs = min(59,now.second());
  cdy= min(31,; 
  cmo= min(now.month(),12); 
  cyr= min(99,now.year()-2000); 
  cdw =now.dayOfTheWeek();

// IsDST(): returns true if during DST, false otherwise
boolean IsDST(int mo, int dy, int dw) {
  if (mo < 3 || mo > 11) { return false; }                // January, February, and December are out.
  if (mo > 3 && mo < 11) { return true;  }                // April to October are in
  int previousSunday = dy - dw;                               
  if (mo == 3) { return previousSunday >= 8; }            // In March, we are DST if our previous Sunday was on or after the 8th.
  return previousSunday <= 0;                             // In November we must be before the first Sunday to be DST. That means the previous Sunday must be before the 1st.

// doClearOLED(start,end): Clear OLED lines from [start] to [end] of characters
void doClearOLED(int st,int en) {
  for(int x=st;x<en;x++) { osc(0,x); ops("                "); }

//                                      Initial Setup
void setup() {
  sb; delay(1000); sb;                    // start serial o/p
  if(doOLED) {
    OzOled.init();                        // initialze Oscar OLED display
    OzOled.setHorizontalMode();           // set addressing mode to Horizontal Mode
    OzOled.setBrightness(Brightness);     // set brightness
    OzOled.clearDisplay();                // clear the screen and set start position to top left corner
    doClearOLED(0,7);                     // clear excess chars at end of line
    for(int x=0; x<8; x++) ops(" ",16,x); // clear excess chars at end of line
    ops("OLED NTP CLOCK",1,0); 
    osc(1,2);                             // set to line 2
    ops(" [Build "); opn((long) bld); ops("."); opn((long) rev); ops("]");

  if(doSerial) {
    spln(F("OLED NTP-Synchronized Soft CLOCK")); 
    spln(F("Adrian Jones, February 2016"));
    sp(F("Build ")); sp(bld);  sp(F(".")); spln(rev);
    spln(F("WiFi configuration at"));    
    WiFiManager wifiManager;                       // WiFiManager: Local intialization. Once its business is done, there is no need to keep it around
    // wifiManager.resetSettings();                 // reset saved settings   
    // wifiManager.setAPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));  //set custom ip for portal
    // wifiManager.autoConnect();                   // use this for auto generated name ESP + ChipID       
    wifiManager.autoConnect("NTP_SynClock_Connect");   // fetches ssid and pass from eeprom and tries to connect. 
    // If does not connect with stored information, start an access point with this name and stay in blocking loop awaiting configuration

  if(doSerial) spln(F("Connected to WiFi"));       // if you get here you have connected to the WiFi
  if(doOLED)   ops("WiFi connected",1,6);    


  RTC.begin(DateTime(F(__DATE__), F(__TIME__)));    // initially set to compile date & time
  if(doSerial) sp(F("Starting UDP with Local Port ")); spln(udp.localPort());
  if(doOLED)   ops("Start UDP",1,4); opn((long)udp.localPort());
  doNTP=true;                                       // get NTP timestamp immediately


//                                      MAIN LOOP
void loop(void) {

  if(cm%60==0) doNTP=true;                 // set flag every hour

  // ****************************** Execute every second ***********************
  if(cs != os && !doNTP) {                              
    if(doSerial) {
      sp(F("Time: "));   sp(ch);  sp(F(":")); if(cm <10) sp("0");  sp(cm);  sp(F(":")); if(cs <10) sp("0");  sp(cs);
      sp(F("\tDate: ")); sp(cyr); sp(F("/")); if(cmo <10) sp("0"); sp(cmo); sp(F("/")); if(cdy <10) sp("0"); sp(cdy);
    if(doOLED) {
      ops("Date: ",1,2); if(cyr<10) ops("0"); opn((long)cyr); ops("/"); if(cmo<10) ops("0"); opn((long)cmo); ops("/"); if(cdy<10) ops("0"); opn((long)cdy);
      ops("Time: ",1,4); if(ch <10) ops(" "); opn((long)ch);  ops(":"); if(cm <10) ops("0"); opn((long)cm);  ops(":"); if(cs <10) ops("0"); opn((long)cs);
    os = cs;
  // *************************************************************************

  // ****************************** Execute every hour ***********************
  if(doNTP) {
    sendNTPpacket(timeServer);            // send an NTP packet to a time server
    delay(1000);                          // wait to see if a reply is available
    int cb = udp.parsePacket();           // get packet (if available)
    if (!cb) {
      if(doSerial) spln(F("... no packet yet"));
      if(doOLED)   ops("No packet yet",1,4);
    } else {
      if(doSerial) sp(F("... NTP packet received with ")); sp(cb); spln(F(" bytes"));     // We've received a packet, read the data from it, NTP_PACKET_SIZE);                            // read the packet into the buffer

      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);  // timestamp starts at byte 40 of packet. It is 2 words (4 bytes) long
      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);   // Extract each word and...
      unsigned long secsSince1900 = highWord << 16 | lowWord;             // ... combine into long: NTP time (seconds since Jan 1 1900):

      const unsigned long seventyYears = 2208988800UL;                    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
      unsigned long epoch = secsSince1900 - seventyYears;                 // subtract seventy years to get to 1 Jan. 1900:

      if(doSerial) {
        // sp("Seconds since Jan 1 1900 = " );  spln(secsSince1900);
        // sp("Unix time = ");  spln(epoch);                       // print Unix time:
      int tz = 5;                                            // adjust for EST time zone
      DateTime gt(epoch - (tz*60*60));                       // obtain date & time based on NTP-derived epoch...
      tz = IsDST(gt.month(),, gt.dayOfTheWeek())?4:5;  // if in DST correct for GMT-4 hours else GMT-5
      DateTime ntime(epoch - (tz*60*60));                    // if in DST correct for GMT-4 hours else GMT-5
      RTC.adjust(ntime);                                     // and set RTC to correct local time   
      nyr = ntime.year()-2000;                       
      nmo = ntime.month();
      ndy =;    
      nh  = ntime.hour(); if(nh==0) nh=24;                   // adjust to 1-24            
      nm  = ntime.minute();                     
      ns  = ntime.second();                     

      sp(F("... NTP packet local time: [GMT - ")); sp(tz); sp(F("]: "));       // Local time at Greenwich Meridian (GMT) - offset  
      if(nh < 10) sp(F(" ")); sp(nh);  sp(F(":"));          // print the hour 
      if(nm < 10) sp(F("0")); sp(nm);  sp(F(":"));          // print the minute
      if(ns < 10) sp(F("0")); sp(ns);                       // print the second

      sp(F(" on "));                                        // Local date
      if(nyr < 10) sp(F("0")); sp(nyr);  sp(F("/"));        // print the year 
      if(nmo < 10) sp(F("0")); sp(nmo);  sp(F("/"));        // print the month
      if(ndy < 10) sp(F("0")); spln(ndy);                   // print the day

      ops("NTP:  ",1,6);                                    // NTP (and adjusted) time
      if(nh <10) ops(" "); opn((long)nh); ops(":");   
      if(nm <10) ops("0"); opn((long)nm); ops(":"); 
      if(ns <10) ops("0"); opn((long)ns);

     // *************************************************************************

11 thoughts on “NetClock: NTP Synchronized OLED Clock: Update

  1. siddesh

    How to use pins of D1 board as output pins as we use in Arduino, I’m using seperate LED’s as seven segments using multiplexing method so I need total 12 output pins for controlling LED’s. Can we use pins available on the board as output pins, if yes how to define pins to use it. Thanks in advance

    1. Adrian Post author

      The WeMos device does not have enough pins to do what you need. You may wish to consider using a couple of I2C port expanders (look for PCF8574 IO Expansion Boards) to give you the pins required. As for pin mapping, look at the WeMos site ( for a detailed description of the pins available and their use.
      Hope that this helps.
      … Adrian

  2. Svein Eirik Olsen

    Hi, great reading.
    I’m trying to compile, but i have issues with ozoled.h. do you have a link for download the correct one?

  3. Peter Kolbe

    Did Anynodu Come right with the NTP packets side.

    I am getting :
    OLED NTP-Synchronized Soft CLOCK
    Adrian Jones, February 2016
    Build 3.3
    WiFi configuration at

    *WM: AutoConnect
    *WM: Connecting as wifi client…
    *WM: Already connected. Bailing out.
    *WM: IP Address:
    Connected to WiFi
    Starting UDP with Local Port 2390
    Sending UDP NTP packet request
    … no packet yet
    Sending UDP NTP packet request
    … no packet yet

    I have even sniffed the wireless network with wireshark, and the D1 Mini is not sending out any ntp packets at all.
    But I can ping it fine.

    1. Adrian Post author

      I have just loaded the same software onto a bare board WeMos D1 Mini and get the following response from the console:

      OLED NTP-Synchronized Soft CLOCK
      Adrian Jones, February 2016
      Build 3.3
      WiFi configuration at

      *WM: AutoConnect
      *WM: Connecting as wifi client…
      *WM: Using last saved values, should be faster
      *WM: Connection result:
      *WM: 3
      *WM: IP Address:
      Connected to WiFi
      Starting UDP with Local Port 2390
      Sending UDP NTP packet request
      … NTP packet received with 48 bytes
      … NTP packet local time: [GMT – 4]: 10:29:36 on 16/09/03

      Time: 10:29:36 Date: 16/09/03
      Time: 10:29:37 Date: 16/09/03
      Time: 10:29:38 Date: 16/09/03
      Time: 10:29:39 Date: 16/09/03
      Time: 10:29:40 Date: 16/09/03
      Time: 10:29:41 Date: 16/09/03
      Time: 10:29:42 Date: 16/09/03
      Time: 10:29:43 Date: 16/09/03
      Time: 10:29:44 Date: 16/09/03
      Time: 10:29:45 Date: 16/09/03
      Time: 10:29:46 Date: 16/09/03
      Time: 10:29:47 Date: 16/09/03
      Time: 10:29:48 Date: 16/09/03
      Time: 10:29:49 Date: 16/09/03

      Perhaps the problem is with your WiFi connection… I would suggest that you reset settings (uncomment line 151), upload and then comment out line 151 and reload. Then go to the WiFi settings and reenter your WiFi credentials.
      Please let me know if this fixes your problem.

      … Adrian

      1. Peter Kolbe

        Thanks, but that Didn’t work
        I have managed to find another NTP client sketch, that works fine.
        I also didn’t need the OLED Code (I am using Shift Register LED Drivers (This is a big clock that uses LED Striplights in a 7 segment configuration), so that works out fine as well.

          1. Peter Kolbe

            Here is the code that is running for me.
            I have added the WifiManager, as well as Time Library to make conversion easier.

            Big Digit Clock using the
            Wemos D1 MINI
            DS1307 RTC
            DS18B20 (Maybe)
            And Shift Register LED Display
            #define bld 4
            #define rev 0


            #include // Conversion

            //needed for WifiManagerlibrary
            #include //

            String esid, epass; // eprom stored network

            //char ssid[] = “”; // your network SSID (name)
            //char pass[] = “”; // your network password

            unsigned int localPort = 2390; // local port to listen for UDP packets

            /* Don’t hardwire the IP address or we won’t get the benefits of the pool.
            Lookup the IP address for the host name instead */
            //IPAddress timeServer(129, 6, 15, 28); // NTP server
            IPAddress timeServerIP; // NTP server address
            const char* ntpServerName = “”;

            const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

            byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

            // A UDP instance to let us send and receive packets over UDP
            WiFiUDP udp;

            void setup()

            Serial.println(F(“Peter Kolbe, September 2016”));
            Serial.println(F(“Build “)); Serial.print(bld); Serial.print(F(“.”)); Serial.println(rev);
            Serial.println(F(“WiFi configuration at”));

            WiFiManager wifiManager; // WiFiManager: Local intialization. Once its business is done, there is no need to keep it around
            // wifiManager.resetSettings(); // reset saved settings
            // wifiManager.setAPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); //set custom ip for portal
            // wifiManager.autoConnect(); // use this for auto generated name ESP + ChipID
            wifiManager.autoConnect(“My_Big_Clock”); // fetches ssid and pass from eeprom and tries to connect.
            // If does not connect with stored information, start an access point with this name and stay in blocking loop awaiting configuration


            // We start by connecting to a WiFi network
            // Serial.print(“Connecting to “);
            // Serial.println(ssid);
            // WiFi.begin(ssid, pass);

            // while (WiFi.status() != WL_CONNECTED) {
            // delay(500);
            // Serial.print(“.”);
            // }
            // Serial.println(“”);

            // Serial.println(“WiFi connected”);
            // Serial.println(“IP address: “);
            // Serial.println(WiFi.localIP());

            Serial.println(“Starting UDP”);
            Serial.print(“Local port: “);

            void loop()

            //NTP FUNCTIONS

            unsigned long GetNTPTime () {
            //get a random server from the pool
            WiFi.hostByName(ntpServerName, timeServerIP);

            sendNTPpacket(timeServerIP); // send an NTP packet to a time server
            // wait to see if a reply is available

            int cb = udp.parsePacket();
            if (!cb) {
            Serial.println(“no packet yet”);
            else {
            // We’ve received a packet, read the data from it
  , NTP_PACKET_SIZE); // read the packet into the buffer

            //the timestamp starts at byte 40 of the received packet and is four bytes,
            // or two words, long. First, extract the two words:

            unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
            unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
            // combine the four bytes (two words) into a long integer
            // this is NTP time (seconds since Jan 1 1900):
            unsigned long secsSince1900 = highWord <= 2016) && (year(epoch) = 1) && (month(epoch) = 1) && (day(epoch) = 1) && (hour(epoch) = 1) && (minute(epoch) = 1) && (second(epoch) >> NTP TIME VALID, SETTING RTC TO NTP”));
            // rtc.adjust(DateTime(year(epoch), month(epoch), day(epoch), hour(epoch), minute(epoch), second(epoch)));
            return (1);
            } else {
            Serial.print(F(“>>> NTP TIME INVALID, NOT SETTING RTC THIS TIME”));
            return (0);


  4. Tony


    Do you have previous versions of your code?
    After some initial problems with the OzOLED library I’ve managed to get it compiled and onto my WeMos D1 but I’m having problems with the NTP packets and also getting the OLED to display anything.

    1. Adrian Post author

      Thanks for your comment. Can you please describe what you are doing, the hardware that you are using and the interface between the ESP and the OLED. Are you getting anything on your display?
      In my case, I am using a 0.96″ OLED and interfacing it to a WeMos D1 Mini ESP8266 board using I2C interface (GPIO5 -> SCL, GPIO4 -> SDA). The code posted works as is. Can you be more specific with the problem that you are having?

      1. Tony

        Thanks Adrian I appreciate you picking up my comment and coming back to me.

        I’ve progressed a little since i made it. Firstly I seem to have a bad batch of OLEDs which were 128×32 rather than 128×64 they were reported as. This caused me some of my initial issues.
        I found this after trying to get the more official library to work and found it didn’t.

        I also found I had to make edits to the OzOled library .h and .cpp file to get them to work at all. (the init setup part needed amendments).

        The OLED now works. 🙂

        However I now find it reports no NTP packets are ever received. Not sure why, I can see the WIFI device join the WIFI network and I can ping it but it reports no NTP.

        I’ve tried several different NTP server addresses.

        I’m still trouble shooting this but it looks like not NTP packet is sent out based on my routers packet counters.



Come on... leave a comment...