2018
May Sunday 20

Bookmark and Share

Related Articles...

MILWAUKEE WI Arduino Meet Up Make Space

WANTED: CORE TEAM MEMBER TO HELP START NON-PROFIT GROUP.

WANTED: Angle Investor Start-Up Funding

Large Industrial Zoned Arduino Make Space Acquired!

Email :

COASMailBox @ gmail.com

MILWAUKEE WI Raspberry Pi Meet Up Make Space

WANTED: CORE TEAM MEMBER TO HELP START NON-PROFIT GROUP.

WANTED: Angle Investor Start-Up Funding

Large Industrial Zoned Raspberry Pi Make Space Acquired!

Email :

COASMailBox @ gmail.com

MILWAUKEE WI Internet of Things (IoT) Meet Up Make Space

WANTED: CORE TEAM MEMBER TO HELP START NON-PROFIT GROUP.

WANTED: Angle Investor Start-Up Funding

Large Industrial Zoned Make Space Acquired!

Full Advanced Fabrication Facility, machine Shop, Welding, Electronics Lab Equipment Acquired!

Email :

COASMailBox @ gmail.com

MILWAUKEE WI Machine Repair, & Installation  - Tier 1 Business Support

Machine Technician Mechanic 20+ Years  Personal Experienced In Industrial Factory Machine Servicing, Rebuilding, Installation And Repair - Plus A 35+ Years Experienced Computer Engineer Service Technician Team Ready For Your Outsourced Market Advantage Needs!

Hartung Industrial - Member - CGI Science & Technology Group

Email :

COASMailBox @ gmail.com


2018 REVIEW

 

Arduino Data Logger Accurate Time Logging - Using NTP

ADVANCED ACCURACY | Arduino NTP Server

2018 APRIL | by Gene Casanova

Senior Systems / Network / Internet / Robotics Engineer


World Standard Time Sync

Datalogging with an Arduino can use the world standard clock, for the most accurate time stamp.

GPS always gives accurate UTC time. RTC will always provide drifted time.

A reliable and highly accurate NTP Server, can be made available to a datalogger, using the global GPS satellite system, for scientific and commercial applications.

A NTP Server can be built using a GPS receiver system by CGI Computer Wares, as a highly accurate time source for timestamping measured data.

The following source code must be modified to work with different GPS chipsets. A U.S. marine industry standard "NMEA" has established data formats referred to as "NEMA sentences". The NEMA sentence "GPRMC", will be used as the time source.

This source code was developed for Arduino boards with a separate UART, like the Arduino UNO, and Arduino Mega256; both providing a Tx and Rx connection pin.

A W5100 Ethernet interface shield has been used for a LAN connection.

The Arduino code fits in a ATmega328 microcomputer IC chip; however, the source code must be modified to provide the Tx/Rx handling using a soft serial port code.

The basic accuracy of the NTP Server is determined by the GPS signal time source, the UDP NTP request processing time of the code and LAN latency.  The GPS signal is accurate to well defined limits. It is possible to tune for the processing time and LAN latency by adjusting the delay/dispersion values and timestamp to suit requirements.

This NTP Server, provides a NTP Response. The NTP Server times out if there is no GPS GPRMC data available. The error handling is very basic.

There are plenty of ways this foundation source code can be improved and features added. The source code can be improved. The purpose of this source code, is to provide a proof of concept in an easy to understand process.

Experimental Source Code

/*
 *  GPS NTP Time Server:
 *
 *  Proof of concept.
 */
#define vers "NTP GPS V01A"

#define debug false

#include <SPI.h>            //  Needed for Arduino versions later than 0018
#include <Ethernet.h> // Ethernet network interface controller (NIC)
#include <EthernetUdp.h> // UDP library from: bjoern@cs.stanford.edu 12/30/2008
#include <TinyGPS.h>

// Time Server Port:
#define NTP_PORT 123

// Time Server MAC address:
byte mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; // Must be a unique value on the attached LAN.

// NTP Server public IP Address:
IPAddress ip(192, 168, 0, 160);

