홈으로 돌아가기

ESP8266의 NTP 서버: 인터넷 없이 정확한 시간 | 가이드

이 기사는 ESP8266 기반 마이크로초 정확도의 자율 NTP 서버 구현을 설명합니다. 네트워크 지연 보상 알고리즘과 동기화 간 시간 정확도 유지에 대해 자세히 분석합니다. 이 솔루션은 외부 NTP 서버 차단 상황에 적합합니다.

자율 NTP 서버: ESP8266이 시간 봉쇄 문제를 어떻게 해결하는가
Advertisement 728x90

ESP8266 독립형 NTP 서버: 외부 의존성 없이 정확한 시간 보장

인터넷 연결이 불안정하거나 외부 NTP 서버가 차단되는 상황에서 정밀한 시간의 로컬 소스가 필수적입니다. 지속적인 인터넷 연결 없이도 계속 작동하는 마이크로초 정확도의 ESP8266 자율 NTP 서버 구축 과정을 단계별로 안내하겠습니다.

표준 솔루션의 한계

현대 인프라는 네트워크 제한과 화이트리스트 정책으로 인해 글로벌 NTP 서버에 접근하지 못하는 경우가 많습니다. 외부 시간 소스가 차단되면 시스템 클럭이 동기화에서 벗어나며, 이는 다음 분야에서 큰 문제로 작용합니다:

  • 재무 거래
  • 보안 로깅
  • 분산 컴퓨팅 시스템
  • 엄격한 타이밍 요구사항을 가진 IoT 기기

해결책은 동기화 사이에 자체적으로 정확한 시간을 유지하는 로컬 NTP 서버를 구축하는 것입니다. ESP8266이 딱 맞습니다: 저전력 소비, 내장 WiFi, 타임스탬프 처리에 충분한 성능.

Google AdInline article slot

시간 시스템 아키텍처

주요 도전 과제는 Arduino 표준 시간 라이브러리의 초 단위 추적 한계를 극복하는 것입니다. NTP(RFC 5905)는 마이크로초 정밀도를 요구합니다. 우리의 사용자 정의 JbTime 클래스가 두 가지 핵심 문제를 해결합니다:

  • 시스템 시간에서 소수점 초 미지원
  • 동기화 간 드리프트 누적

작동 원리:

  • 외부 서버로부터 시간이 도착하면 프로세서의 마이크로초 타임스탬프를 캡처합니다.
  • 각 요청마다 동기화 시점부터 현재까지의 델타를 계산합니다.
  • 수정은 네트워크 지연을 다음 공식으로 반영합니다: offset = ((t2 - t1) + (t3 - t4)) / 2
#ifndef jb_time
#define jb_time

#include <stdint.h>

#define JB_TIME_MAX_AGE  600

class JbTime {
private:
  uint64_t _last;
  uint64_t _sec;
  uint32_t _usec;
  uint32_t _mark;

public:
  bool ok;
  bool fresh;

  JbTime(){
    _sec = 0;
    _usec = 0;
    _mark = 0;
    _last = 0;
    ok = false;
    fresh = false;
  }

  inline bool old() {
    if(_sec == 0) return true;
    if((_sec - _last) > JB_TIME_MAX_AGE) return true;
    return false;
  }

  inline void settime(uint64_t sec, uint32_t usec, uint32_t mark = micros()){
    _mark = mark;
    _sec = sec;
    _sec += usec / 1000000;
    _usec = usec % 1000000;
    ok = true;
    _last = sec;
  }

  inline void gettime(uint64_t *o_sec, uint32_t *o_usec){
    if(ok){
      uint32_t now = micros();
      uint32_t delta = now - _mark;
      _mark = now;

      uint64_t total = (uint64_t)_usec + delta;
      _sec += total / 1000000;
      _usec = total % 1000000;

      *o_sec = _sec;
      *o_usec = _usec;
    } else {
      *o_sec = 0;
      *o_usec = 0;
    }
  }
};
#endif

NTP 요청 처리

NTP 프로토콜은 UDP 포트 123에서 실행되며 시간을 64비트 값(32비트 초, 32비트 분수)으로 전송합니다. 가장 까다로운 부분은 네트워크 지연 보상입니다. 알고리즘은 다음과 같습니다:

Google AdInline article slot
  • 클라이언트가 T1 타임스탬프(자체 로컬 시간)로 요청 전송
  • 서버가 T2에 수신, T3에 응답 전송
  • 클라이언트가 T4에 응답 수신
  • 오프셋: offset = ((T2 - T1) + (T3 - T4)) / 2

JbNTPClient 클래스는 ESP8266 특성을 고려해 이를 처리합니다:

  • RFC 5905에 따른 패킷 형식
  • 자동 에포크 조정(NTP는 1900년 시작, Unix는 1970년)
  • 잘못된 응답 필터링
bool JbNTPClient::requestTime(const char* server, JbTime * mytime) {
  // ... initialization
  
  // Calculation T1
  uint64_t t1_sec = 0;
  uint32_t t1_usec = 0;
  systime->gettime(&t1_sec, &t1_usec);
  t1_sec += NTP_UNIX_EPOCH_DIFF;
  
  // Sending request
  udp.beginPacket(timeServerIP, ntpPort);
  udp.write((uint8_t*)&packet, sizeof(NTPPacket));
  udp.endPacket();

  // Ozhidanie otveta with taymautom 2000 ms
  while (udp.parsePacket() == 0) {
    if (millis() > timeout) return false;
    delay(10);
  }

  // Raschet T4 and setevoy zaderzhki
  double t4 = ntpToDouble(t4_sec, t4_frac);
  _networkDelay = (t4 - t1) - (t3 - t2);
  double offset = ((t2 - t1) + (t3 - t4)) / 2;

  // Installation skorrektirovannogo vremeni
  mytime->settime(sec, usec, t4_mark);
  return true;
}

서버 측: 로컬 네트워크에 시간 제공

외부 서버와 동기화되면 장치는 완전한 NTP 소스가 됩니다. serve() 메서드를 통해 클라이언트 요청을 처리합니다:

  • 수신 UDP 패킷 파싱
  • 모드 확인(모드=3 클라이언트 요청 기대)
  • JbTime에서 현재 시간으로 응답 구축
  • 최대 정확도를 위해 처리 시간 반영

주요 기능: 응답 전송 직전(T3)에 타임스탬프를 캡처해 오류를 최소화합니다. 상용 옵션과 달리 고해상도 카운터에서 직접 시간을 가져와 거친 시스템 함수를 생략합니다.

Google AdInline article slot

주요 기능

  • 자율성: 동기화 간 20~30 마이크로초 정밀도 유지, 수시간 오프라인 상태에서도
  • 자원 효율성: ESP8266 활성 시 70mA 미만 소비, 배터리 친화적
  • 호환성: RFC 5905 준수, 모든 NTP 클라이언트와 즉시 호환
  • 유연성: 필요에 따라 최대 데이터 유효 기간(JB_TIME_MAX_AGE) 조정
  • 장애 내성: 기본 서버 다운 시 백업으로 자동 전환

— Editorial Team

Advertisement 728x90

다음 읽기