Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 02:06 13 Nov 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : ESP8266 Neopixel ntp-sourced clock

Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10572
Posted: 08:50pm 18 Nov 2017
Copy link to clipboard 
Print this post

Something completely different

Behind dark smoked perspex 7:17:38



Development environment




This little development uses an ESP8266 programmed with the Arduino IDE.

The ESP8266 connects to my WIFI and downloads the current time and date from an ntp server every 10 minutes. In between the time is updated on the NEOPIXEL ring from the ESP's crystal oscillator. Red is the seconds, blue the minutes and green the hours. The clock automatically compensates for daylight saving. The Date is shown using a MAX7219 7-segment LCD display

Getting the ESP8266 up and running with the Arduino IDE is simple - just google. I then use a standard USB/UASRT to program the ESP. As always with the ESP GPIO0 must be connected to GND to program and then the chip RESET.

The code is attached. It is currently somewhat UK/Europe specific in terms of the summertime calculation but this is easily modified.

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Adafruit_NeoPixel.h>
#include <time.h>
//
// Local set up, DST flexibility NOT YET IMPLEMENTED IN CODE
//
#define timezone 0 //specify the offset in minutes +/- from GMT
#define hemisphere 0 //specify northern (0) or southern hemisphere (1)
#define ntpfrequency 10 //specify minutes between ntp updates
#define DSTstartSunday 0 //specify which Sunday to start daylight saving (use 0 for last Sunday)
#define DSTendSunday 0 //specify which Sunday to end daylight saving (use 0 for last Sunday)
#define DSTstartmonth 3 //specify month to start daylight savings (1-12)
#define DSTendmonth 10 //specify month to start daylight savings (1-12)
const char* ntpServerName = "3.uk.pool.ntp.org";
char ssid[] = "yourSSID"; // your network SSID (name)
char pass[] = "yourPASSWORDr"; // your network password
//
// Which pin on the ESP8266 is connected to the NeoPixels?
#define PIN 2
// How many NeoPixels are attached to the ESP8266?
#define NUMPIXELS 60
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

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 timeServerIP; // NTP server address

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
unsigned long currentMillis,previousMillis=0;
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
int interval=1000;
int lit=NUMPIXELS-1;
#define YEAR0 1900 /* the first year */
#define EPOCH_YR 1970 /* EPOCH = Jan 1 1970 00:00:00 */
#define SECS_DAY (24L * 60L * 60L)
#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
#define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365)
#define FIRSTSUNDAY(timp) (((timp)->tm_yday - (timp)->tm_wday + 420) % 7)
#define FIRSTDAYOF(timp) (((timp)->tm_wday - (timp)->tm_yday + 420) % 7)
#define TIME_MAX ULONG_MAX
#define ABB_LEN 3
#define MAX_CS 12
#define MAX_CLK 14
#define MAX_DO 13
// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
const uint8_t MAXch[10]={0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B};
const int _ytab[2][12] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
/* Number of days per month (except for February in leap years). */
static const int monoff[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};

static int is_leap_year(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}