static const int NTP_PACKET_SIZE = 48;

// Buffers for Receiving and Sending Data:
byte packetBuffer[NTP_PACKET_SIZE];

// An Ethernet UDP Instance:
EthernetUDP Udp;

// GPS Instance:
TinyGPS gps;

int year;
byte month, day, hour, minute, second, hundredths;
unsigned long date, time, age;
uint32_t timestamp, tempval;

////////////////////////////////////////

void setup() {
// Start the Ethernet and UDP:
Ethernet.begin(mac,ip);
Udp.begin(NTP_PORT);

#if debug
Serial.begin(115200);
#endif

Serial1.begin(9600); // start GPS module UART

#if debug
Serial.print("Version:");
Serial.println(vers);
#endif

// Disable everything but $GPRMC
// Note the following sentences are for UBLOX NEO6MV2 GPS

Serial1.write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
Serial1.write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
Serial1.write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
Serial1.write("$PUBX,40,GGA,0,0,0,0,0,0*5A\r\n");
Serial1.write("$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n");

}

////////////////////////////////////////

void loop() {

if (getgps()) {

gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
if (age == TinyGPS::GPS_INVALID_AGE) {

#if debug
Serial.println("Invalid GPS age");
#endif

}
else {
processNTP();
}
}
}

////////////////////////////////////////

void processNTP() {

// if there's data available, read a packet
int packetSize = Udp.parsePacket();
if(packetSize)
{
Udp.read(packetBuffer,NTP_PACKET_SIZE);
IPAddress Remote = Udp.remoteIP();
int PortNum = Udp.remotePort();

#if debug
Serial.println();
Serial.print("Received UDP packet size ");
Serial.println(packetSize);
Serial.print("From ");

for (int i =0; i < 4; i++)
{
Serial.print(Remote[i], DEC);
if (i < 3)
{
Serial.print(".");
}
}
Serial.print(", port ");
Serial.print(PortNum);

byte LIVNMODE = packetBuffer[0];
Serial.print(" LI, Vers, Mode :");
Serial.print(packetBuffer[0],HEX);

byte STRATUM = packetBuffer[1];
Serial.print(" Stratum :");
Serial.print(packetBuffer[1],HEX);

byte POLLING = packetBuffer[2];
Serial.print(" Polling :");
Serial.print(packetBuffer[2],HEX);

byte PRECISION = packetBuffer[3];
Serial.print(" Precision :");
Serial.println(packetBuffer[3],HEX);

for (int z = 0; z < NTP_PACKET_SIZE; z++) {
Serial.print(packetBuffer[z],HEX);
if (((z+1) % 4) == 0) {
Serial.println();
}
}
Serial.println();

#endif

packetBuffer[0] = 0b00100100; // LI, Version, Mode
packetBuffer[1] = 1 ; // Stratum
packetBuffer[2] = 6 ; // polling minimum
packetBuffer[3] = 0xFA; // precision

packetBuffer[7] = 0; // root delay
packetBuffer[8] = 0;
packetBuffer[9] = 8;
packetBuffer[10] = 0;

packetBuffer[11] = 0; // root dispersion
packetBuffer[12] = 0;
packetBuffer[13] = 0xC;
packetBuffer[14] = 0;

gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);

timestamp = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second);

#if debug
Serial.println(timestamp);
print_date(gps);
#endif

tempval = timestamp;

packetBuffer[12] = 71; //"G";
packetBuffer[13] = 80; //"P";
packetBuffer[14] = 83; //"S";
packetBuffer[15] = 0; //"0";

// reference timestamp
packetBuffer[16] = (tempval >> 24) & 0XFF;
tempval = timestamp;
packetBuffer[17] = (tempval >> 16) & 0xFF;
tempval = timestamp;
packetBuffer[18] = (tempval >> 8) & 0xFF;
tempval = timestamp;
packetBuffer[19] = (tempval) & 0xFF;

packetBuffer[20] = 0;
packetBuffer[21] = 0;
packetBuffer[22] = 0;
packetBuffer[23] = 0;

