카테고리 없음

ESP8266 WIFI 모듈을 사용하여 NTP 서버에서 ARDUINO 시간 동기화

지구빵집 2020. 9. 9. 10:12
반응형

 

ESP8266WIFI 모듈을 사용하여 NTP 서버에서 ARDUINO 시간 동기화  

 

시간은 우리의 삶을 측정하는 단위입니다. 전자 및 컴퓨터 세계의 모든 메커니즘은 시간을 기반으로 합니다. 개념으로서의 자동화는 시간 또는 시간에 따라 조정되는 지능형 작업으로 정의됩니다. 자동화 엔지니어링에 대해 이야기할 때 스마트 프로젝트는 타이밍 및 동기화를 기반으로 하는 매우 정확한 트리거 및 계산에 의해 주도됩니다. 그러나 수동 조정 없이 어떻게 완벽한 시간을 얻을 수 있습니까? 이를 수행하는 한 가지 방법은 NTP 서버에 요청하는 것입니다. 이 기사에서는 RTC 외부 모듈 없이 ESP8266을 Arduino 프로젝트의 시간 소스로 사용하는 방법에 대한 모든 것을 찾을 수 있습니다. 포스팅 원문 출처

 

실시간 소스로 WiFi ESP8266 Arduino 호환 모듈 사용(링크에 아주 자세한 사용법 설명되어 있어요.)

 

NTP 서버란?

 

NTP 약어는 1980 년대부터 운영되고 있는 네트워크 클라이언트 간의 시계 동기화를 위한 네트워킹 통신 프로토콜 인 Network Time Protocol의 약자입니다. NTP 작업은 모든 네트워크 참가자를 몇 밀리 초 내에 UTC (Coordinated Universal Time)로 동기화하는 것입니다. 이 작업을 수행하기 위해 Keith Marlzullo가 개발 한 합의 알고리즘 인 "교차 알고리즘"을 사용하여 서로 다른 노이즈 소스에서 정확한 시간을 추정합니다. NTP는 공용 인터넷에서는 50 밀리 초 미만, LAN 환경에서는 5 밀리 초 미만의 정밀도로 시간을 유지할 수 있습니다. 좀 더 친근한 설명으로 NTP는 UDP 요청을 통해 타임스탬프를 보내거나 받거나 브로드 캐스트 / 멀티 캐스팅을 통해 구현할 수 있는 클라이언트-서버 서비스라고 말하고 싶습니다.

 

NTP 서버를 어떻게 사용할 수 있습니까?

 

NTP 구현은 많은 응용 프로그램에서 찾을 수 있습니다. 운영 체제는 대부분 NTP 서버, 시간 서버, 데이터베이스, 기상 관측소, 중개 및 온라인 시장 교환 응용 프로그램에서 정확한 시간을 요청하여 NTP 서버의 이점을 얻습니다. NTP 서버를 조사하려면 사용자 환경이 로컬 포트에서 UDP 연결을 열고 서버와 동일한 네트워크에서 UDP 패키지를 보내고 받을 수 있어야 합니다. 수신된 패키지에는 UNIX 타임스탬프, 정확성, 지연 또는 시간대와 같은 여러 정보가 포함되어 있으며 많은 개발 환경에서 데이터를 매우 객관적인 형식으로 추출하는 친숙한 방법을 제공하고 있습니다. 

 

NTP 서버는 얼마나 정확합니까?

 

NTP 서버가 얼마나 정확한지 알기 전에 그 아키텍처를 알아야 합니다. NTP 서버는 클록 레벨의 계층 적 반 계층 시스템입니다. 아래 NTP 계층 수준을 참조하십시오. 

 

자료 출처: http://www.kuriwaobservatory.com/pdf_files/NTP-HowAccurate.pdf

 

NTP에 의해 동기화된 시간의 정확성에 영향을 미칠 수 있는 많은 요소가 있습니다. 가시적인 영향은 다음과 같습니다.

  • 클라이언트 인터넷 연결의 속도 (대기 시간).
  • 동기화를 위해 선택한 시간 서버의 계층.
  • 서버와의 신호 거리 (궤도 위성 포함).
  • 사용된 소프트웨어 알고리즘의 품질과 복잡성.

 

참고 : 정확도를 높이려면 종료 지점 (인터넷 공급자 끝 지점)에 물리적으로 가까운 서버를 선택하는 것이 좋습니다. 이렇게 하면 신호가 정지 위성으로 라우팅 되지 않을 가능성이 줄어듭니다.

 

공용 서버 예 :

 

ESP8266 용 Arduino 코드 예제 – NTP 서버 풀링

 

