TrackingLoops.jl
The arithmetic of a GNSS tracking loop, one correlator record at a time.
A tracking loop takes the correlator outputs of one integration — early, prompt and late accumulators for one satellite — and turns them into what the next integration should run with: a carrier Doppler, a code Doppler, a navigation bit when one has completed, and a C/N₀ estimate. This package is that step, and nothing around it. It does not correlate, it does not read samples, and it does not know where its records come from or where its Doppler commands go.
That makes it usable from anywhere a record shows up:
- in a software receiver, where the same process correlates and closes the loop (Tracking.jl is built on it);
- in a dedicated loop process next to an FPGA correlator, where records arrive over DMA and the corrections are written to NCO registers (HardwareLoopCore.jl is built on it, and compiles it with
juliac --triminto a small, allocation-free executable); - in an analysis script that replays logged records.
Because all of these run the same code, a loop tuned or debugged on one of them behaves identically on the others.
Installation
using Pkg
Pkg.add("TrackingLoops")TrackingLoops runs on Linux, macOS and FreeBSD. It does not install on Windows because the navigation-message decoder it depends on (GNSSDecoder.jl) needs Aff3ct, which has no Windows build.
One loop iteration
Per satellite you hold a Doppler-estimator state and, per tracked signal of that satellite, a SignalLoopState. For every correlator record (CorrelatorOutput) the correlator produced, apply_record folds the record into the signal's prompt filter, C/N₀ estimator and bit buffer, and step_loop turns the filtered correlator into the Dopplers the next replica runs with:
using TrackingLoops, GNSSSignals, Unitful
signal = GPSL1CA()
fs = 4e6u"Hz"
estimator = ConventionalAssistedPLLAndDLL()
state = init_estimator_state(estimator, signal, carrier_doppler, code_doppler) # one per satellite
loop = SignalLoopState(signal) # bit buffer, C/N₀ estimator, prompt filter
# For every correlator record `output::CorrelatorOutput` the correlator produced:
previous_prompt = loop.last_filtered_prompt # read before `apply_record` replaces it
loop, prompt, filtered, blocks, overshoot =
apply_record(loop, signal, prn, output, fs, noise_density, noise_density_ready)
overshoot && @warn "record crossed a navigation-bit boundary; bit sync restarted"
record = LoopRecord(signal, filtered, previous_prompt, output, blocks, fs)
state, carrier_doppler, code_doppler =
step_loop(estimator, state, record, FixedNCOWord(carrier_hz, code_hz), NO_LANDING_SAMPLE)
# program the next replica with carrier_doppler and code_dopplerRead the results off the state as they become available: estimate_cn0 on loop.cn0_estimator, has_bit_or_secondary_code_been_found and get_soft_bits on loop.bit_buffer.
Everything is a plain value or a small mutable state that is preallocated once, so a loop can be stepped for hours without allocating — provided the consumer drains the decoded soft bits (get_soft_bits) as they arrive; the bit buffer has room for 64 of them before its vector grows.
Contents of this manual
- Correlators and discriminators — the record types, their accessors, the discriminators and the post-correlation filter.
- Doppler estimators and loop filters — the estimators behind
step_loop, their bandwidth rules, and the per-record foldapply_record. - NCO timeline — what a hardware NCO ran and will run.
- Bit and secondary-code synchronisation — the bit buffer and the per-signal sync detectors.
- C/N₀ estimation — the moments, NWPR and noise-referenced estimators.
- Noise estimation — the noise-density window a noise-referenced estimator reads.
- Vector tracking — the estimator a navigation filter takes over, and the filter that closes every satellite's loops at once.
- Internals — the unexported functions and types, for those extending the package.
Every exported name is documented in one of these pages. Only the exported names are part of the public API that the version number makes promises about.
Package-wide definitions
TrackingLoops — Module
TrackingLoopsThe device-independent core of a GNSS tracking loop, extracted from Tracking.jl so that the same code closes the loops in the software receiver and in the allocation-free loop process of a hardware correlator:
- the correlator record types and their accessors, the discriminators and the post-correlation filter;
- the navigation-bit buffer with its bit-edge and secondary-code sync detectors, per signal;
- the C/N₀ estimators and the noise-density window they read;
- the loop filters' bandwidth rules and the Doppler estimators — the conventional FLL-assisted PLL/DLL and the delay-aware NCO-referenced loop — behind one per-record
step_loopon a plain per-satellite state, and theVectorPLLAndDLLa vector-tracking filter takes over; - the per-record fold
apply_recordthat advances a signal component's prompt filter, C/N₀ estimator and bit buffer identically on both paths; - the
NCOTimeline: what a hardware NCO ran and will run; - vector tracking:
VectorPLLAndDLL, whose navigation engine decodes every satellite's bits, solves the PVT and then closes every satellite's loops at once with a navigation filter, all from the records it is stepped with.
Tracking.jl depends on this package for its software correlator; the loop process's engine (HardwareLoopCore.jl) depends on it without Tracking. Nothing here reads a raw sample, and nothing here knows a device or a segment.
TrackingLoops.TrackingLoops — Module
TrackingLoopsThe device-independent core of a GNSS tracking loop, extracted from Tracking.jl so that the same code closes the loops in the software receiver and in the allocation-free loop process of a hardware correlator:
- the correlator record types and their accessors, the discriminators and the post-correlation filter;
- the navigation-bit buffer with its bit-edge and secondary-code sync detectors, per signal;
- the C/N₀ estimators and the noise-density window they read;
- the loop filters' bandwidth rules and the Doppler estimators — the conventional FLL-assisted PLL/DLL and the delay-aware NCO-referenced loop — behind one per-record
step_loopon a plain per-satellite state, and theVectorPLLAndDLLa vector-tracking filter takes over; - the per-record fold
apply_recordthat advances a signal component's prompt filter, C/N₀ estimator and bit buffer identically on both paths; - the
NCOTimeline: what a hardware NCO ran and will run; - vector tracking:
VectorPLLAndDLL, whose navigation engine decodes every satellite's bits, solves the PVT and then closes every satellite's loops at once with a navigation filter, all from the records it is stepped with.
Tracking.jl depends on this package for its software correlator; the loop process's engine (HardwareLoopCore.jl) depends on it without Tracking. Nothing here reads a raw sample, and nothing here knows a device or a segment.
TrackingLoops.AbstractDopplerEstimator — Type
Abstract supertype for Doppler estimators. Concrete subtypes carry estimator configuration; the per-satellite state lives with the satellite.
Every estimator implements one interface, and a host (Tracking.jl's track!, a hardware correlator's loop process) drives it through that interface alone, so the estimators are interchangeable:
init_estimator_state(estimator, driver_signal, carrier_doppler, code_doppler)builds a satellite's state;step_loop(estimator, state, record, words, landing_sample)folds one record into it and returns(state, carrier_doppler, code_doppler);reset_estimator_state(estimator, state, carrier_doppler, code_doppler)re-seeds it from converged Dopplers, keeping what the estimator chooses to keep.
ConventionalPLLAndDLL, NCOReferencedPLLAndDLL and the vector loop VectorPLLAndDLL all implement it. The vector loop's navigation engine runs inside its step_loop too, from what the records carry.
TrackingLoops.NumAccumulators — Type
Type parameter wrapper for specifying the number of correlator accumulators. Use NumAccumulators(n) to create an instance.
TrackingLoops.NumAnts — Type
Type parameter wrapper for specifying the number of antennas in the system. Use NumAnts(n) to create an instance.
TrackingLoops.update — Function
update(x, prompt)Advance a per-record state — a C/N₀ estimator or a post-correlation filter — with one prompt and return the new state (immutable update).