|
Introduction
Why Cookie-GNSSDO? Take a look at the picture:

A simple cookie tin served as a provisional ground plane for the active GNSS antenna.
Why this project?
It all started with a few videos by Jeff Geerling
on the subject of PTP Grandmasters, and before I knew it, I had fallen down the rabbit hole. But instead of
experimenting with Linux and Raspberry Pis, I decided to "push the bits" directly myself, as this
is where my strengths lie.
The Plan: To realize a fully-fledged GNSS-Disciplined Oscillator (GNSSDO) using affordable,
off-the-shelf evaluation boards, which could later serve as a clock source for a flexPTP based
Grandmaster.
And so began the proof of concept for the GNSSDO.
The Implementation: My design measures the offset between the GNSS-PPS and the IEEE-1588-PPS signal.
A PI (not a Raspberry Pi, a Proportional-Integral) control loop ensures that the difference is driven
towards zero – effectively disciplining the 1588 timer directly via the GNSS signal.
The Validation: Once the system was up and running, the question arose: How good is it really? Following
a suggestion from an AI assistant, I performed a 24-hour data log for subsequent analysis. This analysis,
including all diagrams and statistical metrics, was later carried out using AI-generated Python scripts to
ensure an objective evaluation of the stability.
The GNSSDO is based on an NXP MCXN947 MCU and a u-blox NEO-M8N GNSS module. A simple cookie
tin served as a provisional ground plane for the active GNSS antenna during the measurements. It was
surprising to see how this pragmatic approach contributed to the achieved precision in the double digit
nanosecond range.
With relatively simple means and total costs of under 100 USD, a level of precision was achieved
that is demonstrated in the diagrams below. Over a 24-hour period, the system reached an MTIE of approx.
65 ns, an RMS jitter of approximately 9.9 ns, and a TDEV in the range of 68 to 138 ps.
Whether this qualifies as a "high-precision" GNSSDO is left to the community to decide.
To ensure full transparency, I am making the entire project, including all raw data, scripts,
and measurement logs openly available. Everyone is invited to verify the results for themselves.
How it's measured
To evaluate the performance of the control loop, the internal CTIMER of the MCXN947 is used to
capture the phase offset between the GNSS-PPS (Reference) and the Disciplined-PPS (Output).
Reference: u-blox NEO-M8N PPS signal.
Target: Local PPS generated by the 1588-Timer.
Resolution: With the CTIMER running at 150 MHz, the measurement resolution is 6.67 ns
per clock cycle.
Thermal Stabilization: Since the system uses an uncompensated crystal oscillator (XO),
the hardware is highly sensitive to thermal drift. To suppress transient convection and air currents,
the setup is passively insulated. In this prototype, an
IKEA SAMLA Box (5l) serves as a temporary enclosure to stabilize the ambient temperature.
Antenna Input Optimization: To combat local RF interference that previously caused
intermittent loss of all satellite signals, a
6 dB attenuator was added to the antenna input of the NEO-M8N. This modification
prevents receiver saturation and ensures stable satellite tracking, which may be beneficial depending
on your local RF environment.
This "Residual Measurement" illustrates the effectiveness of the control algorithm. It shows
how precisely the PI control loop synchronizes the local timer to the GNSS time base, effectively filtering
the GNSS jitter despite the 6.67 ns quantization of the timer.
Important Terminology Note: When I refer to an XO later on, I am referring to the crystal
oscillator used for the 1588-Timer. This does not, therefore, refer to the oscillator located
within the GNSS module.
Note on fixed delay compensation (GNSS_ANTENNA_DELAY_NS):
For evaluating control-loop performance (residual phase offset, jitter, ADEV, TDEV, MTIE), the measurement
is referenced to the PPS signal as received at the board input (raw relative timing). Antenna cable delay
compensation (e.g., ~25 ns for 5 m) is only applied when absolute UTC alignment or inter-device comparison
is required.
Startup Dynamics
This diagram illustrates the initial settling behavior of the PI control loop and the progression of the
lock-state machine immediately after powering up the GNSSDO.

(Click inside the picture to expand)
What you see:
Phase Offset (black curve): At startup, the local 1588-PPS timer starts with a significant
initial offset of nearly 120,000 ns. The PI control loop engages immediately, driving the error
down forcefully. It intentionally overshoots slightly to approximately -42,500 ns before smoothly
easing the phase error upward, passing -220 ns right around the 100 second mark as it stabilizes
toward zero.
Lock Status (blue dashed line): Represents the internal state machine of the firmware,
categorized into three distinct levels:
Freerun (0): The initial state in which the control loop attempts to reduce the phase
difference between the GNSS-PPS and 1588-PPS to less than 1000 ns. This condition must be maintained
continuously for 10 seconds before transitioning to Tracking mode.
Tracking (1): The state entered after Freerun has successfully brought the initial error
under 1000 ns. In this mode, the PI control loop fine-tunes the oscillator to actively drive the phase
offset close to zero and stabilize the loop parameters.
Locked (2): This mode is entered once the control loop achieves high stability. Transitioning
from Tracking to Locked requires three strict criteria to be met simultaneously for at
least 10 seconds: A Phase Offset of ≤ 50 ns, a RMS value of ≤ 50 ns, and a Frequency Drift of ≤ 5 ppb.
Settling Time: From the moment of power-on until the control loop has fully locked in,
the system requires approximately 220 seconds. During this particular run, the components were
already warmed up. When starting from a cold state, this process can take anywhere from 5 to 6
minutes.
Why this matters:
Loop Damping: The single, well-bounded overshoot confirms that the proportional (Kp) and
integral (Ki) parameters of the digital filter are properly tuned for a critical damping response,
avoiding endless ringing.
Phase Offset / Satellites in View
This diagram forms the foundation of the entire evaluation, as it illustrates both the time offset and the
GNSS reception conditions over the full measurement period.