static int leap_days(int y1, int y2)
{
--y1;
--y2;
return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400);
}
time_t timegm(const struct tm *tm)
{
int year;
time_t days;
time_t hours;
time_t minutes;
time_t seconds;

year = 1900 + tm->tm_year;
days = 365 * (year - EPOCH_YR) + leap_days(EPOCH_YR, year);
days += monoff[tm->tm_mon];

if (tm->tm_mon > 1 && is_leap_year(year))
++days;
days += tm->tm_mday - 1;

hours = days * 24 + tm->tm_hour;
minutes = hours * 60 + tm->tm_min;
seconds = minutes * 60 + tm->tm_sec;

return seconds;
}
struct tm * gmtime(const time_t *timer)
{
static struct tm br_time;
struct tm *timep = &br_time;
time_t time = *timer;
unsigned long dayclock, dayno;
int year = EPOCH_YR;

dayclock = (unsigned long)time % SECS_DAY;
dayno = (unsigned long)time / SECS_DAY;

timep->tm_sec = dayclock % 60;
timep->tm_min = (dayclock % 3600) / 60;
timep->tm_hour = dayclock / 3600;
timep->tm_wday = (dayno + 4) % 7; /* day 0 was a thursday */
while (dayno >= YEARSIZE(year)) {
dayno -= YEARSIZE(year);
year++;
}
timep->tm_year = year - YEAR0;
timep->tm_yday = dayno;
timep->tm_mon = 0;
while (dayno >= _ytab[LEAPYEAR(year)][timep->tm_mon]) {
dayno -= _ytab[LEAPYEAR(year)][timep->tm_mon];
timep->tm_mon++;
}
timep->tm_mday = dayno + 1;
timep->tm_isdst = 0;

return timep;
}
void sendcommand(uint16_t MAXregister, uint16_t command){
uint16_t tosend=(MAXregister<<8) + command;
for(int i=0; i<16; i++) // There are 16 bits in a command
{
digitalWrite(MAX_DO, bitRead(tosend, 15-i)); // Set MOSI
delayMicroseconds(5);
digitalWrite(MAX_CLK, HIGH); // SCK high
delayMicroseconds(5);
digitalWrite(MAX_CLK, LOW); // SCK low
delayMicroseconds(5);
}
digitalWrite(MAX_CS,HIGH);
delayMicroseconds(50);
digitalWrite(MAX_CS,LOW);
}
// MAX7219 Registers
#define MAXdecode 0x09
#define intens 0x0A
#define scanlim 0x0B
#define Shutdwn 0x0C
#define DigTest 0x0F
#define Init_Inten 0x0f
#define No_Digits 7
#define Set_On 1
#define Set_Off 0
void initmax7219(void){
pinMode(MAX_CS, OUTPUT);
pinMode(MAX_CLK, OUTPUT);
pinMode(MAX_DO, OUTPUT);
digitalWrite(MAX_CS, LOW);
digitalWrite(MAX_CLK, LOW);
delay(10);
sendcommand(MAXdecode,0); //we want straight access to the elements of the LEDs
sendcommand(intens,Init_Inten);
sendcommand(scanlim,No_Digits);
sendcommand(Shutdwn, Set_On);
sendcommand(DigTest, Set_Off);
for(int i=1; i<=8; i++){
sendcommand(i,0);
}
}
void setup()
{
initmax7219();
pixels.begin(); // This initializes the NeoPixel library.
for(int i=0;i<NUMPIXELS;i++){
pixels.setPixelColor(i, pixels.Color(0,0,0));
}
pixels.show();
Serial.begin(115200);
Serial.println();
Serial.println();

// 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");
udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(udp.localPort());

}
int summertimeUK(unsigned long epoch){
struct tm *sos;
struct tm stma;
sos=&stma;
struct tm *eos;
struct tm etma;
eos=&etma;
struct tm *now;
struct tm snow;
now=&snow;
int sundaycount=0;
int dayofmonth=31;
time_t sostimestamp, eostimestamp;
now=gmtime((const time_t*)&epoch);
do {
sos->tm_year = now->tm_year;
sos->tm_mon = 2;
sos->tm_mday = dayofmonth;
sos->tm_hour = 1;
sos->tm_min = 0;
sos->tm_sec = 0;
sostimestamp = timegm(sos);
sos=gmtime(&sostimestamp);
if(sos->tm_wday==0)sundaycount=1;
dayofmonth--;
} while(!sundaycount);
sundaycount=0;
dayofmonth=31;
do {
eos->tm_year = now->tm_year;
eos->tm_mon = 9;
eos->tm_mday = dayofmonth;
eos->tm_hour = 1;
eos->tm_min = 0;
eos->tm_sec = 0;
eostimestamp = timegm(eos);
eos=gmtime(&eostimestamp);
if(eos->tm_wday==0)sundaycount=1;
dayofmonth--;
} while( !sundaycount);
if(epoch<sostimestamp || epoch>eostimestamp){
return 0;
} else {
return 1;
}
}
void printdatetime(const struct tm *tm){
Serial.print(tm->tm_mday);
Serial.print('-');
if ( tm->tm_mon < 9 ) {
// In the first 10 minutes of each hour, we'll want a leading '0'
Serial.print('0');
}
Serial.print(tm->tm_mon+1);
Serial.print('-');
Serial.print(tm->tm_year % 100);
Serial.print(' ');
Serial.print(tm->tm_hour); // print the hour (86400 equals secs per day)
Serial.print(':');
if ( tm->tm_min < 10 ) {
// In the first 10 minutes of each hour, we'll want a leading '0'
Serial.print('0');
}
Serial.print(tm->tm_min); // print the minute (3600 equals secs per minute)
Serial.print(':');
if ( tm->tm_sec < 10 ) {
// In the first 10 seconds of each minute, we'll want a leading '0'
Serial.print('0');
}
Serial.println(tm->tm_sec); // print the second
sendcommand(8,MAXch[tm->tm_mday / 10]);
sendcommand(7,MAXch[tm->tm_mday % 10]);
sendcommand(5,MAXch[(tm->tm_mon+1) / 10]);
sendcommand(4,MAXch[(tm->tm_mon+1) % 10]);
sendcommand(2,MAXch[(tm->tm_year % 100) / 10]);
sendcommand(1,MAXch[tm->tm_year % 10]);
// sendcommand(8,MAXch[tm->tm_hour / 10]);
// sendcommand(7,MAXch[tm->tm_hour % 10]);
// sendcommand(5,MAXch[tm->tm_min / 10]);
// sendcommand(4,MAXch[tm->tm_min % 10]);
// sendcommand(2,MAXch[tm->tm_sec / 10]);
// sendcommand(1,MAXch[tm->tm_sec % 10]);
sendcommand(3,1);
sendcommand(6,1);
}
void loop()
{
int done=0;
unsigned long epoch, DST;
struct tm *tm;
struct tm tma;
tm=&tma;

//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
delay(900);

int cb = udp.parsePacket();
if (!cb) {
Serial.println("no packet yet");
done=ntpfrequency*60-10; //try again in 10 seconds
} else {
Serial.print("packet received, length=");
Serial.println(cb);
// We've received a packet, read the data from it
udp.read(packetBuffer, 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, esxtract 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 << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = " );
Serial.println(secsSince1900);
epoch=secsSince1900-2208988800UL;
tm=gmtime((const time_t*)&epoch);
// print the hour, minute and second:
Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT)
printdatetime(tm);
}
// wait six hundred seconds before asking for the time again
do {
int hourpos;
ESP.wdtFeed();
currentMillis = millis();
if ((unsigned long)(currentMillis - previousMillis) >= interval) {
previousMillis = currentMillis;
// It's time to do something!
// Use the snapshot to set track time until next event
if(done)epoch++;
DST=epoch;
DST+=(timezone*60);
if(summertimeUK(DST)) DST+=3600;
tm=gmtime((const time_t*)&DST);
for(int i=0;i<NUMPIXELS;i++){
pixels.setPixelColor(i, pixels.Color(1,1,1));
if(!(i % 5))pixels.setPixelColor(i, pixels.Color(10,10,10));
}
for(int i=0;i<NUMPIXELS;i+=15){
pixels.setPixelColor(i, pixels.Color(70,70,70));
}
pixels.setPixelColor(59, pixels.Color(30,30,30));
pixels.setPixelColor(1, pixels.Color(30,30,30));
hourpos=(tm->tm_hour % 12) * 5 + (tm->tm_min / 12);
pixels.setPixelColor(tm->tm_sec, pixels.Color(170,0,0));
pixels.setPixelColor(tm->tm_min, pixels.Color(0,0,255));
pixels.setPixelColor(hourpos, pixels.Color(0,170,0));
if(tm->tm_sec==tm->tm_min)pixels.setPixelColor(tm->tm_min, pixels.Color(170,0,255));
if(tm->tm_min==hourpos)pixels.setPixelColor(hourpos, pixels.Color(0,170,255));
if(tm->tm_sec==hourpos)pixels.setPixelColor(hourpos, pixels.Color(170,170,0));
if(tm->tm_sec==tm->tm_min && tm->tm_min==hourpos)pixels.setPixelColor(hourpos, pixels.Color(170,170,255));
pixels.show();
done++;
printdatetime(tm);
delay(900);
}
} while(done<ntpfrequency*60);
done=0;
}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
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;

// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
Edited by matherp 2017-11-20
 
djuqa

Guru

Joined: 23/11/2011
Location: Australia
Posts: 447
Posted: 12:25am 19 Nov 2017
Copy link to clipboard 
Print this post

Great stuff
I have also gone over to the darkside (aka ARDUINO ide/ESP8266)
With ESP8266 (Including Arduino formfactor) modules so cheap, yet so capable of doing amazing things.
Now bought several esp8266 & Arduino boards less than third of the price of the 2 Explore64 kits I killed trying to solder the MONGREL SMD PIC32 MCU.Edited by djuqa 2017-11-20
VK4MU MicroController Units

 
cheeseprader
Newbie

Joined: 06/10/2016
Location: United States
Posts: 7
Posted: 02:24am 21 Nov 2017
Copy link to clipboard 
Print this post

This would be a great project/program for a 28 pin micromite using a DS3231 module. I think I saw someone using ws2812s or APA102s, and the MAX7219 module on the tbs forum. Might have to do a search... You could use one of those I2C adafruit 7 segment displays too I guess with the circular led display.

Another great project Matherp!
 
WhiteWizzard
Guru

Joined: 05/04/2013
Location: United Kingdom
Posts: 2959
Posted: 04:06am 21 Nov 2017
Copy link to clipboard 
Print this post

