[Digi-Key Follow me Issue 4] Advanced Task: Synchronize time from NTP server and get time
[Copy link]
This post was last edited by zygalaxy on 2024-2-23 19:18
Advanced task: synchronize time from NTP server (pay attention to the parsing of data exchange format), obtain time and send it to display screen (serial port) for display.
Matching devices: W5500-EVB-Pico , Adafruit Sharp Memory Display Breakout
1. NTP protocol
NTP (Network Time Protocol) is an application layer protocol based on UDP for computer time synchronization. NTP uses Coordinated Universal Time (UTC) to synchronize computer clock times with extremely high accuracy, such as as low as 1 millisecond in a local area network (LAN) and within tens of milliseconds on the Internet.
2. Working Principle of NTP
NTP obtains accurate time sources from atomic clocks, observatories, satellites or the Internet, and stratifies servers at different levels to synchronize time. According to the distance from the external UTC time source, NTP classifies servers into different layers (Stratum). The top layer is Stratum-1 with external UTC access, and Stratum-2 will obtain time from Stratum-1, and so on. The maximum number of layers is 15. Therefore, the larger the number of layers, the lower the time accuracy. Layer 16 means that it is not synchronized.
The system clock synchronization process is as follows:
- The NTP client sends an NTP request message to the NTP server at time T1. The request message carries the timestamp T1 when it leaves the NTP client.
- The NTP request message arrives at the NTP server, and the time of the NTP server is now T2.
- After processing, the NTP server sends an NTP reply message at time T3. The reply message carries the timestamp T1 when it leaves the NTP client, the timestamp T2 when it arrives at the NTP server, and the timestamp T3 when it leaves the NTP server.
- The NTP client receives the response message at time T4.
Through the above NTP message interaction, the NTP client obtains four time parameters, namely T1, T2, T3, and T4. Since the clocks of the NTP client and the NTP server are completely accurate, we can use the following formula to calculate the time difference between the NTP client and the NTP server, that is, the time that the NTP client needs to adjust.
First, calculate the time Delay required for the NTP message to be sent from the NTP client to the NTP server. Delay = [(T4 - T1) - (T3 - T2)] / 2
Take T4 as an example. At this time, when the message sent by the NTP server is received by the NTP client, the server time is T3 + Delay. Then the time difference Offset can be calculated by the following formula: T4 + Offset = T3 + Delay
After the formula is rearranged, Offset = T3 + Delay - T4 = T3 + [ ( T4 - T1 ) - ( T3 - T2 ) ] / 2 - T4 = [ ( T2- T1 ) + ( T3 - T4 ) ] / 2.
The NTP client adjusts its own clock based on the calculated Offset to achieve clock synchronization with the NTP server.
3. NTP message format
The specific implementation code is as follows:
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
// Screen
#include "PDLS_EXT3_Basic_Global.h"
// SDK
// #include <Arduino.h>
#include "hV_HAL_Peripherals.h"
// Include application, user and local libraries
// #include <SPI.h>
// Configuration
Screen_EPD_EXT3 myScreen(eScreen_EPD_EXT3_370, boardRaspberryPiPico_RP2040);
// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
unsigned int localPort = 8888; // local port to listen for UDP packets
const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;
void setup() {
// You can use Ethernet.init(pin) to configure the CS pin
//Ethernet.init(10); // Most Arduino shields
//Ethernet.init(5); // MKR ETH Shield
//Ethernet.init(0); // Teensy 2.0
//Ethernet.init(20); // Teensy++ 2.0
//Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet
//Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
myScreen.begin();
Serial.println(formatString("%s %ix%i", myScreen.WhoAmI().c_str(), myScreen.screenSizeX(), myScreen.screenSizeY()));
myScreen.clear();
// start Ethernet and UDP
if (Ethernet.begin(mac) == 0) {
Serial.println("Failed to configure Ethernet using DHCP");
// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
} else if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}
// no point in carrying on, so do nothing forevermore:
while (true) {
delay(1);
}
}
Udp.begin(localPort);
}
void loop() {
sendNTPpacket(timeServer); // send an NTP packet to a time server
// wait to see if a reply is available
delay(1000);
if (Udp.parsePacket()) {
// 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, extract 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);
// now convert NTP time into everyday time:
Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears + 3600 * 8;
// print Unix time:
Serial.println(epoch);
myScreen.setOrientation(7);
// #if (USE_FONT_MODE == USE_FONT_TERMINAL)
myScreen.selectFont(Font_Terminal12x16);
// print the hour, minute and second:
Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT)
Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day)
Serial.print(':');
if (((epoch % 3600) / 60) < 10) {
// In the first 10 minutes of each hour, we'll want a leading '0'
Serial.print('0');
}
Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute)
Serial.print(':');
if ((epoch % 60) < 10) {
// In the first 10 seconds of each minute, we'll want a leading '0'
Serial.print('0');
}
Serial.println(epoch % 60); // print the second
myScreen.gText(0, 0, formatString("The UTC time is %02i:%02i:%02i", (epoch % 86400L) / 3600, (epoch % 3600) / 60, epoch % 60));
myScreen.flush();
}
// wait ten seconds before asking for the time again
delay(20000);
Ethernet.maintain();
}
// send an NTP request to the time server at the given address
void sendNTPpacket(const char* 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();
}
Because Beijing time is East 8, this line of code needs to add 8 hours to the time
Upload the program to pico and observe the serial port output as follows
At the same time, the synchronization time is displayed on the ink screen
Since the refresh speed of the ink screen is slow, I changed the synchronization time to 20 seconds
Summarize:
The NTP protocol can ensure that the clocks of various devices in the network remain synchronized, avoiding problems such as data transmission errors and log record confusion caused by inconsistent device clocks. It plays a very important role in daily life.
|