From bare crystal to pseudo-TCXO#
Why#
The Raspberry Pi 5 carries no metrology-grade oscillator: a bare quartz crystal (XO) clocks the SoC, with no temperature-compensation circuit whatsoever. This server’s own measurements confirm it: about 3.8 ppm of frequency swing between 38.5 and 57.5 °C, with local slopes of 0.06 to 0.2 ppm/°C depending on the zone. That is an order of magnitude beyond what a TCXO specification would tolerate, and consistent with the characteristic S-shaped curve of an AT-cut crystal (an inflection was even observed around 38.5-39 °C).
This chapter recaps how the steps of this guide, taken together, rebuild in software what the “TC” of a TCXO does in hardware, then places the result in the oscillator hierarchy, cost included.
How: the five steps of the pseudo-TCXO#
Nothing here is new: each step points to a chapter already applied. It is their combination that constitutes the pseudo-TCXO.
- Measure before acting. The
log tracking measurements tempcomplogging (Chrony chapter) provides timestamped temperature/frequency pairs: the raw material for everything else. Without several days of data, none of the following steps can be calibrated honestly. - Eliminate fan cycling. The 4-tier PWM profile with wide hysteresis, continuously active from 40 °C (Firmware chapter), pins the thermal operating point instead of letting temperature oscillate at the whim of an on/off thermostat.
- Insulate the crystal. The polyurethane foam between the crystal and the enclosure smooths fast transients. Measured counterpart: it also delays the crystal’s response relative to the SoC probe, which makes measurements taken during a thermal shock misleading (Chrony chapter, tempcomp section).
- Hold a setpoint (optional). The controlled CPU load
ntpheat(Thermal stabilization chapter) targets a fixed temperature at the cost of about 1 W permanently; this guide leaves it disabled by default. - Compensate the residual. The
tempcompdirective and its point table, rebuilt daily bytempcomp-refit(Chrony chapter), subtract the predictable part of the drift before the discipline loop even has to correct it.
Measured result in production: Residual freq on the order of 0.01 to
0.02 ppm (Chrony verification chapter) and RMS offset down from ~1.7 µs
to ~0.2-0.4 µs on the first readings (snapshots, to be confirmed over
time).
The oscillator hierarchy#
| Family | Principle | Temperature stability (order of magnitude) | Indicative cost | Without hardware modification? |
|---|---|---|---|---|
| XO (the Pi 5’s) | bare crystal | a few ppm to a few tens of ppm over 0-70 °C (measured here: ~3.8 ppm over 19 °C) | < 1 € | already in place |
| TCXO | crystal + built-in compensation circuit | ±0.1 to ±2.5 ppm over -30/+85 °C | 2 to 20 € | no |
| OCXO | crystal held in a thermostated oven | ±0.001 to ±0.01 ppm | 50 to 300 €, plus 1 to 3 W of permanent heating | no |
| CSAC | miniaturized atomic clock (cesium) | ~±0.0005 ppm | 4,000 to 6,000 €, ~120 mW | no |
| Rubidium (RbXO) | Rb atomic reference disciplining a crystal | ~±0.0001 ppm | 500 to 2,000 €, ~10 W | no |
| Pseudo-TCXO | XO + tiered fan + insulation + tempcomp | ~0.01 to 0.02 ppm residual after compensation (Residual freq, measured at +0.016 ppm in production) | ~0 € of extra hardware (the fan is needed anyway) | yes |
Two ways to read this table:
- In raw stability, the pseudo-TCXO does not play in the league of OCXOs and atomic references, and does not claim to. Its real bound lies elsewhere: the GNSS PPS pulls the clock back onto true time permanently, so the local oscillator only needs to be good between two corrections, a matter of seconds.
- In marginal cost, it is unbeatable: every component was already there (the fan is needed anyway, the SoC thermal probe is standard), all that is left to invest is setup time, reusable as-is on any other Pi.
A better oscillator would not replace GNSS: it does not know the time, only the cadence. Its real contribution would be holdover, the ability to stay right through a prolonged GNSS outage. With the pseudo-TCXO, orphan mode (Chrony chapter) honestly degrades the stratum; with a rubidium, the server would hold for days while drifting by a few microseconds. That criterion, and it alone on this project, is what would justify moving up the range.
Pitfalls to avoid#
- Comparing datasheet figures to field measurements. The table mixes by necessity manufacturer specifications (TCXO, OCXO, CSAC) and real measurements (the Pi’s XO, the pseudo-TCXO). A specification is a worst-case guaranteed envelope, a measurement is one point under given conditions: the orders of magnitude are valid, fine-grained comparisons would be meaningless.
- Forgetting the probe measures the SoC, not the crystal. The whole
compensation rests on
/sys/class/thermal/thermal_zone0, away from the crystal and behind the foam. In steady state, the difference is a simple offset the table absorbs; during a fast transient, the two temperatures diverge and measurements become misleading. - Believing the work is done. A crystal ages (slow drift, on the
order of a ppm over the first year, then decreasing): that is
precisely why the
tempcomptable keeps being recomputed on an ongoing basis, and reread at each restart, rather than frozen at first calibration.
Verify#
# The residual after compensation: Residual freq should stay close to 0
chronyc -h /run/chrony/chronyd.sock tracking | grep -E "Residual|Skew|RMS"
# The compensation applied continuously (Comp. column, non-zero)
tail -5 /var/log/chrony/tempcomp.log📚 Going further
- John R. Vig, Quartz Crystal Resonators and Oscillators, A Tutorial: the free, encyclopedic reference on the frequency/temperature curves of quartz cuts (including the AT cut’s cubic), aging, and oscillator families.
- Related chapters: Firmware (fan profile), Thermal stabilization (
ntpheat), Chrony (tempcompand its dynamic table).