Friday 27 April 2018

A clock that never needs to be set- ESP8266 WiFi-synchronized LED Clock with MAX7219

"Time, it needs time ..." Scorpions, Still Loving You
I like LED clocks. Waking up at night, you just need to glance at it for the time. No fumbling with the light switch, or peering at the radioactive hands of a mechanical clock. That way I have a much better chance of going back to sleep.

LED Clock


A smartphone works just the same except I have to keep moving it and it may not be there when I need the time. An LCD clock is much less legible. Or if the backlight is turned up, it is too bright as a bedside clock.

The trouble with an LED clock is they are not expensive enough to be made very accurate. Typically a quartz clock will be off by some 4 minutes a year. It gets worse if they lose mains power. They are battery-backed, and they keep the time, but with much less accuracy. Over a few power outtages, you can get an outrageously-wrong clock.

Ideally, my LED clock should synchronize itself over the Internet, much like my smartphone. Now if I can keep the large LED segment displays and replace the  electronics I would get my ideal clock.

The ESP8266 with a MAX7219 are perfect for this. The idea is to get the system working, then remove the MAX7219 7-segment displays and wire it to the much larger LEDs in the clock. There should be no trouble fitting an ESP-12E and MAX7219 into the clock.  

I would need a battery-backed 5V for the ESP8266. A lithium power bank circuit would do quite nicely. There is no need to set time as the unit will automatically correct itself once WiFi is available.

Earlier, I had a MAX7219 working with an ESP-12E NodeMCU ESP8266. The Arduino IDE has sample code for the WiFi time, ESP8266 NTPClient. I just need to paste my MAX7219 SPI code into it. No wiring needed.

ESP-12E ESP8266 Nodemcu with MAX7219


And it was so. In just one hour I have a self-correcting WiFi LED clock. The battery-backed power circuit still need to be designed and tested, the software may well need to be changed, and the ESP8266 does not boot up unless the MAX7219 is first disconnected, and last but not least it whole thing needs to be stuffed into the LED clock. 

It was over so quickly, it was only later I found there is already an existing ESP8266 project that does exactly that, courtesy of sebouh.

An Internet of Things for Industry sometimes require accurate reliable time, especially when it is part of a distributed control system. This would be a cheap way of adding NTP to an embedded control system.

There you have it, ESP8266 WiFi-synchronized LED Clock with MAX7219. Happy Trails.

/*
 * TimeNTP_ESP8266WiFi.ino
 * Example showing time sync to NTP time source
 *
 * This sketch uses the ESP8266WiFi library
 */

#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SPI.h>

class ESP_MAX7219
{
private:
    uint8_t _ss_pin;
    void SS_High()
    {
        digitalWrite(_ss_pin, HIGH);
    }
    void SS_Low()
    {
        digitalWrite(_ss_pin, LOW);
    }

public:
    ESP_MAX7219(uint8_t pin):_ss_pin(pin) {}
    void begin()
    {
        pinMode(_ss_pin, OUTPUT);
        SS_Low();
        delay(1000);
        SS_High(); // Default to high
    }

    void writeData(const uint8_t MSB, const uint8_t LSB)
    {
        SS_Low();
        SPI.transfer(MSB);
        SPI.transfer(LSB);
        SS_High();
    }
};

ESP_MAX7219 esp(SS);


void send(const uint8_t msb, const uint8_t lsb)
{
  esp.writeData(msb, lsb);
  delay(10);
}
void init_MAX7219(void)
{
    send(0x0F, 0x00);
    delay(100);
    Serial.println("Test mode off");
    send(0x09, 0xFF);
    Serial.println("BCD Mode all 8 digits");
    delay(100);
    send(0x0A, 0x03);
    Serial.println("Quarter brightness");
    delay(100);
    send(0x0B, 0x07);
    Serial.println("8 Digit Scan");
    delay(100);
    send(0x0C, 0x01); // Normal Operation
    Serial.println("Shutdown off");
    delay(100);
}

uint8_t put_bcd(const uint8_t bcd)
{
  if (bcd>9)
    return 0x00;
  send(0x08, bcd);
  delay(10);
  return 0x01;
}

static const uint8_t bcd100_table[] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
  0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
};

uint8_t put_bcd100(const uint8_t bcd)
{
  uint8_t c;

  if (bcd>99)
    return 0x00;
  c= bcd100_table[bcd];
  send(0x01, (c & 0x0F)); // Display LSN
  delay(10);
  send(0x02, (c>>4) & 0x0F); // Display MSN
  delay(10);
  return 0x01;
}
uint8_t put_bcd10000(const uint8_t bcd)
{
  uint8_t c;

  if (bcd>99)
    return 0x00;
  c= bcd100_table[bcd];
  send(0x03, (c & 0x0F)); // Display LSN
  delay(10);
  send(0x04, (c>>4) & 0x0F); // Display MSN
  delay(10);
  return 0x01;
}

uint8_t put_bcd1000000(const uint8_t bcd)
{
  uint8_t c;

  if (bcd>99)
    return 0x00;
  c= bcd100_table[bcd];
  send(0x05, (c & 0x0F)); // Display LSN
  delay(10);
  send(0x06, (c>>4) & 0x0F); // Display MSN
  delay(10);
  return 0x01;
}

void blank_display(void)
{
  send(0x01, 0x0F);
  delay(10);
  send(0x02, 0x0F);
  delay(10);
  send(0x03, 0x0F);
  delay(10);
  send(0x04, 0x0F);
  delay(10);
  send(0x05, 0x0F);
  delay(10);
  send(0x06, 0x0F);
  delay(10);
  send(0x07, 0x0F);
  delay(10);
  send(0x08, 0x0F);
  delay(10);
}
const char ssid[] = "****************";  //  your network SSID (name)
const char pass[] = "*****************";       // your network password
// NTP Servers:
static const char ntpServerName[] = "pool.ntp.org";
//static const char ntpServerName[] = "time.nist.gov";
//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov";

const int timeZone = +8;     // Central European Time
//const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
//const int timeZone = -8;  // Pacific Standard Time (USA)
//const int timeZone = -7;  // Pacific Daylight Time (USA)


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

time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.println("TimeNTP Example");

delay(3000);
  Serial.println("Initializing SPI");
  SPI.begin(); // Delay sending output to MAX7219 to prevent it leaching power
  esp.begin();
  Serial.println("Initializing MAX7219 - 1st attempt");
  init_MAX7219();
  blank_display();
  put_bcd100(1);
  delay(3000);
  Serial.println("Initializing MAX7219- 2nd attempt");
  init_MAX7219();
  blank_display();
  put_bcd100(2);
  delay(3000);
  Serial.println("Initializing MAX7219- 3rd attempt");
  init_MAX7219();
  blank_display();
  put_bcd100(3);
  delay(3000);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("IP number assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(300);

}

time_t prevDisplay = 0; // when the digital clock was displayed

uint8_t hours = 0;
uint8_t mins = 0;
uint8_t loop_ctr = 0;
void loop()
{
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
  hours = hour();
  mins = minute();
  loop_ctr = second();
  put_bcd100(loop_ctr);
  put_bcd10000(mins);
  put_bcd1000000(hours);
    }
  }
}

void digitalClockDisplay()
{
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(".");
  Serial.print(month());
  Serial.print(".");
  Serial.print(year());
  Serial.println();
}
{
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // 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();
}

No comments:

Post a Comment