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^2The 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) )^2which 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
@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- default2.046e6.:doppler_min_hz/:doppler_max_hz/:doppler_step_hz- the Doppler search grid (defaults-2500,2500,500Hz).
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 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 integration loss in decibels, 10*log10(coherent_loss(f, T)).
Returns :neg_infinity at an exact null (loss of zero).
@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- default2.046e6.:doppler_hz- residual carrier Doppler to wipe off (default0.0).:code_phase_chips- replica code phase offset in chips (default0.0).:code_doppler_hz- replica code-rate Doppler (default0.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.
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.
@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 (default2.046e6, 2 samples/chip).:integration_time_s- integration time in seconds (default one code period,1023 / 1_023_000= 1 ms). DeterminesNtogether with the sample rate, unless:num_samplesis given.:num_samples- explicit sample countN(overrides:integration_time_s).:code_phase_chips- initial code phase offset in chips (default0.0).:code_doppler_hz- code-rate Doppler scaling in Hz (default0.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]
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