NTP 서버에서 데이터를 가져오려면 ESP 모듈을 UDP 클라이언트로 프로그래밍해야 합니다. Arduino IDE를 사용하여 ESP8266을 프로그래밍하는 것을 선호하지만 LUA 코드 또는 AT 명령을 통해 동일한 결과를 얻을 수 있습니다. Arduino로이를 수행하기 위해 ESP8266WiFi.h 및 WiFiUdp.h라는 두 가지 사용 가능한 라이브러리를 사용했습니다. 첫 번째 라이브러리는 WiFi 연결을 허용하고 관리하고 두 번째 라이브러리는 UDP 패키지를 보내고 받습니다. 두 라이브러리는 Arduino IDE의 보드 관리자에서 ESP8266 보드를 설치한 후 사용할 수 있습니다.

 

SSID 및 암호를 홈 라우터 WiFi 자격 증명으로 바꾸고 ntpServerName을 시간을 가져올 서버로 바꾸고 다음 코드를 ESP에 업로드합니다. NTP 서버의 직접 IP 주소를 가리킬 수 있지만 대부분의 경우 POOL 이점을 잃게 됩니다. 

 

/********************
- www.geekstips.com
- Arduino Time Sync from NTP Server using ESP8266 WiFi module 
- Arduino code example
 ********************/

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

char ssid[] = "*************";  //  your network SSID (name)
char pass[] = "********";       // your network password


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 timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP; // time.nist.gov NTP server address
const char* ntpServerName = "time.nist.gov";

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
WiFiUDP udp;

void setup()
{
  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());
}

void loop()
{
  //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(1000);
  
  int cb = udp.parsePacket();
  if (!cb) {
    Serial.println("no packet yet");
  }
  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);

    // 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;
    // print Unix time:
    Serial.println(epoch);


    // 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
  }
  // wait ten seconds before asking for the time again
  delay(10000);
}

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

 

위 코드는 다음 출처의 코드에서 영감을 받았습니다. 직렬 모니터링 터미널을 열고 ESP 모듈을 재설정하십시오. 다음과 같은 결과가 표시되어야 합니다.

 

이미지 출처는 본 문서 원문인 https://www.geekstips.com/arduino-time-sync-ntp-server-esp8266-udp/

 

 

이 예에서는 글로벌 NTP 서버 (time.nist.gov)를 사용했지만 유럽에서 온 것이 최선의 선택이 아닙니다. 내 프로젝트의 경우 다음 예제와 같이 물리적 위치에 더 가까운 NTP 서버를 사용합니다.

 

const char* ntpServerName = "0.europe.pool.ntp.org";

 

귀하의 위치와 일치하는 NTP를 얻으려면 www.pool.ntp.org로 이동하고 오른쪽 사이드 바에서 귀하의 영역 / 대륙을 클릭하여 전체 목록을 확인하십시오. 

 

이미지 캡쳐는 https://www.ntppool.org/ko/

 

 

 

 

Configuring ESP8266 as a Time source 

ESP 모듈을 시간 원본으로 사용하는 방법에는 여러 가지가 있지만 추가 시간을 줄이거나 없애려면 트랜잭션 중에 가장 성능이 좋은 모듈을 선택해야 합니다. 가장 빠른 방법과 프로젝트에서 사용한 방법은 직렬 터미널을 통해 ESP8266에서 시간을 얻는 것입니다. 또 다른 방법은 ESP에 웹 서버를 설정하고 요청 시 HTTP 응답을 통해 시간을 반환하는 것이지만 이는 네트워크에 따라 다르며 네트워크에 연결된 프로젝트가 필요합니다. 출력 트랜잭션에 의해 생성된 지연은 반환된 시간의 정확성에 직접적인 영향을 미친다는 점에 유의하십시오.

 

다음 예에서는 직렬 터미널을 통해 ESP8266을 표준 Arduino UNO R3에 연결하고 지정된 시간에 NTP와 동기화하도록 Arduino UNO 내부 RTC를 설정합니다. 이를 위해 디지털 GPIO 2 및 3에서 직렬 통신을 에뮬레이트 하기 위해 SoftwareSerial 라이브러리를 사용하고이 라인을 ESP 모듈 수 신용으로 예약해야 합니다. 

 

마찬가지 이미지 출처는 https://www.geekstips.com/arduino-time-sync-ntp-server-esp8266-udp/

 

 

이전 자습서에서 알 수 있듯이 ESP8266에는 견고한 외부 3.3v 전원 공급 장치를 사용하는 것이 좋지만 데모를 위해 UNO의 3.3v VCC를 사용합니다. Arduino UNO에서 작업을 수행하기 전에 먼저 방금 ESP8266에 업로드 한 코드를 정리해야 합니다.

 

