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 --trim into 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, of every signal, apply_record folds the record into the signal's prompt filter, C/N₀ estimator and bit buffer, and step_loop turns it into the Dopplers the next replica runs with. The estimator state knows its satellite's driver signal, the one whose records close the loops; a record of another signal of the satellite returns the command in force, so the same four calls serve every record and every estimator:

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:
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, output, loop, fs; prn)
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_doppler

Read 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

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
TrackingLoops

The device-independent core of a GNSS tracking loop, shared by the software receiver (Tracking.jl) and the allocation-free loop process of a hardware correlator (HardwareLoopCore.jl):

  • correlator record types, discriminators and the post-correlation filter;
  • the navigation-bit buffer with per-signal bit-edge and secondary-code sync;
  • the C/N₀ estimators and the noise-density window they read;
  • loop-filter bandwidth rules and the Doppler estimators (conventional FLL-assisted PLL/DLL, delay-aware NCO-referenced loop) behind one per-record step_loop;
  • apply_record, the per-record fold of a signal component (prompt filter, C/N₀ estimator, bit buffer);
  • the NCOTimeline: what a hardware NCO ran and will run;
  • vector tracking: VectorPLLAndDLL decodes every satellite's bits (a pilot + data pair's from the data component, ranging on the pilot), solves the PVT and closes all loops at once with a navigation filter.

Nothing here reads a raw sample or knows a device or a segment.

source
TrackingLoops.TrackingLoops — Module
TrackingLoops

The device-independent core of a GNSS tracking loop, shared by the software receiver (Tracking.jl) and the allocation-free loop process of a hardware correlator (HardwareLoopCore.jl):

  • correlator record types, discriminators and the post-correlation filter;
  • the navigation-bit buffer with per-signal bit-edge and secondary-code sync;
  • the C/N₀ estimators and the noise-density window they read;
  • loop-filter bandwidth rules and the Doppler estimators (conventional FLL-assisted PLL/DLL, delay-aware NCO-referenced loop) behind one per-record step_loop;
  • apply_record, the per-record fold of a signal component (prompt filter, C/N₀ estimator, bit buffer);
  • the NCOTimeline: what a hardware NCO ran and will run;
  • vector tracking: VectorPLLAndDLL decodes every satellite's bits (a pilot + data pair's from the data component, ranging on the pilot), solves the PVT and closes all loops at once with a navigation filter.

Nothing here reads a raw sample or knows a device or a segment.

source
TrackingLoops.AbstractDopplerEstimator — Type

Abstract supertype for Doppler estimators. Subtypes carry configuration; the per-satellite state lives with the satellite. Hosts drive every estimator through one interface:

  • init_estimator_state(estimator, driver_signal, carrier_doppler, code_doppler) builds a satellite's state, which records the driver: the signal whose records close the loops;
  • step_loop(estimator, state, record, words, landing_sample) folds one record into it and returns (state, carrier_doppler, code_doppler). The host calls it on every record of every signal of the satellite (each built from that signal's SignalLoopState); the estimator picks out the ones it uses and, for the rest, returns the state unchanged and the command in force;
  • reset_estimator_state(estimator, state, carrier_doppler, code_doppler) re-seeds it from converged Dopplers, keeping what the estimator chooses to keep.

Implemented by ConventionalPLLAndDLL, NCOReferencedPLLAndDLL and VectorPLLAndDLL, whose navigation engine also runs inside step_loop.

source
TrackingLoops.update — Function
update(x, prompt)

Advance a per-record state (C/N₀ estimator, post-correlation filter) by one prompt and return the new state.

source