(Click inside the picture to expand)
What you see:
This plot illustrates the fluctuation of the PPS offset between the GNSS signal and the disciplined
oscillator over a 24-hour period.
Phase Offset (top): The offset predominantly stays within a range of approximately ±25 ns,
with peak excursions reaching around ±35 ns.
Satellite Tracking (bottom): This curve shows the number of tracked satellites, which
typically fluctuated between 16 and 28 during the measurement.
Why this matters:
Precision: The offset represents the actual real-time timing accuracy of the GNSSDO.
Correlation: The satellite count explains the stability (or noise) of the offset at any given time.
Performance Baseline: This diagram provides the crucial first impression of the overall system
performance.
Phase Offset: Histogram / CDF
This diagram illustrates the statistical distribution of all measured PPS offsets over the full 24-hour period.
While the time-series plot shows the performance over time, this visualization summarizes the frequency of specific
offset values and highlights the typical operating range of the GNSSDO.

(Click inside the picture to expand)
The slight ripples in the histogram are a result of the system's discrete time resolution (quantization). Thanks
to the long-term 24-hour observation, this quantization noise is statistically smoothed, revealing the true
underlying performance of the oscillator.
What you see:
This diagram illustrates the statistical distribution of all measured PPS offsets over the full 24-hour period.
Histogram (top): This plot represents the frequency distribution of the measured offset values. It
clearly shows that the majority of samples lie within approximately ±19 ns. The mean value is
centered at approximately -0.01 ns, and the RMS jitter is 9.91 ns.
CDF Curve (bottom): Shows the cumulative percentage of measurements within a given offset range. It
demonstrates high consistency: 95% of all values stay within ±19 ns, and 99%
remain within ±24 ns.
Why this matters:
Short-term Stability: The histogram visualizes the short-term stability of the GNSSDO and illustrates
how tightly the offset distribution is clustered.
Statistical Boundaries: The CDF provides concrete statistical limits that are critical for timing
applications (e.g., 95% or 99% coverage intervals).
Reliability: Together, both plots provide a precise picture of how reliably and reproducibly the
GNSSDO operates within the nanosecond range.
Quantitative Summary: This representation perfectly complements the time-series plot by summarizing
the overall performance quantitatively.
Residual Allan Deviation
This diagram shows the Allan Deviation (ADEV) of the GNSSDO across various observation times (Tau). While previous
plots focused on phase offset and its distribution, the Allan Deviation characterizes the frequency stability
of the disciplined oscillator. It illustrates how effectively the loop filter suppresses both short-term noise and
long-term drift.

(Click inside the picture to expand)
The steady decline of the curve demonstrates that the system becomes increasingly precise over longer
observation periods. At an averaging time of approximately 2.8 hours (104 s) the frequency
stability reaches a level of nearly 10-12, representing excellent performance for a disciplined
standard crystal oscillator (XO).
What you see:
This plot illustrates how the Allan Deviation changes as the observation time (τ) increases.
Short-term Performance: For short intervals between 1 to 10 s, the ADEV stays within a range
of approximately 10-8 to 10-9, which is characteristic of the jitter inherent
in GNSS signals.
Long-term Stability: As the observation time increases, the ADEV continues to drop, reaching
values between 10-10 and 10-11 for τ values of 100 to 1000 s.
Loop Efficiency: This downward trend confirms that the digital disciplining loop effectively smooths
the oscillator, providing a highly stable frequency over extended periods.
Why this matters:
Core Metric: The Allan Deviation is the definitive metric for evaluating the frequency stability
of an oscillator.
Loop Health: A consistently falling curve proves that the GNSSDO operates with long-term stability
and that the loop filter parameters are correctly tuned.
Noise Analysis: ADEV allows for the identification of underlying noise sources, such as phase noise,
flicker noise, or frequency drift.
Network Sync: For applications like PTP Grandmasters or time distribution systems, a low
ADEV is critical, as it directly determines the quality of the generated PPS and IEEE 1588 signals.
Complementary Data: This plot complements the previous offset diagrams by visualizing the
frequency-side performance of the system.
MTIE / TDEV
This diagram illustrates two critical metrics from telecommunications and synchronization engineering:
Maximum Time Interval Error (MTIE) and Time Deviation (TDEV). Both metrics are used in ITU standards,
such as G.8261 and G.8271, to evaluate the timing stability and accuracy of a clock system. While MTIE
describes the maximum time error over various observation intervals, TDEV illustrates timing stability
in a manner similar to Allan Deviation, but follows telecommunications industry definitions.

