Adrian Smith
Published © GPL3+

LED clock with auto time set over Wi-Fi / NTP protocol

A seven segment LED clock that connects to the internet to automatically set the time and sync on the hour every hour.

IntermediateFull instructions provided1 hour759
LED clock with auto time set over Wi-Fi / NTP protocol

Things used in this project

Hardware components

NodeMCU V3 ESP8266-12E
×1
ICM7218AIPI
×1
Broadcom HDSP-5501
×8
33uf capacitor 10V (generic)
×1
Micro-B USB cable, right angle
×1
Deep photo frame 6x4"
×1
Capacitor 100 nF
Capacitor 100 nF
×2
JLCPCB Customized PCB
JLCPCB Customized PCB
×1

Software apps and online services

Arduino IDE
Arduino IDE
ESP8266 support for Arduino

Story

Read more

Schematics

Schematic

KiCad files

Code

Source Code

C/C++
The source code of the clock
// ***DESCRIPTION, HOW TO USE AND CREDITS***

// V1.2 - added DST mode on / off indicator on rightmost decimal point of display.

// Full instructions can be found at https://adrian-smith31.co.uk/blog

// This program will create an access point to configure wifi. SSID "inetclock", password is "password"
// Connect to this AP with an Android or IOS phone and sign in to the network. Config box will appear.
// The clock will do a display test and display dashes to indicate it is connecting to the WiFi network.
// Pressing the function button will toggle daylight saving time on and off.

// Uses open source WiFi manager for easy wireless configuration by Tzapu https://github.com/tzapu
// Rest of libraries part of ESP8266 Arduino repository and installed by library manager in Arduino IDE.

// Written for NodeMCU version 1.0 on ESP-12E V3 module. Should work with others, pin assignments may need changing.
// Settings: CPU clock 80Mhz, flash size 4Mb, V2 lower memory, Vtables=flash, erase flash: only sketch, legacy

// ***TROUBLESHOOTING & PROBLEMS***

// Please use a terminal program such as terraterm to monitor serial output. Any error messages
// will be shown such as HTTP status codes plus any debug messages from the NodeMCU RTOS and / or debugger.
// Serial settings: 115200 baud, 8 bits, no parity, one stop bit. First line will show garbage, that's normal.
// If there is a problem connecting to the internet or WiFi config isn't set, display will show --HELP--
// use ESP8266 community V2.6.0 and change stack size to #define _stackSize (6748/4) 
// in stackthunk.cpp in the ESP8266 core files otherwise it just crashes with later 
// versions of the libraries. Stackthunk.cpp is in local appdata/arduinoxx where xx version no

#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <WiFiClientSecure.h> // for connecting to SSL sites
#include <EEPROM.h>
#include <ArduinoJson.h>
#include <stackthunk.h>
#include <ICM7218.h> // library also works with the ICM7228 with limited functions. Enough for this though.
#include <RTClib.h>
#include <Time.h>

// Configure the 10 OUTPUT pins used to interface with ICM7218: D0-D7, mode, write
ICM7218 myLED(D1, D2, D5, D6, D4, ICM7218::NO_PIN, ICM7218::NO_PIN, D3, D7, D8);

// Pins ID5 and ID6 are not used. Hardwired to CODEB decode mode.
// Accepts numbers 0-9, characters H,E,L,P,-,space and full stop. Full stop lights decimal point.
// Pin numbers not in sequence as some have pullups or specific functions at boot. Onboard LED will
// light when display is in normal operation. 
// CODEB character mode: supports digits 0-9, characters 'H', 'E', 'L', 'P',
// space (' ') and hyphen ('-') plus decimal points

//ESP8266WebServer server(80); // configure built in web server to port 80

WiFiClientSecure client;

int buttonState; // stores state of the DST adjust button
long utcOffsetInSeconds = 0; // change this if this gets sold outside of the UK. 3600 seconds per hour.
int timeset = 0;
int dst = 1; // inital high state so DST is off. Button goes low to turn it on.
int tempdisplay = 0; // flag to indicate in temperature display mode or not
int dstset = 0;
int tempc;
byte mode = 0;
const int buttonPin = D0;
const int shortPresstime = 500; // half a second
const int longPresstime = 2000; // 2 seconds
int lastBtnstate = 0;
unsigned long btnPressedtime = 0;
unsigned long btnReleasedtime = 0;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds); // a more local NTP server may work better
RTC_Millis rtc; // setup software RTC using CPU clock as timebase. This gets autocorrected every hour.