//copy originate timestamp from incoming UDP transmit timestamp
packetBuffer[24] = packetBuffer[40];
packetBuffer[25] = packetBuffer[41];
packetBuffer[26] = packetBuffer[42];
packetBuffer[27] = packetBuffer[43];
packetBuffer[28] = packetBuffer[44];
packetBuffer[29] = packetBuffer[45];
packetBuffer[30] = packetBuffer[46];
packetBuffer[31] = packetBuffer[47];

//receive timestamp
packetBuffer[32] = (tempval >> 24) & 0XFF;
tempval = timestamp;
packetBuffer[33] = (tempval >> 16) & 0xFF;
tempval = timestamp;
packetBuffer[34] = (tempval >> 8) & 0xFF;
tempval = timestamp;
packetBuffer[35] = (tempval) & 0xFF;

packetBuffer[36] = 0;
packetBuffer[37] = 0;
packetBuffer[38] = 0;
packetBuffer[39] = 0;

//transmitt timestamp
packetBuffer[40] = (tempval >> 24) & 0XFF;
tempval = timestamp;
packetBuffer[41] = (tempval >> 16) & 0xFF;
tempval = timestamp;
packetBuffer[42] = (tempval >> 8) & 0xFF;
tempval = timestamp;
packetBuffer[43] = (tempval) & 0xFF;

packetBuffer[44] = 0;
packetBuffer[45] = 0;
packetBuffer[46] = 0;
packetBuffer[47] = 0;

// Reply to the IP address and port that sent the NTP request

Udp.beginPacket(Remote, PortNum);
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}
}

////////////////////////////////////////

static bool getgps()
{
while (Serial1.available())
{
char c = Serial1.read();
#if debug
Serial.write(c); // GPS data flowing
#endif
if (gps.encode(c))
return true;
}
return false;
}

////////////////////////////////////////

const uint8_t daysInMonth [] PROGMEM = {
31,28,31,30,31,30,31,31,30,31,30,31 }; //const or compiler complains

const unsigned long seventyYears = 2208988800UL; // to convert unix time to epoch

// NTP since 1900/01/01
static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) {
if (y >= 1970)
y -= 1970;
uint16_t days = d;
for (uint8_t i = 1; i < m; ++i)
days += pgm_read_byte(daysInMonth + i - 1);
if (m > 2 && y % 4 == 0)
++days;
days += 365 * y + (y + 3) / 4 - 1;
return days*24L*3600L + h*3600L + mm*60L + s + seventyYears;
}

////////////////////////////////////////

#if debug

////////////////////////////////////////

static void print_date(TinyGPS &gps)
{
int year;
byte month, day, hour, minute, second, hundredths;
unsigned long age;
gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
if (age == TinyGPS::GPS_INVALID_AGE)
Serial.print(F("******* ******* "));
else
{
char sz[32];
sprintf(sz, "%02d/%02d/%02d %02d:%02d:%02d :",
month, day, year, hour, minute, second);
Serial.print(sz);
}
print_int(age, TinyGPS::GPS_INVALID_AGE, 5);

}

////////////////////////////////////////

static void print_int(unsigned long val, unsigned long invalid, int len)
{
char sz[32];
if (val == invalid)
strcpy(sz, "*******");
else
sprintf(sz, "%ld", val);
sz[len] = 0;
for (int i=strlen(sz); i<len; ++i)
sz[i] = ' ';
if (len > 0)
sz[len-1] = ' ';
Serial.print(sz);

}

#endif

Further Information For Home DIY, And Professional Custom System Development And OEM Builds, Is Made Available Online In The 'Inner Lab Quick References'.  Limited Access Made Available To Registered Users. Contact For Details.


Use The Technology Wisely & Keep It Simple

- Cheers!

Gene Casanova


Cyber Security Since 1979 - Consulting Available | IoT Full Stack And App Developer

CGI Computer Wares | EST 1979

Send E-MailCONTACT: [click]@CGIComputerWares.com