Orbis.GNSS.Signal.Correlator (Orbis v0.9.0)

Copy Markdown View Source

Baseband simulation, correlation, and acquisition for the GPS L1 C/A signal.

This module builds the "generate a replica, correlate, and acquire a PRN" layer on top of Orbis.GNSS.Signal.CA. It works entirely in complex baseband (the carrier has already been removed down to a residual Doppler), using the standard time-domain signal-processing model described in the GNSS acquisition literature:

  • Kaplan & Hegarty, Understanding GPS/GNSS: Principles and Applications (3rd ed.), Ch. 5-8 (signal acquisition, coherent correlation, the sinc Doppler-mismatch loss, and the C/N0 to post-correlation SNR relation).
  • Misra & Enge, Global Positioning System: Signals, Measurements, and Performance (2nd ed.), Ch. 10-11.
  • Borre, Akos, Bertelsen, Rinder & Jensen, A Software-Defined GPS and Galileo Receiver (the 2D code-phase by Doppler search and the peak-to-noise acquisition metric).

Sampled-code replica (zero-order-hold / nearest-chip)

The 1023-chip C/A code is sampled to a sampling rate fs over an integration time T (so N = round(fs * T) samples). At sample n the code phase, in chips, advances at the code rate:

code_rate = f_chip * (1 + fd_code / f_L1)
sampled[n] = chip(prn, floor((code_phase + n * code_rate / fs) mod 1023))

with f_chip = 1_023_000 cps and f_L1 = 1_575_420_000 Hz. code_phase is the initial offset in chips and fd_code is an optional code-rate Doppler scaling (defaults to 0.0; code Doppler is negligible over one 1 ms period but the parameter is exposed). This is a clean nearest-chip (zero-order-hold) sampler: it picks the chip the sample instant falls within. CA.chip/2 already wraps its index modulo 1023, so the sampler wraps for free.

Coherent correlation

For a complex baseband record x[n] = xI[n] + j*xQ[n] (a real-valued test signal has xQ = 0), correlation against a local carrier wipe-off exp(-j 2*pi*f_d*n/fs) times the real bipolar code c[n] is the coherent sum over the integration window:

S = sum_{n=0}^{N-1} x[n] * c[n] * exp(-j 2*pi*f_d*n/fs)
I = sum ( xI[n]*c[n]*cos(th_n) + xQ[n]*c[n]*sin(th_n) )
Q = sum ( xQ[n]*c[n]*cos(th_n) - xI[n]*c[n]*sin(th_n) ),  th_n = 2*pi*f_d*n/fs
power = I^2 + Q^2

The amplitude is recovered as sqrt(I^2 + Q^2); for a real input the carrier wipe-off still spreads energy across both I and Q so the magnitude is the meaningful quantity.

Acquisition metric

acquire/3 performs a 2D search over code-phase bins (at sample resolution) and Doppler bins, computing the correlator power on each cell. The detection metric is the standard peak-to-noise ratio used in software receivers:

metric = peak_power / mean(off_peak_powers)