void setup() 
{
  pinMode(buttonPin, INPUT); // Pin D0 only has a pulldown resistor, not pullup. Use external pullup instead.
  myLED.setMode(ICM7218::CODEB);
  myLED.print("8.8.8.8.8.8.8.8."); // do a display test at power up
  delay(1000);
  myLED.print("--------"); // indicate a dash to show it's trying to connect to the internet
  WiFi.hostname("Inetclock1049"); // set up hostname consisting of Inetclock and last 4 digits of MAC address
  WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
  Serial.begin(115200);
  WiFiManager wm;
  wm.setAPCallback(configModeCallback); // this runs if it can't connect to the internet. Function for this below.
  timeClient.begin(); // start NTP client
  
   //reset settings - wipe network config (for testing purposes only)
    //wm.resetSettings();

    // Automatically connect using saved credentials,
    // if connection fails, it starts an access point with the specified name ( "YTcounterAP"),
    // if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
    // then goes into a blocking loop awaiting configuration and will return success result

    bool res;   
    res = wm.autoConnect("inetclock","password"); // password protected ap
        
    Serial.print("*NET: IP address: "); 
    Serial.println(WiFi.localIP());
    char IP[12];
    String LocalIP = String() + "1P" + "." + WiFi.localIP()[2] + "." + WiFi.localIP()[3]; // display last two octets of IP address
    strcpy(IP, LocalIP.c_str());
    myLED.print(IP);
    delay(3000);
    client.setInsecure(); // required for version 2.5 and above
    timesync(); // sync software RTC with NTP clock at boot. 
}

void loop() 
{
   //timedisplay();
   buttoncheck();

   if (tempdisplay == 1)

   {
     displaytemp();
   }

   else 

   {
     timedisplay();
   }
   
   DateTime now = rtc.now();
   
   if (now.minute() == 0 && now.second() == 0 && timeset == 0)

   {
    timesync(); // sync the time on the hour. Avoids repeated NTP calls & possibly getting IP banned by NTP service.
    timeset = 1; // this prevents timesync running over and over till zero second has passed.
    Serial.println(F("NTP server sync run. If time still incorrect, power cycle unit or NTP server is down"));    
   }

   if (now.minute() == 10 && now.second() == 0 && timeset == 1)

   {
     timeset = 0; // reset timeset flag after 10 mins so time will update again on the next hour
   }

}

//===============================================================
// This function runs if it can't connect to wifi network
//===============================================================

void configModeCallback (WiFiManager *myWiFiManager) 
    {
    Serial.println("Entered config mode");
    Serial.println(WiFi.softAPIP());
    Serial.println(myWiFiManager->getConfigPortalSSID());
    myLED.print("--HELP--"); 
    }

//===============================================================
// This function monitors the button & selects data to display
//===============================================================

void buttoncheck()
{
  
  buttonState = digitalRead(buttonPin);

  if (lastBtnstate == 1 && buttonState == 0) // button pressed
     btnPressedtime = millis();
  else if (lastBtnstate == 0 && buttonState == 1) // button released
  {
     btnReleasedtime = millis();

  long btnPressduration = btnReleasedtime - btnPressedtime;

  if (btnPressduration < shortPresstime)
     dst = !dst; // toggle DST mode
     //tempdisplay = !tempdisplay;

  if (btnPressduration > longPresstime)
     dst = !dst; // toggle DST mode
  }

  lastBtnstate = buttonState;     
     
 }

//===============================================================
// This function syncronises the software RTC with NTP time
//===============================================================

void timesync()
{
    timeClient.update();
    DateTime now = rtc.now();
    int hr,mins,sec,days;
    sec = timeClient.getSeconds();
    mins = timeClient.getMinutes();
    hr = timeClient.getHours();
    days = timeClient.getDay();
    rtc.adjust(DateTime(2021,1,1,hr,mins,sec)); // set the time (date doesn't really matter)
}