That NeoPixel ring is brilliant - never seen them as a 'clock design' before (i.e. with a suitable quantity of LEDs).

I have a 44pin MKI Micromite (MX150) that has been running a 40x8 red led matrix digital GPS clock for over 3years now. My children are just starting to understand how to read analogue time so I wanted to somehow 'retro-fit' an analogue feature to the matrix clock.

So that 'ring' is the perfect addition but I will definitely be doing it as 'cheeseprader' suggests; i.e. for a MM rather than the ESP.

THANKS for sharing this brilliantly simple design idea.

WWEdited by WhiteWizzard 2017-11-22
 
zoggermaster
Newbie

Joined: 21/11/2017
Location: Germany
Posts: 2
Posted: 10:21am 29 Nov 2017
Copy link to clipboard 
Print this post

Great, 2 questions I would have. Would it be possible to turn the clock so that it runs the other way and that is the start pixel on the 6. Almost at LED30.Dann I would provide my LED clock with your sketch and obstruct additionally ne date display.
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10572
Posted: 11:19am 29 Nov 2017
Copy link to clipboard 
Print this post

  Quote  Would it be possible to turn the clock so that it runs the other way and that is the start pixel on the 6.


I assume you want this so the wires connect at the bottom? This should be easy to do. Just find all the "pixels.setPixelColor" statements and then change the first parameter by adding 30 modulus 60. e.g.

pixels.setPixelColor(hourpos, pixels.Color(0,170,0));


becomes:

pixels.setPixelColor((hourpos+30) % 60, pixels.Color(0,170,0));
Edited by matherp 2017-11-30
 
zoggermaster
Newbie

Joined: 21/11/2017
Location: Germany
Posts: 2
Posted: 01:27pm 30 Nov 2017
Copy link to clipboard 
Print this post

Yeah exact. My clock is build from another instructions (sorry for my Bad English). Changed that and that's how it works. Now the clock only has to turn clockwise ^^.
 
rxmas
Newbie

Joined: 28/06/2019
Location: United States
Posts: 1
Posted: 05:17pm 28 Jun 2019
Copy link to clipboard 
Print this post

So it took me a couple of days but I finally got this sort of working. I didn't put in the 7 segment displays, I just wanted the ntp and ring clock.

My problem is it's running counter clockwise. How do I change the direction? I'm not very good at coding but I know there is a lot of excess code in this sketch so I'm not sure where to even look. I saw the reply to change the position of the 12 oclock, I'm going to need that as well, but the direction has me stumped.


Thank you for any help.
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10572
Posted: 06:03pm 28 Jun 2019
Copy link to clipboard 
Print this post

  Quote  My problem is it's running counter clockwise. How do I change the direction?

Try:


pixels.setPixelColor(59-tm->tm_sec, pixels.Color(170,0,0));
pixels.setPixelColor(59-tm->tm_min, pixels.Color(0,0,255));
pixels.setPixelColor(59-hourpos, pixels.Color(0,170,0));


and to also move the start point


pixels.setPixelColor(59-((tm->tm_sec + 30) % 60), pixels.Color(170,0,0));
pixels.setPixelColor(59-((tm->tm_min + 30) % 60), pixels.Color(0,0,255));
pixels.setPixelColor(59-((hourpos + 30) % 60), pixels.Color(0,170,0));
Edited by matherp 2019-06-30
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025