Loop Filter

The loop filters are provided by TrackingLoopFilters.jl. This includes:

  • first order loop filter FirstOrderLF
  • second order bilinear loop filter SecondOrderBilinearLF
  • second order boxcar loop filter SecondOrderBoxcarLF
  • third order bilinear loop filter ThirdOrderBilinearLF
  • third order boxcar loop filter ThirdOrderBoxcarLF
  • third order assisted bilinear loop filter ThirdOrderAssistedBilinearLF (combines PLL and FLL)

Default Configuration

The default Doppler estimator is ConventionalAssistedPLLAndDLL which uses:

  • ThirdOrderAssistedBilinearLF for the carrier loop (FLL-assisted PLL for improved dynamics)
  • SecondOrderBilinearLF for the code loop

When TrackState builds the default estimator from a signal-tuple declaration, the carrier loop is staged (see CarrierLoopStage): it pulls in as an FLL-assisted PLL at a wide 50 Hz with a 5 Hz FLL-assist bandwidth, drops the FLL once a phase-lock indicator reads lock, and later narrows to a narrow 18 Hz. The code bandwidth is a flat 1 Hz. All are one-sided noise bandwidths BL (Kaplan & Hegarty), for every signal. The stages only advance, and reset_loop_filters! keeps the stage reached; a satellite added anew starts over. Read a satellite's stage with carrier_loop_stage(get_doppler_estimator_state(sat)).

Where the replica wipes every sign modulation off the prompt (a dataless signal synced to its secondary code) the carrier discriminators turn four-quadrant.

The defaults are not scaled with the integration time; each is capped at filter time by its stability product BL · T_int: the PLL by 0.09, the FLL path by 0.02, the code loop by 0.018 (effective_carrier_loop_filter_bandwidth and siblings). The bandwidths that end up in the loop are:

Integration timeWide BLNarrow BLFLL-assist BLCode BL
1 ms (L1 C/A, L5I, …)50 Hz18 Hz5 Hz1 Hz
4 ms (E1B / E1C)22.5 Hz18 Hz5 Hz1 Hz
10 ms (L1C-D/P, B1C)9 Hz9 Hz2 Hz1 Hz
20 ms (L1 C/A or L2 CM after sync)4.5 Hz4.5 Hz1 Hz0.9 Hz
1.5 s (L2 CL)0.06 Hz0.06 Hz0.013 Hz0.012 Hz

The TrackingLoops manual ("Loop-filter bandwidths") gives the rationale for the bandwidths and caps.

Before TrackingLoops 4 the carrier filter was fed radians but its output read as Hz, making the loop about 5.6× wider than configured (≈ 100 Hz for 18 Hz); to get approximately that loop back, set both wide_carrier_loop_filter_bandwidth and narrow_carrier_loop_filter_bandwidth to 85.0Hz. Before TrackingLoops 5 the loop ran FLL-assisted at one carrier bandwidth throughout; pass equal wide and narrow bandwidths for one PLL bandwidth.

Override per signal by defining methods of default_wide_carrier_loop_filter_bandwidth, default_narrow_carrier_loop_filter_bandwidth, default_fll_assist_loop_filter_bandwidth / default_code_loop_filter_bandwidth, or override at construction time by passing your own doppler_estimator = to TrackState.

Doppler Estimators

In the TrackingLoops manual:

Resetting loop filters

After changing a signal's integration length with set_preferred_num_code_blocks_to_integrate!, reset the affected loop filters so the old integration length's filter state does not leak into the new one.

Tracking.reset_loop_filters! — Function
reset_loop_filters!(track_state)

Re-seed the Doppler-estimator state of every satellite (or one addressed satellite) from its current Doppler. For the conventional PLL/DLL this zeroes the loop-filter integrators and keeps carrier_doppler, code_doppler, any per-satellite bandwidth override and the carrier-loop stage. Each signal's last_fully_integrated_filtered_prompt is cleared, so the first FLL update does not measure a rotation spanning the old integration interval.

Use it when a signal's integration length changes mid-track, e.g. GPS L5I from 1 ms to 10 ms via set_preferred_num_code_blocks_to_integrate!: the integrator state does not carry over to the new update interval, and the transient can drag the loop out of lock.

No satellite id resets every satellite; (group, prn) or (single-group) prn resets one. Mutates track_state and returns it. Works for any AbstractDopplerEstimator through its init_estimator_state hook.

set_preferred_num_code_blocks_to_integrate!(track_state, 1, GPSL5I, 10)
reset_loop_filters!(track_state, 1)          # clean handoff for PRN 1
reset_loop_filters!(track_state)             # …or reset every satellite
source

Custom Configuration

You can customize the loop filters and bandwidths when creating the Doppler estimator:

julia> using Tracking, TrackingLoops, GNSSSignals, TrackingLoopFilters

julia> using Tracking: Hz

julia> # Use non-assisted PLL with custom loop filter types
       doppler_estimator = ConventionalPLLAndDLL(
           ThirdOrderBilinearLF,      # carrier loop filter type
           SecondOrderBilinearLF;     # code loop filter type
           wide_carrier_loop_filter_bandwidth = 30.0Hz,
           narrow_carrier_loop_filter_bandwidth = 15.0Hz,
           code_loop_filter_bandwidth = 0.5Hz
       );

julia> track_state = TrackState(; signal = GPSL1CA(), doppler_estimator);

julia> track_state = add_satellite!(track_state; prn = 1, code_phase = 50.0, carrier_doppler = 1000.0Hz);

Custom Loop Filters

You can implement a custom loop filter MyLoopFilter <: AbstractLoopFilter. In this case, a specialized filter_loop function is needed. For more information refer to TrackingLoopFilters.jl.

Custom Doppler Estimator

To replace the loop-filter-based estimator with a different algorithm (Kalman filter, joint-channel estimator, …), see the dedicated guide in custom_doppler_estimator.md.