Greetings;
My goal is to build a desktop clock using an ESP8266 and LCD and learn about Arduino (ESP8266) programming the process. I am using the Arduino IDE (1.6.9) and I am a complete beginner (as the code will demonstrate). My objective is to use NTP (and later an RTC) to achieve a synchronous time display with a known time standard such as audio broadcasts on WWV (to within +/-0.5 seconds).
As implemented below, the clock displays time/date and generally does what it's supposed to do (displays time, AM/PM indicator, Daylight Savings vs Standard time, etc.).
However, there are a couple of issues that I don't understand and was hoping others might educate me.
First, the display will occasionally pause for a second, and then continue (maybe one second will be displayed for 1.5 seconds or so). Also, occasionally a second will get skipped; ...3, 4, 6, 7, 8, 9, 11... The seconds that get skipped are not the same seconds every minute. It may do this "skipping" & "pausing" 3 or 6 times a minute. When it's not skipping or pausing the clock keeps time - in other words, the clock is not drifting, it's just stuttering in the display (if that makes any sense...).
Is this behavior a result of an error in my coding (likely), or is this the nature of the way the time library works?
Second, after synchronizing the clock is almost always slow by ~2 seconds when compared to a standard time reference (e.g. WWV). This delay can vary from 1 to 2 seconds. I am guessing it's due to the latency in getting the packets from the NTP servers and for the controller to parse and process the packet it receives - I guess I was just expecting the magnitude of this processing would be a very small portion of one second and thus negligible. But it's more likely that I am doing something in the code that is causing the problem.
If it's just latency, then is there a way to measure this in the routine and then add that to the time stamp - if so, how? Is there a better way to correct this error? Again, this is just an education for me.
Thank you in advance for any suggestions/wisdom.
Rob
/*
*
* ESP8266 module using Arduino IDE v1.6.9
* 20 x4 LCD module
* DS3231 RTC module (not yet implemented)
*
* FEB-26-2017: NTP only, no RTC. Added the TimeZone library. The clock is about 2 seconds slow; occassionally the digits stop and it skips numbers (seconds). I fiddled with a fudge factor in the getNtpTime routine - toward the
* end just before the return statement, but I would like to add some method of measuring the packet latency and adding that back-in to the time reading.
*
* IDEAS/FIXES: Add BMP280 for temp, humidity & pressure; top of hour beep; add calibration routine to adjust for time it takes to get packets
* add error handling & flag for network errors; add RTC with periodic NTP-sync
*
*/
#include <TimeLib.h>
#include <Timezone.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
const char ssid[] = "******"; // your network SSID (name)
const char pass[] = "*****"; // your network password
// NTP Servers:
static const char ntpServerName[] = "us.pool.ntp.org";
WiFiUDP Udp;
unsigned int localPort = 2390; // local port to listen for UDP packets
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
//US Pacific Time Zone
TimeChangeRule usPDT = {"PDT ", Second, Sun, Mar, 2, -420}; //UTC - 7 hours
TimeChangeRule usPST = {"PST ", First, Sun, Nov, 2, -480}; //UTC - 8 hours
//{abbrev, week, dow, mon, hr, offset in minutes}
Timezone usPacific(usPDT, usPST);
TimeChangeRule *tcr; //pointer to the time change rule, use to get TZ abbrev
time_t pacific, utc;
void setup()
{
lcd.begin(20,4);
Wire.begin();
lcd.setCursor(6,0);
lcd.print("NTP Clock");
lcd.setCursor(0,1);
lcd.print("ssid: ");
lcd.print(ssid);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
lcd.print(".");
}
lcd.setCursor(0,2);
lcd.print("IP: ");
lcd.print(WiFi.localIP());
Udp.begin(localPort);
lcd.setCursor(0,3);
lcd.print("port: ");
lcd.print(Udp.localPort());
delay(2500);
lcd.clear();
setSyncProvider(getNtpTime); //defaults to sync every 5 minutes
setSyncInterval(180); // changes sync interval in seconds
}
time_t prevDisplay = 0; // when the digital clock was displayed
void loop()
{
{
utc = now();
pacific = usPacific.toLocal(utc, &tcr);
}
if (timeStatus() != timeNotSet) {
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
digitalClockDisplay(pacific, tcr -> abbrev);
}
}
}
void digitalClockDisplay(time_t t, char *tz)
{
// print system time to LCD
lcd.setCursor(3,1);
lcd.print(hourFormat12(t));
printDigits(minute(t));
printDigits(second(t));
if (isAM(t)==true) {lcd.print(" AM ");
lcd.print(tz);}
else {lcd.print(" PM ");
lcd.print(tz);}
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(2,3);
lcd.print(dayShortStr(weekday(t)));
lcd.print(", ");
lcd.print(monthShortStr(month(t)));
lcd.print(" ");
lcd.print(day(t));
lcd.print(", ");
lcd.print(year(t));
}
void printDigits(int digits) // utility for digital clock display: prints preceding colon and leading 0
{
lcd.print(":");
if (digits < 10)
lcd.print('0');
lcd.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()
{
int attempts = 10;
while (attempts--) {
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
lcd.setCursor(0,3);
lcd.print(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
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; //adjusted for time zone
return secsSince1900 - 2208988800UL + 2; //UTC option, with a fudge factor
}
delay(10);
}
}
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();
}