Bit and secondary-code synchronisation

Until the navigation-bit boundary (for a data signal) or the secondary-code phase (for a pilot) is known, a signal can only be integrated over one primary code period. The BitBuffer collects the prompts of those short integrations, and a per-signal detector (detect_bit_or_secondary_code_sync) searches them for the boundary. Once has_bit_or_secondary_code_been_found returns true, longer coherent integrations become available (see calc_num_code_blocks_to_integrate) and the buffer accumulates the decoded soft bits, which get_soft_bits returns. For a pilot, the secondary-code sync also gives the prompt's sign, which a record's carrier_phase_polarity hands to the four-quadrant carrier discriminators (see LoopRecord).

Detectors exist for every GPS, Galileo and BeiDou signal that GNSSSignals.jl models. How a signal is synchronised is a method of detect_bit_or_secondary_code_sync and of the small trait functions below (uses_soft_bit_edge_detection, get_bit_edge_or_secondary_code_tolerance, …), so a new signal type can be supported by adding methods for it.

TrackingLoops.PhaseAccumulators — Type

Per-hypothesis bin statistics for the soft CFAR sync detectors, one entry per timing hypothesis: bit-edge phase with one-bit bins (_detect_bit_edge_cfar, _update_phase_accumulators!) or overlay rotation with one overlay-wiped secondary period per bin (_detect_secondary_code_cfar, _update_secondary_accumulators!). O(hypotheses) per block, no growing history. period is blocks_per_bit or the secondary-code length N.

The vectors are mutated in place across BitBuffer rebuilds: an immutable form would be copied (~660 B) or boxed every block, allocating pre-sync.

Fields (all length period once seeded; empty before the first block):

  • open_bin_sum: coherent sum of the open bin (overlay-wiped for secondary).
  • mean_bin_energy / bin_energy_sum_of_squared_deviations: running mean and M₂ = Σ(energyᵢ − mean)² of the completed-bin energies (Welford; M₂ forgets beyond BIN_ENERGY_VARIANCE_MEMORY bins). The bin count is div(num_blocks - hypothesis, period).
  • last_bin_polarity: sign of the last completed bin's real part (0 before).
source
TrackingLoops.SyncResult — Type
SyncResult

Outcome of a per-signal bit-sync / secondary-code-sync detector call.

Fields:

  • found::Bool: whether the detector locked on this update.
  • phase::Int: the secondary-code chip the upcoming integration aligns to (0:N-1, from the hard _secondary_code_search); zero for the soft CFAR detectors and signals without a secondary code, which fire on a boundary.
  • polarity::Int8: ±1, the locked match orientation, applied to the decoded bits so a negative-polarity lock does not invert them.
source
TrackingLoops.get_bit_edge_detection_confidence — Method
get_bit_edge_detection_confidence(_)

Target confidence (1 − P(false lock)) of the soft CFAR sync detectors, default 0.999: they integrate until the best hypothesis beats its competitor with this confidence (two bins for a clean signal, longer in noise). Lower it to lock faster at the cost of more false locks.

Overriding

TrackingLoops.get_bit_edge_detection_confidence(::GPSL1CA) = 0.9999

Takes effect at the next detector call.

source
TrackingLoops.get_bit_edge_or_secondary_code_tolerance — Method
get_bit_edge_or_secondary_code_tolerance(_)

Hamming tolerance of the hard-decision _secondary_code_search: the largest fraction of chip errors accepted, max_errors = floor(Int, tolerance × N). Default 0.025 (45 of 1800 chips). Only hard-path signals (GPS L1C-P, BeiDou B1C-P) read it; the soft detectors use get_bit_edge_detection_confidence.

Overriding

E.g. to loosen it for low-C/N₀ work:

TrackingLoops.get_bit_edge_or_secondary_code_tolerance(::GPSL1C_P) = 0.05

Takes effect at the next detector call.

source
TrackingLoops.get_code_block_buffer_type — Method
get_code_block_buffer_type(_)

Concrete Unsigned width B of BitBuffer.code_block_buffer for signal (default UInt64), a type parameter so construction is type-stable.

Hard-path signals need at least N bits (the 1800-chip overlay pilots use UInt1800); soft secondary-code signals keep one period so their hard path stays available; GPS L1 C/A's holds the pre-sync bits recovered at sync; signals with nothing to search use UInt8. Per-signal values: Code-block buffer widths.

source
TrackingLoops.get_soft_bits — Method
get_soft_bits(bit_buffer::BitBuffer)
get_soft_bits(state::SignalLoopState)