//===============================================================
// This function displays the temperature from an analog sensor
//===============================================================

void displaytemp()

{
  tempc = analogRead(A0) * 3300/1024;
  Serial.printf("Temperature =  %02u.%1uC\r\n", tempc / 10, tempc % 10);
  char tempcdata[8];
  char temp[8]; 
  itoa(tempc /10, temp, 10); // get first digit, convert integer to char and put in temp array
  strcpy(tempcdata, temp); // copy temp array to tempcdata array
  strcat(tempcdata, "."); // insert decimal point
  itoa(tempc %10, temp, 10); // get second digit and convert to char then put in temp array
  strcat(tempcdata, temp); // append to temp array
  myLED.print(tempcdata); // write to the ICM7228
  delay(100);
  
}

//===============================================================
// This function displays the time with leading zero correction
//===============================================================

void timedisplay() // if / else statements ahoy! A mess. But it works...

{
    DateTime now = rtc.now();
    char timedata[10];
    char temp[8]; 
   
    if (dst == 1 && dstset == 0)
      {
        Serial.println("DST ON");
        dstset = 1;
      }

   else if (dst == 0 && dstset == 1 ) 
      {
        Serial.println("DST OFF");
        dstset = 0; 
      }

    if (now.hour() <10)
    
     {
        if (dstset == 1 && (now.hour() == 9)) // do not add 0 if hour +1 is 10
        {
          itoa(now.hour()+1, temp, 10); // convert integer output from RTC, add 1 then store in char temp array
          strcpy(timedata, temp); // copy to timedata array
          strcat(timedata, "-"); // append to timedata array
        }
    
       else if (dstset == 1) // only add leading zero if hour +1 is 9 or less
       
        {
          itoa(0, temp, 10); // convert integer 0 to char and put in temp array
          strcpy(timedata, temp); // copy temp array to timedata array
          itoa(now.hour()+1, temp, 10); // convert integer output from RTC, add 1 then store in char temp array
          strcat(timedata, temp); // append to timedata array
          strcat(timedata, "-"); // append to timedata array
        }
        
        else
        {
          itoa(0, temp, 10);
          strcpy(timedata, temp);
          itoa(now.hour(), temp, 10); // convert int output from RTC, DO NOT add 1 then store in char temp array
          strcat(timedata, temp);
          strcat(timedata, "-");
        }
     }

     else
     {
        if (dstset == 1)
        {
          // this replaces 24 with 00 at midnight if DST is on as it adds one hour to whatever now.hour() is.
          if (now.hour() == 23) 
          {
            itoa(0, temp, 10);
            strcpy(timedata, temp);
            strcat(timedata, "0");
            strcat(timedata, "-");
          }

          else 
          {
           itoa(now.hour()+1, temp, 10);
           strcpy(timedata, temp);
           strcat(timedata, "-");
          }
        }
        
        else
        {
          itoa(now.hour(), temp, 10);
          strcpy(timedata, temp);
          strcat(timedata, "-");
        }
     }

    if (now.minute() <10)
    {
      strcat(timedata, "0");
      itoa(now.minute(), temp, 10);
      strcat(timedata, temp);
      strcat(timedata, "-");
    }

    else 
    {
      itoa(now.minute(), temp, 10);
      strcat(timedata, temp);
      strcat(timedata, "-");
    }
    if (now.second() <10)
     {
       strcat(timedata, "0");
       itoa(now.second(), temp, 10);
       
       if (dstset == 1)
       {
        strcat(timedata, temp);
        strcat(timedata, ".");
       }
       
       else 
       { 
       strcat(timedata, temp);
       }
     }

     else
     {
       itoa(now.second(), temp, 10);
       if (dstset == 1)
       {
        strcat(timedata, temp);
        strcat(timedata, ".");
       }
       
       else 
       { 
       strcat(timedata, temp);
       }
     }

    myLED.print(timedata);
    delay(10); // 10ms delay to allow ICM7228 to update. RTC keeps going whilst function is delayed
}

Credits

Adrian Smith

Adrian Smith

6 projects • 2 followers
Electronics engineer for around 10 years, repair electronic LED signs and other old electronics from the 1980 / 1990's.

Comments