GNSSDO
Introduction
How it's measured
Startup Dynamics
Phase Offset / Satellites in View
Phase Offset: Histogram / CDF
Residual Allan Deviation
MTIE / TDEV
PI-Control Loop Activity
PI-State: Drift (I) vs. Jitter (P)
RF Environment & Diagnostics
Summary
Conclusion
Holdover Performance Limitations

Extra: 72-hour long-term recording

Hardware
What's next?

NEO-M8N configure
FRDM-MCXN947 configure
Software
Building and Flashing
Firmware
Portainer, Telegraf, InfluxDB
Python scripts, AllanTools
Terminal Output
Did I use AI here?
Download
 

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:

  • Step 1: Create a solder bridge between Pin 5 and Pin 6 directly on the NEO-M8N module.

  • Step 2: Use a thin magnet wire to connect Pin 3 with Pin 15.

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.

  • Portainer, Telegraf, InfluxDB

Python, along with AllanTools and various scripts, was used for the subsequent analysis. I created the scripts with the help of an AI assistant.

  • Python scripts, AllanTools

Next up is the download section. I'll be storing my source code and data in a GitHub project.

  • Download

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:

  1. Selection: Select PRT (Ports) from the list on the left.

  2. Setting: Change the Baudrate to 115200.

  3. 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".

  1. Selection: Select NAV5 (Navigation 5) from the list on the left.

  2. Setting: Change the Dynamic Model to 2 - Stationary.

  3. 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:

  1. Selection: Select SBAS (SBAS Settings) from the list on the left.

  2. Setting: Set Subsystem to Disabled.

  3. 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:

  1. Selection: Select GNSS (GNSS Config) from the list on the left.

  2. Setting: Enable the checkboxes for GPS, Galileo, and GLONASS. Disable all other systems.

  3. 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:

  1. Selection: Select CFG (Configuration) from the list on the left.

  2. Setting: Ensure that Save current configuration is selected and highlight the entries 0 - BBR and 2 - FLASH under "Devices".

  3. 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:

  • TinyCTS/AL a Cooperative Task Scheduler

  • lwIP a Lightweight TCP/IP stack

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