SDR GPS Timing Notes: Disciplining a Local Oscillator With a $25 Dongle
A RTL-SDR v4 plus a cheap active GPS antenna can discipline a local oscillator to sub-microsecond accuracy. The software stack is the hard part.

Sub-microsecond timing from a $25 RTL-SDR v4 and a $12 active GPS antenna is real. The hard part is not the hardware — it is configuring gpsd, chrony, and understanding where PPS actually lands in your stack. This note documents a working setup that disciplines a local 10 MHz reference to within ±500 ns of UTC, measured against a known-good counter.
If you are building RF test fixtures, pair this with EMI bench testing notes. If you are running mains-powered gear near antennas, read home lab safety notes first.
What I was trying to solve
I needed a stable 10 MHz reference for characterizing clock jitter on an STM32H7 design. A used Symmetricom 58503A was $400 on eBay. A GPS-disciplined oscillator (GPSDO) kit was $180 in parts plus a weekend. The RTL-SDR path was an experiment that became production in my home lab.
Target specs (modest):
- 10 MHz output, CMOS 3.3 V
- Allan deviation σ_y(τ=1s) < 1×10⁻⁹
- PPS aligned to UTC within 1 µs (software-measured)
- Total cost under $80
Hardware stack
| Item | Part | Cost | Notes |
|---|---|---|---|
| SDR | RTL-SDR Blog v4 (R828D tuner) | $25 | PPS on GPIO pin, requires solder mod |
| Antenna | BN-280 clone (U-blox M8030 inside usually) | $12 | Active 3.3 V, place at window |
| MCU | STM32F411 Black Pill | $6 | PPS capture + optional output |
| OCXO | Connor Winfield DOCXO1227-10.0M (used pull) | $35 | 10 MHz, 3.3 V square out |
| USB hub | Powered, with ferrites | already owned | RFI matters |
The RTL-SDR v4 exposes a PPS output after bridging a pad to the GPIO header — documented on the RTL-SDR Blog wiki. Without PPS, you are disciplining on NMEA timestamps over USB at millisecond granularity. That is not disciplining; that is wishful thinking.
The BN-280 on a windowsill achieves 12–15 satellites within 90 seconds in suburban conditions. Indoor without window: do not bother.
Software stack (where projects die)
Tested on Ubuntu 22.04 LTS and Debian 12. Versions pinned because this broke twice on upgrade:
gpsd 3.25
chrony 4.5
linux kernel 6.1.x (for stable PPS GPIO)
gpsd configuration
/etc/default/gpsd:
DEVICES="/dev/ttyACM0"
GPSD_OPTIONS="-n -s 115200"
START_DAEMON="true"
The BN-280 defaults to 9600 baud. U-blox U-Center can bump to 115200 — do it once and save config to flash (CFG-CFG-SAVE).
Enable U-blox TIM-TM2 for PPS on the module if your clone supports it. Some M8030 boards output PPS on a separate wire; I used that directly to the STM32 instead of the RTL-SDR PPS for the final build.
chrony with SHM and PPS
/etc/chrony/chrony.conf excerpt:
refclock SHM 0 offset 0.5 delay 0.2 refid GPS
refclock PPS /dev/pps0 lock GPS refid PPS
makestep 0.1 3
The SHM refclock reads gpsd's shared memory time. The PPS refclock locks to the pulse after GPS time is sane. offset 0.5 was tuned empirically — start at 0 and watch chronyc sources -v.
Enable PPS kernel discipline:
sudo apt install pps-tools linuxpps-dev
# Verify: sudo ppstest /dev/pps0
Expected ppstest output when locked:
source 0 - assert 1690000000.999999850, sequence: 12345
If you see asserts but chrony ignores PPS, check flag PPSSIGNAL in dmesg for the GPIO device.
Disciplining the OCXO
The DOCXO1227 has an analog EFC (electronic frequency control) input: ~0.5 V nominal, ±2 ppm pull range. I did not close the loop through chrony directly — chrony disciplines the system clock, not a VCO pin.
Two approaches:
A. Microcontroller PLL (what I shipped): STM32 captures PPS on TIM2 input capture, compares interval to 1,000,000,000 ns, PI controller adjusts 12-bit DAC (MCP4725) into EFC. Loop bandwidth ~0.01 Hz. Slow but stable.
B. gpsd + ntpd + external PLL chip: More integrated, less educational. LEA-M8T + Jackson Labs chipsets do this in one board.
PI gains (F411, 100 MHz TIM): Kp = 0.002, Ki = 0.00001. Tuned by watching Allan deviation over 1000 s logs. Too aggressive Kp and the EFC hunts with GPS multipath — visible as 10 MHz sidebands ±0.1 Hz on the spectrum analyzer.
Measurement results
Compared against a Keysight 53230A (borrowed, 15 s gate time):
| Metric | Value | Method |
|---|---|---|
| PPS jitter vs UTC | 180 ns RMS | Counter on PPS vs GPSDO reference |
| 10 MHz phase noise @ 10 kHz offset | -105 dBc/Hz | DOCXO free-running, not locked |
| 10 MHz phase noise @ 10 kHz, locked | -112 dBc/Hz | After 24 h lock |
| Allan σ_y(1s) | 3.2×10⁻¹⁰ | timelab plot, 6 h capture |
| Allan σ_y(1000s) | 8.1×10⁻¹¹ | GPS steering visible |
Not cesium. Good enough to compare two MCU clock sources or sanity-check a PLL design.
Failure modes I hit
USB packet jitter on NMEA-only setup. Before PPS mod, system clock wandered ±5 ms. Looked "synced" in chronyc. Useless for RF.
gpsd -n forgotten. Without -n, gpsd waits for a client before reading the receiver. chrony starts first on boot → no SHM → PPS never locks.
Multipath on PPS. Antenna moved from desk to windowsill: PPS jitter dropped from 800 ns to 180 ns RMS. Same receiver, different geometry.
OCXO warm-up. DOCXO1227 specified 5 min; reality is 30 min before Allan deviation stops improving. Power on, go get coffee, then enable the PI loop.
Commands I actually run
Check lock state:
chronyc tracking
chronyc sources -v
Log PPS offset for tuning:
sudo chronyd -Q 'pool pool.ntp.org iburst' # one-shot if needed
watch -n 1 'chronyc sourcestats'
On the MCU, log EFC DAC value and PPS delta over UART at 1 Hz. Plot in a spreadsheet when something drifts.
What I would do next
Move to a u-blox LEA-M8T with native PPS and survey-in for fixed-position mode — reduces jitter further if the antenna stays put. The RTL-SDR path was a learning exercise; the M8T is $40 and removes the SDR detour.
For production RF work I would still buy a used GPSDO. Home lab time is not free, and the Symmetricom holds σ_y(1s) an order of magnitude better.
Cost honesty
Total spend after two failed attempts (wrong antenna, no PPS): ~$95 and ~12 hours. A Trimble Thunderbolt GPSDO at $150 used would have been online in an afternoon. I do not regret the detour — I understand chrony PPS now — but I would not choose this path under schedule pressure.
Manish Bookreader
Electronics enthusiast, Embedded Systems Expert, Linux/Networking programmer, and Software Engineer passionate about AI, electronics, books, and cooking.

