홈으로 돌아가기

Raspberry Pi에서 chrony와 GPS를 사용한 Stratum 1 NTP

이 기사는 chrony, libgpiod v2, DCF77 및 GPS NEO-M8T를 사용하여 Raspberry Pi에서 자율 Stratum 1 NTP 서버를 만드는 방법을 설명합니다. 42 ns 정확도가 달성되었습니다. ntpd와 GPIO API 마이그레이션 문제가 다루어집니다.

RPi에서 정밀 NTP Stratum 1: GPS PPS로 42 ns
Advertisement 728x90

라즈베리 파이 독립형 Stratum 1 NTP 서버 구축: 2026년 DCF77 및 GPS PPS를 활용한 chrony 설정

라즈베리 파이 OS Bookworm/Trixie에서는 libgpiod v2 도입, chrony로의 전환, GPIO 칩 변경으로 인해 기존 NTP 서버 튜토리얼이 작동하지 않습니다. C++20 구현으로 GPIO 17에 DCF77 수신기 RC8000, GPIO 18에 PPS 기능을 갖춘 ublox NEO-M8T GPS 모듈을 연결합니다. 이 서버는 인터넷 없이도 <1 µs 정확도를 달성하며 독립적인 시간 소스 검증을 제공합니다.

구형 구현 방식의 문제점

libgpiod v2로 인한 GPIO 코드 호환성 문제

libgpiod v2는 이전 버전과의 호환성 없이 v1 API를 제거했습니다. gpiod_chip_open_by_namegpiod_chip_get_line을 사용하는 기존 코드는 컴파일되지 않습니다:

// libgpiod v1 — v2에서는 컴파일되지 않음
struct gpiod_chip *chip = gpiod_chip_open_by_name("gpiochip0");
struct gpiod_line *line = gpiod_chip_get_line(chip, 17);
gpiod_line_request_both_edges_events(line, "dcf77");

새로운 API는 gpiod_line_settings_new()gpiod_line_config_new()를 요구합니다:

Google AdInline article slot
// libgpiod v2 — 올바른 접근 방식
const SettingsPtr settings(gpiod_line_settings_new());
gpiod_line_settings_set_edge_detection(settings.get(), GPIOD_LINE_EDGE_BOTH);
gpiod_line_settings_set_event_clock(settings.get(), GPIOD_LINE_CLOCK_REALTIME);

const LineCfgPtr line_cfg(gpiod_line_config_new());
gpiod_line_config_add_line_settings(line_cfg.get(), &offset, 1, settings.get());

const RequestPtr request(gpiod_chip_request_lines(chip.get(), req_cfg.get(), line_cfg.get()));

주요 장점은 GPIOD_LINE_CLOCK_REALTIME입니다: 이벤트는 인터럽트 발생 시 커널로부터 타임스탬프를 받아 지터를 40 ms에서 <5 ms로 줄입니다.

ntpd에서 chrony로의 전환

Bookworm에서는 ntpd가 chrony로 대체되었습니다. 비교:

| 매개변수 | ntpd | chrony |

Google AdInline article slot

|-----------|------|--------|

| 수렴 속도 | 분 단위 | 초 단위 (makestep 0.5 3) |

| 신호 손실 | 성능 저하 | 홀드오버 (local stratum 10) |

Google AdInline article slot

| PPS | 패치 필요 | 기본 지원 refclock PPS |

| PPS 정확도 | ~100 µs | <1 µs |

둘 다 공유 메모리를 통한 데이터 교환을 위한 NTP SHM을 지원합니다.

RPi 5의 GPIO 칩

gpiochip0은 더 이상 사용되지 않습니다. RPi 5/Trixie에서는 gpiochip4를 사용하세요 (gpiodetect로 확인).

시스템 아키텍처

두 가지 시간 소스:

  • GPS NEO-M8T: PPS (/dev/pps0) + gpsd를 통한 NMEA (NTP SHM #0)
  • DCF77 RC8000: GPIO 17 → 디코더 → NTP SHM #2
  • chrony: PPS 우선 선택을 통한 stratum 1
  • 웹 모니터: :8080 포트에서 chronyc + journald

chrony 설정:

refclock PPS /dev/pps0 refid PPS precision 1e-9 poll 0 dpoll -2 lock NMEA prefer
refclock SHM 0 refid NMEA precision 1e-3 poll 0 dpoll -2 offset 0.0 delay 0.2 noselect
refclock SHM 2 refid DCF7 precision 1e-2 offset 0.0 delay 0.008 poll 6 dpoll 5
local stratum 10

DCF77는 백업 및 GPS 스푸핑 탐지기 역할을 합니다 (몇 초 이상의 불일치 시).

DCF77 디코더

커널 타임스탬프

GPIOD_LINE_CLOCK_REALTIME은 사용자 공간이 아닌 인터럽트 타임스탬프를 제공합니다. SHM의 표준 편차: <5 ms.

64비트를 위한 NTP SHM

패딩이 포함된 구조체:

struct NtpShmTime {
    int32_t mode;
    int32_t count;
    int64_t clockTimeStampSec;
    int32_t clockTimeStampUSec;
    int32_t _pad;
    int64_t receiveTimeStampSec;
    int32_t receiveTimeStampUSec;
    int32_t leap;
    int32_t precision;
    int32_t nsamples;
    int32_t valid;
};
volatile NtpShmTime *shm_ = nullptr;

DCF77 프로토콜

패리티가 포함된 BCD 형식의 59비트/분:

  • 100 ms HIGH → 비트 0
  • 200 ms HIGH → 비트 1
  • >1.5 s 일시 정지 → 분 마커

검증: 3개의 패리티 그룹 + timegm(). 부트스트랩: 60초 차이를 가진 2개의 프레임.

NEO-M8T 구성

  • TIM 펌웨어: RAIM, 낮은 PPS 지터
  • dynModel=Stationary: 지터 5–10 ns
  • antCableDelay=50 ns: 케이블 보상
  • 4개 별자리: 20–30개 위성
  • lpMode=Continuous: 절전 모드 없음

웹 모니터 및 배포

dcf77_web: HTTP :8080, JSON 형식의 chronyc, 바이너리 UI. API: /api/status, /api/prefer (로컬호스트 전용).

vcpkg + CMake로 빌드:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

Ansible 배포 및 systemd 강화.

결과

chronyc tracking:

  • Stratum: 1
  • 시스템 시간: +42 ns
  • RMS 오프셋: 51 ns
  • 루트 분산: 15 ns

DCF77: ~3 ms.

핵심 요약

  • libgpiod v2는 GPIOD_LINE_CLOCK_REALTIME을 포함한 새로운 객체 모델을 요구합니다
  • chrony는 PPS 정확도 (<1 µs)와 홀드오버 측면에서 ntpd를 능가합니다
  • 64비트 NTP SHM은 int64_t를 위한 명시적 패딩이 필요합니다
  • NEO-M8T TIM + Stationary: PPS 지터 5–10 ns
  • DCF77는 GPS 스푸핑에 대한 검증을 제공합니다

— Editorial Team

Advertisement 728x90

다음 읽기