where the off-peak set excludes the peak cell and an exclusion zone of one code-phase bin on either side of the peak (at the peak's Doppler bin), so the main correlation lobe is not mistaken for the noise floor. For a clean signal this metric is large (order of the number of samples); for noise or the wrong PRN it is close to one. The alternative peak-to-second-peak ratio is also a standard choice; this module exposes peak-to-mean-off-peak as metric.

Coherent integration loss and post-correlation SNR

A residual frequency error f over a coherent integration time T attenuates the correlation by the sinc-squared Doppler-mismatch loss (Kaplan & Hegarty, the residual-carrier loss whose discrete form |sin(N*pi*df*Ts) / (N*sin(pi*df*Ts))| tends to sinc(pi*f*T) in the continuous limit):

coherent_loss(f, T) = sinc^2(pi*f*T) = ( sin(pi*f*T) / (pi*f*T) )^2

which is 1 (0 dB) at f = 0 and has its first null at f = 1/T. The correlation amplitude scales as |sinc(pi*f*T)| = sqrt(coherent_loss).

For the post-correlation signal-to-noise ratio this module exposes only the relation it can state cleanly from a standard reference: coherent integration over T seconds gives the predetection SNR

snr_post_db(cn0_dbhz, T) = cn0_dbhz + 10*log10(T)

i.e. the carrier-to-noise-density ratio C/N0 (dB-Hz) plus the processing gain 10*log10(T) corresponding to a 1/T effective noise bandwidth (Kaplan & Hegarty; Misra & Enge). The exact noise-bandwidth convention (1/T versus 1/(2T)) differs between texts; this module uses the 1/T form. No second, competing gain formula is exposed.

Summary

Functions

Acquires a PRN by a 2D search over code phase and Doppler.

Coherent integration loss from a residual frequency error.

Coherent integration loss in decibels, 10*log10(coherent_loss(f, T)).

Coherently correlates a complex baseband record against a PRN replica.

Low-level coherent correlation of a baseband record against an explicit sampled ±1 code.

Builds a sampled ±1 C/A code replica.

Post-correlation (predetection) SNR in dB from C/N0 and integration time.

Functions

acquire(samples, prn, opts \\ [])

@spec acquire(list(), integer(), keyword()) ::
  {:ok, map()}
  | {:error, :empty_samples | :too_short | {:unsupported_prn, integer()}}

Acquires a PRN by a 2D search over code phase and Doppler.

samples is the complex baseband record (list of {i, q} tuples or bare reals). Options:

  • :sample_rate_hz - default 2.046e6.
  • :doppler_min_hz / :doppler_max_hz / :doppler_step_hz - the Doppler search grid (defaults -2500, 2500, 500 Hz).

The code-phase axis is searched at sample resolution over one code period. Returns

{:ok, %{
  code_phase_chips: float,
  doppler_hz: float,
  peak_metric: float,
  metric: float,
  peak_power: float,
  grid: %{
    doppler_hz: [float],
    code_phase_bins: integer,
    doppler_step_hz: float,
    samples_per_chip: float
  }
}}

metric (and its alias peak_metric) is the peak-to-mean-off-peak power ratio described in the module docs. Errors:

  • {:error, :empty_samples} for an empty record,
  • {:error, :too_short} if the record is shorter than one code period,
  • {:error, {:unsupported_prn, prn}} propagated from the code generator.

coherent_loss(freq_error_hz, integration_time_s)

@spec coherent_loss(number(), number()) :: float()

Coherent integration loss from a residual frequency error.

Returns the linear loss sinc^2(pi*f*T) = (sin(pi*f*T)/(pi*f*T))^2 in [0, 1]: 1.0 at f = 0, with its first null at f = 1/T. The correlation amplitude scales as sqrt of this value.

Examples

iex> Orbis.GNSS.Signal.Correlator.coherent_loss(0.0, 1.0e-3)
1.0

coherent_loss_db(freq_error_hz, integration_time_s)

@spec coherent_loss_db(number(), number()) :: float() | :neg_infinity

Coherent integration loss in decibels, 10*log10(coherent_loss(f, T)).

Returns :neg_infinity at an exact null (loss of zero).

correlate(iq, prn, opts \\ [])

@spec correlate(list(), integer(), keyword()) ::
  {:ok, %{i: float(), q: float(), power: float()}}
  | {:error, {:unsupported_prn, integer()} | :empty_samples}

Coherently correlates a complex baseband record against a PRN replica.

iq is a list of samples, each either a {i, q} tuple or a bare real number (interpreted as i with q = 0). Options:

  • :sample_rate_hz - default 2.046e6.
  • :doppler_hz - residual carrier Doppler to wipe off (default 0.0).
  • :code_phase_chips - replica code phase offset in chips (default 0.0).
  • :code_doppler_hz - replica code-rate Doppler (default 0.0).

Returns {:ok, %{i: i, q: q, power: i*i + q*q}}, or propagates the PRN error. The replica is generated at length(iq) samples to match the record.

correlate_against(iq, code, fs, doppler_hz)

@spec correlate_against(list(), [integer()], number(), number()) :: {float(), float()}

Low-level coherent correlation of a baseband record against an explicit sampled ±1 code.

iq is a list of {i, q} tuples or bare reals; code is the sampled ±1 vector (same length); fs is the sample rate and doppler_hz the residual carrier to wipe off. Returns {i, q}, the real and imaginary parts of the coherent sum.

replica(prn, opts \\ [])

@spec replica(
  integer(),
  keyword()
) :: {:ok, [integer()]} | {:error, {:unsupported_prn, integer()}}

Builds a sampled ±1 C/A code replica.

Options:

  • :sample_rate_hz - sampling rate in Hz (default 2.046e6, 2 samples/chip).
  • :integration_time_s - integration time in seconds (default one code period, 1023 / 1_023_000 = 1 ms). Determines N together with the sample rate, unless :num_samples is given.
  • :num_samples - explicit sample count N (overrides :integration_time_s).
  • :code_phase_chips - initial code phase offset in chips (default 0.0).
  • :code_doppler_hz - code-rate Doppler scaling in Hz (default 0.0).

Returns {:ok, samples} with samples a list of ±1 integers of length N, or propagates {:error, {:unsupported_prn, prn}} from Orbis.GNSS.Signal.CA.

Examples

iex> {:ok, s} = Orbis.GNSS.Signal.Correlator.replica(1, num_samples: 4, sample_rate_hz: 1.023e6)
iex> s
[-1, -1, 1, 1]

snr_post_db(cn0_dbhz, integration_time_s)

@spec snr_post_db(number(), number()) :: float()

Post-correlation (predetection) SNR in dB from C/N0 and integration time.

Uses the standard relation snr_post_db = cn0_dbhz + 10*log10(T) (Kaplan & Hegarty; Misra & Enge), corresponding to a 1/T effective noise bandwidth.

Examples

iex> Float.round(Orbis.GNSS.Signal.Correlator.snr_post_db(40.0, 1.0e-3), 6)
10.0