The soft bits decoded so far: per navigation bit, the polarity-corrected real part of the summed de-rotated prompts. The sign is the hard decision, the magnitude the reliability, ready for soft-decision decoding (e.g. Viterbi for Galileo E1B, LDPC for GPS L1C-D). Bits recovered from the pre-sync window are sign votes scaled by the sync-time prompt magnitude, to stay comparable.

The buffer's own vector, not a copy: drain it (empty!) after reading. A SignalLoopState's holds 64 bits before it allocates.

source
TrackingLoops.has_bit_or_secondary_code_been_found — Method
has_bit_or_secondary_code_been_found(bit_buffer::BitBuffer)
has_bit_or_secondary_code_been_found(state::SignalLoopState)

Whether the navigation-bit boundary or secondary-code phase has been found; from then on the signal may integrate over several code periods and bits are collected for get_soft_bits.

source
TrackingLoops.uses_soft_bit_edge_detection — Method
uses_soft_bit_edge_detection(signal)

Whether signal's bit edge is found by the soft CFAR detector _detect_bit_edge_cfar instead of the hard detect_bit_or_secondary_code_sync.

By default true when a bit spans more than one code period and there is no secondary code (currently only GPS L1 C/A). BeiDou B1I/B3I do not qualify even on GEO PRNs, since their signal type reports a 20-chip secondary code (see BeiDou GEO satellites). Override per signal type, e.g.:

TrackingLoops.uses_soft_bit_edge_detection(::SomeSignal) = false

Constant-folded per signal type.

source
TrackingLoops.uses_soft_secondary_code_detection — Method
uses_soft_secondary_code_detection(signal)

Whether signal's secondary code is found by the soft CFAR detector _detect_secondary_code_cfar instead of the hard _secondary_code_search.

Each bin integrates one secondary period coherently, so the default enables it only for short codes, 1 < N ≤ 100; the 1800-chip overlays (18 s) are too long to integrate and too long to false-lock, and keep the hard sweep. Mutually exclusive with uses_soft_bit_edge_detection. Override per signal type, e.g.:

TrackingLoops.uses_soft_secondary_code_detection(::GPSL5I) = false

Constant-folded per signal type.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Symbol-sync detector for GPS L1C-D: one CNAV-2 symbol per 10 ms primary code period (100 sps; IS-GPS-800G §3.2.3), so it locks immediately — see _detect_symbol_is_code_block_sync.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Secondary-code sync detector for GPS L1C-P: the hard _detect_secondary_code_sync over the per-PRN 1800-chip overlay (IS-GPS-800G §3.2.2.1.2) on the 10 ms primary code, an 18 s cycle. Needs 1800 buffered blocks; accepts up to 45 errors (2.5 %).

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(_, _, _, _)