(Click inside the picture to expand)
Note: The rise at the end of the MTIE curve (near 105 s) is an artifact caused by the
finite length of the 24-hour data set and does not indicate any system instability.
What you see:
MTIE (top): The Maximum Time Interval Error graph shows the peak phase deviation over time.
For this 24-hour run, the error stays well below 65 ns.
TDEV (bottom): The Time Deviation curve highlights the precision in the time domain. It starts
around 10 ns at Tau = 1 s and consistently drops, reaching its minimum between 68 ps and 138 ps
at observation times of several thousand seconds.
Phase Noise suppression: The smooth, descending TDEV curve confirms that the system effectively
filters out high-frequency jitter from the GNSS receiver.
Why this matters:
Network Limits: In professional telecommunications, MTIE is used to define wander masks. For
this 24-hour capture, the MTIE stays well below 65 ns, which is a strong indicator of a high-quality
clock source for packet-switched timing applications.
Sub-Nanosecond Precision: A TDEV minimum in the 68 ps to 138 ps range demonstrates that the
disciplined loop provides very tight time stability at long observation intervals, comparable to what is
often expected from much more expensive dedicated timing modules.
System Integrity: A smooth MTIE trace without sudden step changes indicates that the MCXN947 PI
control loop maintained stable phase tracking throughout the full measurement window.
PI-Control Loop Activity
This diagram illustrates the progression of the timer addend correction over time. This value represents the
frequency adjustments the GNSSDO applies to its local oscillator. The addend determines the rate at which the
CPU's internal timer increments. Any changes directly reflect the effort required by the control loop to
keep the local oscillator synchronized with the GNSS signal. Consequently, this plot provides crucial insights
into oscillator drift, thermal behavior, and the overall stability of the control loop.

(Click inside the picture to expand)
What you see:
Drift tracking: The addend trace follows the slow frequency drift of the crystal oscillator
(temperature and aging), which is exactly what a GNSS-disciplined clock must compensate continuously.
Control loop effort: Short-term wiggles reflect normal GNSS timing jitter and small loop corrections.
The overall motion stays bounded, indicating stable loop operation without large corrective bursts.
Why this matters:
Loop transparency: The addend is the "control effort" signal. A smooth long-term trend
with moderate short-term activity usually indicates a well-damped control loop that is not fighting instability.
Hardware reality: A standard XO is never exactly on-frequency; the disciplined loop must keep
adjusting the timer increment rate. This plot shows that the software is actively maintaining lock to the
GNSS time base over the full 24-hour run.
Reproducibility: Because the addend reflects real environmental drift, it is also a practical
sanity check that the measurement window captured normal operating conditions rather than an artificially
quiet startup period.
This diagram does not just show the end result, but the actual "work in progress" of the PI
control loop. Since a standard crystal oscillator (XO) never vibrates at the exact target frequency and
is highly sensitive to temperature fluctuations, the software must constantly provide active compensation.
PI-State: Drift (I) vs. Jitter (P)
This diagram illustrates the internal states of the PI control loop that regulates the oscillator.
The P-Term responds to short-term deviations (jitter), while the I-Term compensates for the long-term
drift of the local oscillator. This plot provides a direct look into the control loop's behavior and
demonstrates how stably and cleanly the GNSSDO operates internally.

(Click inside the picture to expand)
The I-Term smoothly follows the thermal drift of the crystal, while the P-Term (shown here with a 60-second
moving average) merely compensates for the unavoidable statistical noise of the GNSS receiver. This
demonstrates a stable settling behavior and the absence of any dangerous resonances.
What you see:
P-Term Dynamics: The P-Term curve shows fast, minor fluctuations within a range of a few ppb.
These movements reflect the short-term GNSS jitter and demonstrate how the control loop responds to rapid
changes in offset. The low amplitude indicates a well-damped control loop.
I-Term Progression: The I-Term is significantly smoother, showing gradual changes over the
entire measurement period. This movement corresponds to the long-term drift of the XO oscillator caused
by temperature fluctuations and the natural aging of the crystal. The I-Term continuously compensates for
this drift, keeping the oscillator aligned with GNSS time on average.
Synergy: Together, both curves illustrate how the PI control loop simultaneously mitigates
short-term disturbances and long-term drift.
Why this matters:
Sensitivity: The P-Term demonstrates how the control loop responds to short-term jitter. A stable
progression indicates that the control loop does not overshoot.
Drift Compensation: The I-Term reveals the long-term compensation performance and provides insights
into the quality and thermal stability of the local XO oscillator.
Loop Stability: The combination of both terms confirms whether the PI control loop is stable,
well-tuned, and free of oscillations.
Internal Dynamics: This plot is particularly valuable for developers, as it visualizes the internal
dynamics of the control loop – something that cannot be identified from phase offset or ADEV plots
alone.
Predictability: A clean and smooth progression of both terms confirms that the GNSSDO operates
in a robust, stable, and predictable manner.
RF Environment & Diagnostics
This multi-plot diagram provides deep insights into the Radio Frequency (RF) environment and the health of the
receiver's front-end setup using data from the UBX-MON-HW and UBX-NAV-SAT messages.