Arduino Time 라이브러리는 UNIX 시간 매개 변수를 전달함으로써 만 내부 또는 외부 RTC를 동기화할 수 있는 가능성을 제공하기 때문에 예제에서 사용된 다른 직렬 출력이 필요하지 않으므로 ESP 모듈의 생산 코드는 다음과 같아야 합니다. 

 

/********************
- www.geekstips.com
- Arduino Time Sync from NTP Server using ESP8266 WiFi module 
- Arduino code example
 ********************/

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

char ssid[] = "***********";  //  your network SSID (name)
char pass[] = "***********";       // your network password


unsigned int localPort = 2390;      // local port to listen for UDP packets
IPAddress timeServerIP;
const char* ntpServerName = "time.nist.gov";

const int NTP_PACKET_SIZE = 48;

byte packetBuffer[ NTP_PACKET_SIZE];
WiFiUDP udp;

void setup()
{
  Serial.begin(115200);
  WiFi.begin(ssid, pass);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  udp.begin(localPort);
}

void loop()
{
  WiFi.hostByName(ntpServerName, timeServerIP); 

  sendNTPpacket(timeServerIP);
  delay(1000);
  
  int cb = udp.parsePacket();
  if (!cb) {
    delay(1);
  }
  else {
    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    const unsigned long seventyYears = 2208988800UL;
    unsigned long epoch = secsSince1900 - seventyYears;
    Serial.print("UNX");
    Serial.println(epoch);
  }
  delay(10000);
}