"Sync" detector for GPS L2CL — a no-op that never reports found. L2CL is a dataless pilot without overlay whose 767250-chip (1.5 s) code period already is the coherent integration, so there is nothing to lock.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Symbol-sync detector for GPS L2CM: one CNAV symbol per 20 ms code period (50 sps; IS-GPS-200N §3.2.2), so it locks immediately — see _detect_symbol_is_code_block_sync.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Symbol-sync detector for Galileo E1B (both GalileoE1B and the BOC(1,1) approximation GalileoE1B_BOC11): one I/NAV symbol per 4 ms primary code period (250 sym/s; Galileo OS SIS ICD Tables 11, 15), so it locks immediately — see _detect_symbol_is_code_block_sync.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Hard-path secondary-code sync detector for the Galileo E1C pilot (both GalileoE1C and the BOC(1,1) approximation GalileoE1C_BOC11) over the shared CS25 code (Galileo OS SIS ICD Table 4; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

"Sync" detector for Galileo E5a-QP — reports found = true from the very first integration via _detect_symbol_is_code_block_sync.

With no data and no overlay every block boundary is equivalent; the lock only gates the switch from single-block pre-sync integration to the whole-cycle one (max_num_code_blocks_to_integrate).

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Symbol-sync detector for Galileo E6-B: one C/NAV symbol per 1 ms primary code period (1000 sym/s; OS SIS ICD v2.2 Table 5), so it locks immediately — see _detect_symbol_is_code_block_sync.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Hard-path secondary-code sync detector for the Galileo E6-C pilot over the per-SVID CS100 code (E6-B/C Codes Technical Note §2.4: CS100ₙ for SVID n, the same half of the table as GalileoE5aQ; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Hard-path secondary-code sync detector for the BeiDou B2a data component over the shared 5-chip code (00010; BDS-SIS-ICD-B2a-1.0 §5.2.1) — see _detect_secondary_code_sync. One period is exactly one 200 sym/s B-CNAV2 symbol. The default detector is the soft one (uses_soft_secondary_code_detection).

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Symbol-sync detector for BeiDou B2bI: one B-CNAV3 symbol per 1 ms primary code period (1000 sym/s; BDS-SIS-ICD-B2b-1.0 §5), so it locks immediately — see `detectsymboliscodeblock_sync`.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Symbol-sync detector for the BeiDou B1C data component: one B-CNAV1 symbol per 10 ms primary code period (100 sym/s; BDS-SIS-ICD-B1C-1.0 Table 5-1), so it locks immediately — see _detect_symbol_is_code_block_sync.

source
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
    signal,
    prn,
    code_block_bits,
    num_code_blocks
)

Secondary-code sync detector for the BeiDou B1C pilot: the hard _detect_secondary_code_sync over the per-PRN 1800-chip overlay (BDS-SIS-ICD-B1C-1.0 §5.2.2) on the 10 ms primary code, an 18 s cycle — the same shape as GPS L1C-P. Needs 1800 buffered blocks; accepts up to 45 errors (2.5 %).

source

Code-block buffer widths

get_code_block_buffer_type per signal, and what the packed buffer of that width is for:

SignalReturnsDetector / buffer role
GPS L1 C/AUInt64soft bit-edge CFAR — holds up to 3 pre-sync bits
Galileo E1BUInt8symbol = primary period, buffer unused
GPS L5IUInt32soft secondary CFAR — packed buffer vestigial
GPS L5QUInt32soft secondary CFAR — packed buffer vestigial
GPS L1C-DUInt8symbol = primary period, buffer unused
GPS L1C-PUInt1800hard rotation sweep — 1800-chip overlay horizon
GPS L2CMUInt8symbol = primary period, buffer unused
GPS L2CLUInt8dataless pilot, no sync, buffer unused
Galileo E1CUInt32soft secondary CFAR — packed buffer vestigial
Galileo E5a-IUInt32soft secondary CFAR — packed buffer vestigial
Galileo E5a-QUInt128soft secondary CFAR — packed buffer vestigial
Galileo E5a-QPUInt8no data, no overlay, buffer unused
Galileo E5b-IUInt32soft secondary CFAR — packed buffer vestigial
Galileo E5b-QUInt128soft secondary CFAR — packed buffer vestigial
Galileo E6-BUInt8symbol = primary period, buffer unused
Galileo E6-CUInt128soft secondary CFAR — packed buffer vestigial
BeiDou B1IUInt32soft secondary CFAR — packed buffer vestigial
BeiDou B3IUInt32soft secondary CFAR — packed buffer vestigial
BeiDou B2a-IUInt32soft secondary CFAR — packed buffer vestigial
BeiDou B2a-QUInt128soft secondary CFAR — packed buffer vestigial
BeiDou B2b-IUInt8symbol = primary period, buffer unused
BeiDou B1C-DUInt8symbol = primary period, buffer unused
BeiDou B1C-PUInt1800hard rotation sweep — 1800-chip overlay horizon

The soft detectors read the incremental PhaseAccumulators instead of the packed buffer. GPS L1 C/A still recovers the pre-sync bits it holds once sync is found; the secondary-coded signals do not, as their pre-sync signs carry the overlay, not data.

BeiDou GEO satellites

BDS-SIS-ICD-B1I-3.0 §5.2.1 applies the 20-bit Neuman-Hoffman overlay only on the MEO/IGSO satellites (PRN 6-58), which broadcast the D1 message at 50 sym/s: one NH20 period is one data symbol. The GEO satellites (PRN 1-5 and 59-63) broadcast D2 at 500 sym/s and carry no overlay; GNSSSignals models that with all-ones columns of a per-PRN secondary code, so B1I and B3I report a 20-chip secondary code for every PRN and use the secondary-code detector, not the bit-edge one.

On a GEO PRN that search never completes: an all-ones reference is rotation-invariant, so the 20 rotation hypotheses differ only in how their 20-block bins straddle the data, and with 2-block D2 symbols every bin averages about ten random symbols and none stands out. The soft CFAR detector needs a peak that beats its runner-up, so it declines to lock. A GEO satellite therefore tracks and can be ranged on, but stays in the one-block pre-sync integration and decodes no bits. It takes both the missing overlay and the D2 rate: without an overlay at the D1 rate the bins would reduce to GPS L1 C/A's bit-edge search and lock on the symbol boundary (test/signals.jl tests both cases). Decoding a GEO satellite needs a per-PRN data rate upstream: get_data_frequency is per signal type and reports the D1 50 Hz for every PRN.