(Click inside the picture to expand)
Phase Offset & Satellites In View: Shows the continuous phase tracking performance
and the stable count of tracked satellites (16 to 28), ensuring a robust data pool.
Avg C/N0 (Carrier-to-Noise Density Ratio): Displays the average signal strength fluctuating
smoothly between 27 dB-Hz and 38 dB-Hz. The daily wave pattern reflects the natural motion of the
satellite constellations.
Noise per ms: Monitors the baseline electromagnetic noise floor. The stable level around
70 to 136 indicates the absence of heavy broadband interference near the antenna
(dimensionless, u-blox internal scale, see UBX-MON-HW).
AGC Monitor (Automatic Gain Control): Represents the internal amplifier utilization
As percentage of maximum gain, range 0 to 100%. The steady level between 24% and 33%
confirms a strong, healthy signal from the active antenna without overloading the RF stage.
CW Suppression (Continuous Wave Jamming Detection): Tracks the activity of the receiver's
internal notch filters mitigating narrowband interference, scaled 0 = no CW jamming, 255 = strong CW
jamming.
Note: The 24-hour and 72-hour plots on this page were recorded with continuous GNSS lock
and therefore do not demonstrate holdover performance.
Summary
The following tables provide a compact summary of all key measurement results from the evaluation. The first
table contains the core timing metrics that describe the external performance of the GNSSDO – including
offset stability, jitter, Allan Deviation, and time error limits. The second table focuses on the control loop
metrics, offering insights into the internal behavior of the PI loop and illustrating the oscillator's
drift and jitter compensation. Together, these tables provide a clear and comprehensive overview of the overall
performance and stability of the GNSSDO.
Timing Metrics (GNSSDO Performance)
| Metric |
Value(s) |
Unit |
Meaning / Interpretation |
| Phase Offset |
typ. ±25, max. ±35 |
ns |
Short-term deviation between GNSS and 1588 timer. |
| Satellites (Used) |
16 to 28 |
count |
High redundancy and stability through Multi-GNSS. |
| Histogram Mean |
-0.01 |
ns |
Near-zero systematic time error (Bias). |
| RMS Jitter |
9.91 |
ns |
Short-term precision of the disciplined clock. |
| CDF 95% Range |
±19 |
ns |
95% of all offsets fall within this confidence interval. |
| CDF 99% Range |
±24 |
ns |
99% of all offsets fall within this confidence interval. |
| ADEV (1 to 10 s) |
~10-9 |
– |
Characteristic short-term GNSS jitter. |
| ADEV (100 to 1,000 s) |
10-10 to 10-11 |
– |
Excellent long-term frequency stability. |
| ADEV (10,000 s) |
1.8 × 10-12 |
– |
Frequency stability in the parts-per-trillion range. |
| MTIE (1 to 100,000 s) |
50 to 66 |
ns |
Maximum Time Interval Error over 24 h. |
| TDEV minimum |
68 to 138 |
ps |
Time stability (complementary to ADEV). |
Control loop Metrics (PI Loop Behavior)
| Metric |
Value(s) |
Unit |
Meaning / Interpretation |
| Addend Correction |
-12,200 to -12,780 |
ppb |
Total correction range required to compensate for crystal drift. |
| P-Term StdDev |
14.87 |
ppb |
Metric for control loop stability and effective jitter attenuation. |
| Control loop Behavior |
Stable |
– |
No oscillations or resonances observed within the PI loop. |
Conclusion
Since I do not have years of experience in high-precision metrology to evaluate these results conclusively,
I submitted the data to an AI for analysis. Below is the AI-generated summary of the project:
"The present analysis proves that modern general-purpose GNSS modules like the u-blox NEO-M8N,
combined with a precise digital control loop and the use of IEEE 1588 hardware support, deliver results
that were previously reserved for expensive specialized hardware.
Despite its pragmatic construction, the system achieves a stability (ADEV in the 10-12 range)
and a timing accuracy (RMS jitter ~9.91 ns) that qualify it for demanding synchronization tasks in PTP
environments. This makes the GNSSDO proof that high-end performance is now achievable with
affordable, off-the-shelf components."
Holdover Performance Limitations
While the analysis confirms excellent performance under normal operation, there is an important technical
trade-off regarding holdover mode (i.e., when the GNSS signal is unavailable).
A traditional GNSSDO relies on a high-stability oscillator (such as a TCXO or OCXO) to maintain accurate
time during signal outages. Since this project uses only a standard crystal oscillator (XO), the
achievable holdover performance is inherently limited. Without GNSS disciplining, the frequency drift of
a standard crystal oscillator results in a usable holdover period on the order of ~30 seconds.
How the ~30 s figure was determined: Holdover was not evaluated using a prolonged GNSS outage. Instead, the
PI control loop was manually disabled, preventing further updates of the 1588 timer addend. With disciplining
frozen, the phase offset between the GNSS-PPS reference and the local 1588-PPS output increases as the
uncompensated crystal oscillator drifts.
In repeated tests, the phase error typically exceeded 100 ns after approximately 30 seconds, with variations
of several seconds depending on temperature and instantaneous oscillator drift. This should be regarded as an
empirical estimate of short-term holdover behavior rather than a measurement of sustained GNSS outage performance.
This limitation is a deliberate trade-off to keep hardware cost below 100 USD. The project is designed for
applications requiring high-precision timing under continuous GNSS lock, rather than long-duration autonomous
operation during GNSS loss.
Extra: 72-hour long-term recording
This section adds a continuous 72-hour capture (standard XO) alongside the 24-hour results above.
Same hardware, configuration, and raw relative measurement.
The diagram below illustrates the inner workings of the PI control loop over a 72-hour period.
It visualizes how the firmware splits its tasks into long-term drift compensation and short term
jitter filtering.

