The Dream: Precision Clock Without GPS

GPS modules offer timing accuracy in tens of nanoseconds, but they fail indoors. The author wanted a drop-in replacement using WiFi NTP on an ESP8266, targeting sub-millisecond precision. Six years later, the project remains unfinished. This is the post-mortem.

NTP Implementation Pitfalls

Most Arduino NTP libraries are trash. The built-in "NTP library" uses delay(10) in a loop, quantizing measurements to 10ms increments. The author implemented NTP from scratch, noting that only one byte in the 48-byte packet needs to be non-zero: the first byte indicating client mode and NTP version. The rest can be zero.

NTP timestamps are 8-byte fixed-point numbers: 4 bytes for seconds since 1900 (overflow in 2036), 4 bytes fractional part. Converting to Unix time requires adding an offset. Endianness matters: NTP uses big-endian, ESP8266 is little-endian. The author tried GCC's scalar_storage_order("big-endian") attribute, but Arduino's g++ doesn't support it.

Measuring PPS Jitter with a Logic Analyzer

The author used a cheap £5 USB logic analyzer (Cypress FX2, 8 channels, 24MHz) with sigrok. The key command:

sigrok-cli -d fx2lafw --config samplerate=200kHz --samples 800M -C D0,D1,D2,D3,D4 -P jitter:clk=D0:sig=D4 -B jitter

This captures offset between GPS PPS (clock) and ESP8266 PPS (signal). A Perl script wrapped the output, compensating for whole-second wraps. gnuplot provided real-time visualization.

First Results: 3ms Offset and Heavy Jitter

Initial plots showed outliers up to 15ms, biased positive. The author speculates WiFi contention in his flat caused asymmetric delays. After filtering outliers, a 3ms systematic offset remained, likely due to oscillator drift.

Disciplining the ESP8266 Oscillator

The ESP8266's internal oscillator is inaccurate and temperature-sensitive. The author planned a complex control loop: poll NTP every 16 seconds, gradually back off, keep a history of estimates, and discipline a secondary timer. The project stalled before completing this loop.

Hardware Setup

A grotty breadboard with a flying barrel jack and regulator. An FTDI cable for programming, a GPS module for reference, and the logic analyzer. A jumper enabled programming mode.

Why It Failed

Sub-millisecond accuracy over WiFi with a cheap oscillator is a hard problem. The author admits being too ambitious. But the detailed writeup (including NTP packet structure, endianness workaround, and measurement setup) is a goldmine for anyone attempting similar precision timing projects.

Key Technical Details

  • NTP requires only one byte in the 48-byte request packet.
  • ESP8266 is little-endian; NTP uses big-endian. No GCC extension support in Arduino.
  • Logic analyzer sampling at 200kHz for hours captured jitter patterns.
  • Systematic 3ms offset remained even after outlier removal.