unsigned long sendNTPpacket(IPAddress& address)
{
  Serial.println("sending NTP packet...");
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  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
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  udp.beginPacket(address, 123);
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

 

보시다시피, Arduino 내부 클럭 설정 구성에 매개 변수로 전달하는 데 사용하므로 UNIX 시간 (epoch)만 인쇄합니다. 또한 ESP가 가비지 데이터를 제공하지 않도록 타임스탬프 앞에 문자열 "UNX"토큰을 인쇄했습니다.

이제 Arduino UNO에서 ESP8266 직렬 전송을 수신해야 합니다. ESP가 TX 직렬 채널에서 데이터를 보낼 때, 우리는 어떤 가비지 나 오류가 아닌 실제 UNIX 시간인지 확인해야 합니다. "UNX"토큰 이어야 하는 처음 3 개의 문자를 확인하여 이를 수행할 수 있습니다. 이것이 사실이면 다음 10 개의 문자는 UNIX epoch 시간을 나타내야 합니다. 아래 예의 데모를 참조하십시오. 

 

/********************
- www.geekstips.com
- Arduino Time Sync from NTP Server using ESP8266 WiFi module
 ********************/
#include <SoftwareSerial.h>
#include <TimeLib.h>

// setup a commong baudrate for UNO and ESP
int baudRate = 115200;
char unixString[11];
long unixTime;
boolean dataSync = false;
// setup the ESP emulated serial channel
SoftwareSerial esp8266(2, 3);

void setup() {
  Serial.begin(baudRate);
  esp8266.begin(baudRate);
}

void loop() {
  
  char buffer[40];
  int i = 0;

  // while the ESP output available, push it
  // into the buffer and set the flag true
  while (esp8266.available()) {
    buffer[i++] = esp8266.read();
    dataSync = true;
  }

  // if data is available, parse it
  if (dataSync == true) {
    if ((buffer[0] == 'U') && (buffer[1] == 'N') && (buffer[2] == 'X')) {
      // if data sent is the UNX token, take it
      unixString[0] = buffer[3];
      unixString[1] = buffer[4];
      unixString[2] = buffer[5];
      unixString[3] = buffer[6];
      unixString[4] = buffer[7];
      unixString[5] = buffer[8];
      unixString[6] = buffer[9];
      unixString[7] = buffer[10];
      unixString[8] = buffer[11];
      unixString[9] = buffer[12];
      unixString[10] = '\0';

      // print the UNX time on the UNO serial
      Serial.println();
      Serial.print("TIME FROM ESP: ");
      Serial.print(unixString[0]);
      Serial.print(unixString[1]);
      Serial.print(unixString[2]);
      Serial.print(unixString[3]);
      Serial.print(unixString[4]);
      Serial.print(unixString[5]);
      Serial.print(unixString[6]);
      Serial.print(unixString[7]);
      Serial.print(unixString[8]);
      Serial.print(unixString[9]);
      Serial.println();
      
      unixTime = atol(unixString);
      // Synchronize the time with the internal clock
      // for external use RTC.setTime();
      setTime(unixTime);
      dataSync = false;
    }
  }
}

 

setTime () 메서드에는 많은 오버 로딩 버전이 있으며 UNIX 타임스탬프 매개 변수와 함께 사용할 수 있지만 시간, 분, 초와 같은 특정 시간 부분을 매개 변수로 사용할 수도 있습니다. Arduino 공식 문서로부터 추가 : 

 

/********************
- www.geekstips.com
- Arduino Time Sync from NTP Server using ESP8266 WiFi module 
- Arduino code example
 ********************/ 

 setTime(t);              // Set the system time to the
                          // give time t UNX timestamp
 setTime(hr,min,sec,day,month,yr); // Another way to set
                                   // the time with time parts
 adjustTime(adjustment); // Adjust system time by adding
                         // the adjustment value
                         // WARNING: this is not compatible with using a sync provider as it
                         // only adjusts the library system time and not time in the sync provider.
                         // This offset adjustment will be lost on the next time sync.
                         // If the time is moved forward, an immediate call to now() can get the time
                         // from the sync provider rather than the adjusted library system time.
                         // i.e. do not depend on now() to return an adjusted time when using
                         // a sync provider even when called immediately after adjustTime()

 timeStatus();   // Indicates if time has been set and
                 //  recently synchronized
                 //  returns one of the following
                 //  enumerations:
 * timeNotSet    // The time has never been set,
                 //  the clock started at Jan 1 1970
 * timeNeedsSync // The time had been set but a sync
                 //  attempt did not succeed
 * timeSet       // The time is set and is synced
                 //  Time and Date values are not valid if
                 //  the status is timeNotSet. Otherwise
                 //  values can be used but the returned
                 //  time may have drifted if the status is
                 //  timeNeedsSync.    

 setSyncProvider(getTimeFunction);// Set the external time
                                  //  provider
 setSyncInterval(interval);    // Set the number of
                               //  seconds between re-sync

 

UNIX 타임스탬프로 시간을 업데이트한 후 다음 예제와 같은 함수를 호출하여 결과를 확인할 수 있습니다. 

 

void displayCurrentTime() {
  Serial.print(hour());
  Serial.print(":");
  Serial.print(minute());
  Serial.print(":");
  Serial.print(second());
  Serial.println();
  delay(1000);
}

 

나는 집에서 자체 조정 디지털시계를 만들기 위해 똑같은 원리를 사용했으며 지금까지 꽤 잘 작동하고 있습니다. 또한 요청이 실패하거나 인터넷 연결이 끊어진 상황에 대해 몇 가지 오류 방지 기능을 구현했습니다. NTP에서 받은 UNIX 시간과 RTC의 현재 타임스탬프 간의 차이가 서버로 전송된 요청 간의 지연보다 크지 않은지 몇 가지 확인하고 테스트해야 합니다. 예를 들어 ESP 모듈에서 받은 마지막 UNIX 타임스탬프를 변수에 저장하고 현재 타임스탬프와 비교할 수 있습니다. 그들 사이의 차이는 지연을 위해 ESP 루프 구조에서 사용되는 간격과 ~ 같아야 합니다.

 

내 디지털시계 프로젝트는 다음과 같습니다.

 

 

큰 사용자 지정 글꼴을 사용하여 16x2 일반 LCD 디스플레이에 NTP 서버의 시계를 표시하고 DHT22 센서로 측정 한 SSD1306 OLED 디스플레이에 실내 온도 및 습도를 표시합니다. 나는 10 분마다 시간을 업데이트하고 완벽한 GMT 경기를 갖기 위해 매번 2 초씩 수정합니다. 프로젝트에 필요한 부품을 얻으려면 여기에서 제가 사용한 것입니다 (Amazon.com의 링크). 

1. Arduino Nano v 3.0 미니 USB
2. ESP8266 ESP-01
3. 16 × 2 LCD 디스플레이
4. SSD1306 OLED 디스플레이
5. LM2596 전원 공급 장치 출력 1.23V-30V

 

이것은 사물 인터넷 프로젝트에서 NTP를 활용하는 방법입니다. 이 작업을 수행하기 위해 더 복잡하거나 더 잘 코딩된 다른 메서드를 찾을 수 있습니다. 또한 UDP 라이브러리와 예제가 상당히 많이 있으며, 이를 시도하고 프로젝트에 맞는 최상의 솔루션을 찾는 데 방해가 되는 것은 없습니다. 하루가 끝나면 수동으로 조정하지 않고도 인터넷을 통해 정확한 시간 참조를 얻을 수 있습니다. 

 

마지막으로 유튜브 사이트 홍보와 다양한 질문과 답변이 있다.

 

 

 

 

반응형