(Click inside the picture to expand)
The significant wave-like movement of the black I-Term curve is completely normal and demonstrates
that the software loop is working exactly as intended:
The First Two Nights (May 28th & 29th): The prominent peaks and valleys (spanning roughly 800 ppb)
directly reflect the day-and-night temperature cycles in the room. As the room cooled down at night, the raw
oscillator's frequency drifted. The integrator caught this instantly and adjusted the correction value
to keep the output stable.
The Final Run & Heat-Up (May 30th): Towards the end of the measurement, the curve shows a continuous
downward trend. This progressive drop is the direct result of a subsequent rise in room temperature (reaching
24.8 °C). Because the uncompensated crystal reacts dynamically to ambient heat, the I-Term continuously
shifts the correction downward to actively counteract this thermal push.
Note: The drifting I-Term curve is neither an error nor an indication of instability. It serves as visual proof
that the PI control loop successfully counteracts the physical characteristics of a low-cost, uncompensated XO
solely through pure software logic.
Use the links below for full-size plots:
72-hour summary (standard XO). The Summary table in the main section above still refers to the primary
24-hour evaluation.
| Metric |
Value(s) |
Unit |
Notes |
| Duration |
72 |
h |
Continuous capture (~May 28 to 30 UTC) |
| Samples |
259,811 |
– |
Phase offset samples |
| Phase Offset |
typ. ±25, max. ±35 |
ns |
From time-series |
| Satellites (Used) |
16 to 28 |
count |
Stable tracking |
| Histogram Mean |
-0.03 |
ns |
Near-zero bias |
| RMS Jitter |
9.94 |
ns |
Comparable to 24 h |
| CDF 95% Range |
-19 to +20 |
ns |
|
| CDF 99% Range |
-23 to +24 |
ns |
|
| ADEV (10,000 s) |
~1.4 x 10-12 |
– |
Long Tau from 72 h data |
| MTIE (full window) |
~50 to 66 |
ns |
Peak interval error |
| TDEV minimum |
~80 |
ps |
At Tau 104 |
| Addend correction span |
~1000 |
ppb |
From -12,000 to -13,000 ppb |
| P-Term StdDev |
14.90 |
ppb |
Similar to 24 h |
| RF / CW |
CW spikes visible |
– |
Phase offset remained bounded, see RF plot |
Hardware
The required hardware is affordable, with total component costs amounting to approximately 96 USD
(plus taxes/shipping). Below are the prices at the time of writing:
In total, the setup stays just under the 100 USD mark without the optional accessories.
Optional Accessories:
A 6 dB SMA fixed attenuator was used for all measurements shown on this page. In your own
setup, you may only need one if you see RF overload, intermittent loss of satellites, or
strong local interference.
6 dB SMA Fixed Attenuator (e.g., BECEN 2W SMA attenuator). Since an
active antenna is used, attenuation can prevent the NEO-M8N input from saturating due
to out-of-band noise. Pay attention to the input and output assignments. SMA adapters
are required as listed below.
SMA Female to SMA Female is needed to connect the Antenna to the Attenuator.
SMA Male to SMA Male is needed to connect the Attenuator to the NEO-M8N module.
Passive Insulation: IKEA SAMLA Box (5l), a budget-friendly temporary enclosure to mitigate thermal
drift from air currents.
Yes, it might sound a bit strange to use an active antenna only to apply an attenuator afterward.
However, since I didn't have a passive antenna on hand, I decided to use the attenuator to
prevent the NEO-M8N's input stage from saturating and to increase its headroom.
This is what the FRDM-MCXN947 looks like with the GNSS 5 click mounted:
For optimal performance, it is recommended to place the active GPS antenna on a metallic surface (ground plane).
Even a simple metal cookie tin, as used in this project, works perfectly to improve the signal quality.
To use the GNSS 5 click (NEO-M8N) for this GNSSDO project, a small hardware modification is required to
route the Timepulse (PPS) signal correctly. Since the pins on the NEO-M8N module are easily accessible,
this can be done with just a bit of soldering. See the image below for the exact pin locations:
This modification ensures that the MCU receives the high-precision pulse via the MikroBUS header, which
is essential for frequency synchronization.
What's next?
Anyone who has made it this far might want to try replicating the project. That said, this isn't
actually a fully independent project yet. It is currently a proof-of-concept to test whether it
can work. Later on, I plan to use this part with flexPTP to create a GNSSDO-PTP-Grandmaster. That's
the plan, let's see if it will work.
To replicate the setup, the following configuration steps are still required:
NEO-M8N configure
FRDM-MCXN947 configure
The sections below cover board setup and software so you can build, measure, and analyze your own GNSSDO data.
Software
Building and Flashing
Firmware
You should now have a setup that allows you to take your own measurements, just like I've done here.
To store the data, I used a Raspberry Pi with Telegraf and InfluxDB. I installed Telegraf and InfluxDB
together with Portainer using Docker. Here are my Docker Compose files for your use.
Python, along with AllanTools and various scripts, was used for the subsequent analysis. I created the
scripts with the help of an AI assistant.
Next up is the download section. I'll be storing my source code and data in a GitHub project.
So you should now be able to reproduce this project on your own.
NEO-M8N configure
For optimal performance as a GNSS source for the GNSSDO, the NEO-M8N must be configured accordingly.
By default, these modules are typically set to standard parameters for mobile navigation, which is not
ideal for a high-precision time base.
To set it up, I simply connected only the NEO-M8N module to the PC via USB.
For the configuration, I used u-center version v25.06. The configuration is performed within u-center
using the "Configuration View" window.
First, the communication speed of the serial interface is adjusted to ensure smooth data transmission:
The configuration is performed in three simple steps:
Selection: Select PRT (Ports) from the list on the left.
Setting: Change the Baudrate to 115200.
Apply: Click the Send button to transfer the setting to the module.
To improve the stability of the time base with a fixed antenna, the dynamic model is changed to
"Stationary".
Selection: Select NAV5 (Navigation 5) from the list on the left.
Setting: Change the Dynamic Model to 2 - Stationary.
Apply: Click the Send button to transfer the setting to the module.
To avoid unpredictable correction calculations that could affect the jitter of the time pulse,
the SBAS function is disabled:
Selection: Select SBAS (SBAS Settings) from the list on the left.
Setting: Set Subsystem to Disabled.
Apply: Click the Send button to transfer the setting to the module.
To optimize processing performance, only the GPS, Galileo, and GLONASS systems are enabled,
while all others (e.g. BeiDou, QZSS) are disabled:
Selection: Select GNSS (GNSS Config) from the list on the left.
Setting: Enable the checkboxes for GPS, Galileo, and GLONASS.
Disable all other systems.
Apply: Click the Send button to transfer the setting to the module.
Finally, the changes must be saved permanently so that they are not lost after a restart or power failure:
Selection: Select CFG (Configuration) from the list on the left.
Setting: Ensure that Save current configuration is selected and highlight the entries
0 - BBR and 2 - FLASH under "Devices".
Apply: Click the Send button to write the configuration permanently to the memory.
This completes the configuration of the NEO-M8N, and the module is now optimally prepared for use as a
precise time reference in the GNSSDO.
FRDM-MCXN947 configure
The board includes an MCU-Link debug probe. For SEGGER Embedded Studio, its firmware must be updated from
the factory CMSIS-DAP image to a J-Link-compatible image. The probe hardware stays the same, only the
firmware changes.
The NXP LinkServer for Microcontrollers is required to update to a J-Link debug probe. As of the
time I am writing this description, version 26.3.123 is the latest version. Please install LinkServer according
to your operating system. I used the Windows version here.
To prepare for an update, jumper J21, shown here as the red jumper, must be set:
It is important to note that the board should only be connected to the PC via the USB-C port J17 (MCU-LINK)
after jumper J21 has been set. In the image, this is the USB-C port in the upper right corner.
I'm using Windows here, so I can't say how the process works on other operating systems.
The MCU-LINK_installer was installed alongside the LinkServer. This directory contains another directory
named "scripts". To update to a J-Link probe, please double-click the program_JLINK.cmd file.
After double-clicking, a Command Prompt window should open that looks like this:
Now that you have pressed a key, the update will begin:
After a successful update, the window looks like this:
You can now close the Command Prompt by pressing "CTRL-C" as prompted by the script. Then
disconnect the USB-C cable from the board and remove jumper J21.
If the NEO-M8N module isn't already plugged in, you should plug it in now and connect the antenna as
well. But please don't reconnect it via USB just yet. We'll do that later, once we have installed
the J-Link Tools and SEGGER Embedded Studio.
Software
First of all, the toolchain. The embedded firmware is developed using
SEGGER Embedded Studio, which is available free of charge for non-commercial use under
SEGGER's
Friendly License. In addition, the J-Link tools are required too.
For this project, SEGGER Embedded Studio version 8.28 and J-Link Tools version 9.38a were used.
You can download the software here:
Please install both pieces of software. After that, we will check whether the J-Link is recognized on
the FRDM-MCXN947 board and whether another update is needed. Once both pieces of software have been installed,
the board can be reconnected to the PC via USB.
Once the board is reconnected to the PC, launch the "J-Link Commander" software from the
Start menu. A command prompt should open that looks like this:
You should see the onboard probe listed as a J-Link (serial number / firmware version). If no emulator is
found, recheck USB port J17, jumper J21 removed, and that the MCU-Link was flashed to J-Link as described
above.
If an update for the J-Link firmware is available, it will be installed automatically. As shown in the
screenshot here, the J-Link firmware on my board was updated. You can close the J-Link Commander by pressing
"CTRL-C".
Building and flashing the GNSSDO firmware is covered in the Building and Flashing section below.
Building and Flashing
At this point, the onboard probe should already run the J-Link firmware, and the J-Link Tools and
SEGGER Embedded Studio (SES) should be installed (see FRDM-MCXN947 configure and Software above).
The frdmn947-gnssdo project's directory structure looks like this:
The repository/project name frdmn94-gnssdo refers to the FRDM-MCXN947 board used in this build.
The actual project file used for SES is located in the "prj" folder. Here the file
"frdmn947-gnssdo.emProject":
Double-clicking on "frdmn947-gnssdo.emProject" launches SES, which should look as follows:

(Click inside the picture to expand)
In this project, the "Project Explorer" allows you to select which settings should be used.
Here, "Flash Release" is selected:
The following project settings are available:
Flash Debug
Flash Release
TCM Debug
TCM Release
In the GNSSDO project, there is the option to build the project for either the Flash or TCM region.
TCM stands here for Tightly-Coupled Memory, which is a fast block of RAM. During the development phase,
I prefer to use the TCM region, as saving the program there can sometimes be faster than in the Flash
region. However, the TCM region is volatile memory. This means that if this area is used for the program,
the program will be lost following a reset or a power cycle.
"Flash Release" was selected here because the program is intended to restart automatically later,
following a reset or power cycle.
One way to build the project is, for example, via "Build / Rebuild frdmn947-gnssdo":
After a successful build, the program can be downloaded by e.g. "Start Debugging":
And then started via "Continue Execution":
Alternatively, the download can be initiated by pressing F5, and then launching by pressing F5 again.
I recommend using the "TCM Debug" setting here to experiment with the project and familiarize
yourself with SES.
Firmware
This project is based on one of my lwIP projects and therefore uses the following core components:
The extensions for the GNSSDO functionality are all located in the "gnss_sync" directory
which contains the following files:
gnss_data: This module takes care of the communication with the GNSS receiver. It disables the standard
NMEA messages and enables the binary UBX protocol instead, while also handling the decoding of all incoming UBX
data.
gnss_sync: This is the heart of the GNSSDO control logic. It utilizes the CTIMER of the MCXN947 CPU to
precisely measure the offset between the GNSS-PPS pulse and the internal 1588-PPS signal. In addition, the lock
state machine shown in startup dynamics is also implemented here.
pi_ctrl: This file contains the actual PI control loop. It takes the measured timing offset as its
input and calculates the required "Addend" value for the 1588 timer to keep everything perfectly in sync.
Keep in mind that this is currently more of a proof of concept than a finished project. It is intended
to serve as a foundation for a future GNSSDO-PTP-Grandmaster project – though I can't say yet whether that
will be feasible with this specific GNSSDO.
The software core of the GNSSDO relies on the CTIMER peripheral of the MCXN947 to precisely capture the PPS
signal from the GNSS module. To minimize overhead and latency, NMEA messages are entirely disabled in favor
of the binary UBX protocol. Regarding the offset calculation for very large offsets, there was a race condition
that the AI brought to my attention, which has since been fixed.
How the Offset is Measured:
The time difference measurement relies on the CTIMER hardware and is handled entirely within an Interrupt
Service Routine (ISR). When a PPS pulse occurs, the current timer value is captured and stored in either
pps_value or gps_value, and the corresponding pps_ready or gps_ready flag is
raised. Since these variables are modified inside the ISR and processed later by a task, they are all marked
as volatile.
Once both flags are set, the ISR releases a semaphore to wake up the SyncTask. Inside this task, the
function ProcessSyncPPS calculates the timing offset
Naming in the firmware: gps_value holds the CTIMER capture timestamp for the GNSS reference pulse
(u-blox NEO-M8N PPS). pps_value holds the capture timestamp for the local disciplined pulse (IEEE
1588 timer PPS output). The residual offset is therefore "reference minus local":
offset = gps_value - pps_value;
The Race Condition and its Fix:
While this approach works flawlessly when the offset is small (e.g., under 2 milliseconds), it triggered a
subtle race condition during very large offsets – such as 998 milliseconds.
Imagine a scenario where pps_value is updated first, and gps_value arrives much later. Both flags are now set,
and the SyncTask is unblocked. However, right at this exact moment, a new PPS pulse arrives before the task
can finish its calculation. The ISR fires again and overwrites pps_value with a brand-new timestamp. When the
SyncTask resumes, it calculates the offset using the newly updated pps_value combined with the old gps_value.
Because the two values no longer belong to the same clock cycle, the resulting offset calculation is corrupted.
To resolve this race condition, the ISR was updated to copy both timestamps into a dedicated set of shadow
variables – pps_latched_value and gps_latched_value – just before releasing the semaphore. This
freezes the data pair safely inside the interrupt context. The SyncTask now reliably calculates the offset
using these latched values:
offset = gps_latched_value - pps_latched_value;
The 32-Bit Register Limit & The Solution:
With an ideal 50 MHz clock source, the timer's Increment register is set to 20 and the
Addend register is set to its maximum value of 0xFFFFFFFF. However, if the crystal oscillator is slightly
inaccurate or off-frequency, the control loop might need an Addend value greater than 0xFFFFFFFF to
compensate – which is structurally impossible in a 32-bit register.
To bypass this limit, the Increment register can be increased to 25. This lowers the nominal Addend
value, leaving plenty of headroom in the register. While this solves the clipping issue, a multiplicative
control approach now becomes highly non-linear and fails to stabilize the offset close to zero.
The Trick: Instead of complex multiplication, a fixed DEFAULT_ADDEND value was determined using
an oscilloscope to produce a PPS period of roughly one second. The PI control loop now calculates the required
frequency correction directly in PPB (corr_ppb). The new Addend value is then updated using a straightforward
subtraction:
addend = DEFAULT_ADDEND - (int32_t)corr_ppb;
By switching to this linear addition/subtraction method, the problematic internal integral behavior of the
hardware was bypassed. This allowed the use of a standard, well-behaved PI control loop. The results are very
good now: the system achieves a timing offset well within typ. ±25 ns, max. ±35 ns, with
an RMS of just approximately 9.9 ns over a 24-hour period.
To verify the performance of the PI control loop and capture the long-term stability of the GNSSDO, a dedicated
monitoring setup was built. More about this in the next section.
Portainer, Telegraf, InfluxDB
For long-term recording of the measured values, Portainer, Telegraf, and InfluxDB 2.7 are used as Docker containers
running on a Raspberry Pi 4. It does not necessarily have to be a Raspberry Pi 4; any other Linux PC capable
of running Docker and the required containers can be used instead. The data from the GNSSDO block is sent via
UDP in JSON format to Telegraf, where it is decoded and passed to InfluxDB. Inside InfluxDB, the data is then
stored in a time-series database.
Note: There is a specific detail regarding this Docker installation. An internal network
named "proxy" is used for the containers. Therefore, the network must be created first using the following
command:
docker network create proxy
The Docker Compose files are located in the following directories:
docker\compose\influxdb
docker\compose\portainer
docker\compose\telegraf
Portainer is only used as a web UI to manage the containers; Telegraf and InfluxDB handle the actual data processing.
The MCU sends JSON measurement frames via UDP to the Telegraf host. Therefore, the corresponding IP address in the
"IP_TELEGRAF_HOST" definition within the header file "incprj\project.h" must be adjusted
accordingly.
Note that Telegraf also uses a configuration file named "telegraf.conf". This file contains the
elements "token", "organization", and "bucket", which need to be adjusted
accordingly. However, I strongly recommend changing only the "token", as my scripts rely on the
other variables remaining as they are configured.
When configuring InfluxDB, you will obtain the "token" that must be entered into the "telegraf.conf"
file. Additionally, a bucket named "gm_data" must be created within InfluxDB.
Since comprehensive guides on Docker, Portainer, InfluxDB, and Telegraf are widely available online, this
section focuses purely on providing a transparent, ready-to-use configuration for this project. Feel free
to explore the provided Compose files to customize the setup for your own needs.
With the database environment ready, the next step covers the scripts used for data analysis.
Python scripts, AllanTools
Since I lack experience with Python, these analysis scripts were created with the assistance of an AI.
The scripts are located in the following directories:
data\M8N-24-6
data\M8N-72-6
These directories contain the 24-hour and 72-hour recordings, respectively. The folder "M8N-24-6"
represents the 24-hour recording using a NEO-M8N receiver and a 6 dB attenuator. The contents of this
directory are as follows:
For this project, Python version 3.13.12 under Windows was used.
00-get-data.py: This script is used to export the data from InfluxDB. Users only need to change
the URL to match their own InfluxDB server. The default configuration is set as follows:
url = "http://192.168.1.60:8086"
The script generates a CSV file named "gnss_data_m8n.csv". I have included my own generated
CSV file in the repository so you can immediately run and test the evaluation scripts with real
measurement data.
Analysis & Output: Once the CSV file is present, the remaining scripts can be executed
sequentially according to their numbering (01 to 08). These scripts process the data and generate
the corresponding plots inside the current working directory. For full transparency, my own generated
plots are already included in the "images" folder.
AllanTools (see requirements.txt) is used for Allan deviation and related stability metrics; the numbered
scripts 01 to 08 turn the CSV data into the plots shown in the measurement sections on this website.
Installing Dependencies:
Before running the scripts, you need to install the required Python libraries. This can be done easily using
the provided "requirements.txt" file. Open your command prompt or terminal in the script directory
and execute the following command:
pip install -r requirements.txt
The file contains the following specific versions used for this project:
The directory "M8N-72-6" is structured identically and contains the scripts and reference data for
the 72-hour recording.
Terminal Output
The terminal provides a continuous debug stream showing the internal state of the GNSSDO loop (e.g., current
offset, RMS, and drift values):
During operation, you can use the following interactive single-character commands:
g: General Information & System Status. Prints a comprehensive summary of the current receiver
state, including uptime, detailed lock status, satellite count (e.g., GPS, Galileo, GLONASS), signal-to-noise
ratios (C/N0), jamming levels, and exact UTC time parameters.
q: Start Holdover Test. Manually deactivates the internal PI control loop to test the stability
and drift of the oscillator without GNSS corrections.
w: Stop Holdover Test. Reactivates the PI control loop and resumes normal disciplined operation.
By pressing "g", the full system status is displayed as follows:
By pressing "q", the Holdover test is started. The output changes as follows, showing the
free-running drift until the test is stopped again by pressing "w":
As explained in the performance analysis section, the holdover period depends heavily on temperature and
instantaneous crystal drift. While the average usable holdover is around ~30 seconds before exceeding a 100 ns
phase error, the terminal log above shows a real-world example where the error limit is reached after 24 seconds
(visible where lock: 2 drops to lock: 1 at timestamp 625.682).
Did I use AI here?
Yes, and here is the story behind it.
I started this project purely out of curiosity, triggered by diving into the "GNSSDO rabbit
hole" after watching videos by Jeff Geerling. Before this, I had zero experience with GNSSDO
timing metrology and data evaluation, or Python programming.
To bridge this gap, I used Artificial Intelligence (AI) as a digital mentor and development assistant
in the following areas:
Concepts & Metrology: AI helped me understand what kinds of diagrams, metrics, and data
points are essential for evaluating a GNSSDO.
Python Programming: Since I hadn't used Python in my projects before, the AI helped me
create and debug the scripts for data processing and visualization.
Mathematical Functions: AI helped me develop the algorithms for RMS (Root Mean Square) and
clock drift calculations.
Content & Translation: AI helped fine-tune the English phrasing and translations across
the project documentation to ensure clarity.
The AI summary in the Conclusion is based on my measurements and plots; it does not replace hands-on
review of the firmware or raw data.
What I did myself:
The hardware integration, system architecture, and the complete firmware on the MCXN947 are my own work.
That includes the lock state machine, CTIMER-based phase measurement, PI control loop tuning, and logging/capture.
Where AI helped on the firmware side was as a second pair of eyes, not as an author. For instance, it
reviewed the PI control loop implementation and pointed out a race condition in the capture code, which
I corrected after verifying the issue.
The Limitations: Always Verify
Using AI requires critical thinking, as different models have vastly different capabilities and technical
limits. For example, during development, I asked AI "A" a question about this project's website
and received a plausible answer. However, when I cross-checked and asked if it could actually access my
live URL, it admitted it could not and had only relied on generic search snippets. In contrast, AI
"B" was genuinely capable of accessing and analyzing the live website. This experience showed
me that you must always double-check AI outputs and understand the tool's actual boundaries.
Why I am sharing this?
For me, AI did not replace the engineering effort; it lowered the entry barrier to a highly specialized
topic. It allowed me to focus on the hardware integration and overall system design while learning new
skills at an accelerated pace.
Download
Since you made it all the way through the documentation, you've earned access to the repository.
I hope you didn't cheat by skipping straight to the bottom! 😉
The project is fully open-source – you can find the repository on GitHub:
frdmn947-gnssdo
|