Fix local carrier generation in PCPS acquisition

The Doppler wipeoff replicas were generated with
volk_gnsssdr_s32f_sincos_32fc, which accumulates the phase in single
precision over the whole vector without wrapping it. With a large
frequency offset, such as the GLONASS FDMA channel bias (several rad per
sample), the accumulated phase reaches 1e5 rad over a coherent block and
its float resolution drops to 0.01-0.06 rad, so the effective frequency
of the local carrier was off by hundreds of Hz to kHz, changing at every
power-of-two boundary of the accumulator.

In GLONASS L1/L2 C/A receivers this corrupted the Doppler grid: the
noise estimate taken from the opposite Doppler row no longer represented
the grid, satellites absent from the signal passed the pfa threshold on
every dwell (tracked for ~11 s at ~30 dB-Hz with no carrier lock, then
dropped and re-acquired, periodically), and the acquisition Doppler of
the satellites actually present was biased by up to one bin, which could
push the tracking loop into a false lock.

Generate the carrier with a complex rotator instead, renormalized every
512 samples. The residual frequency error is below 0.1 Hz.
This commit is contained in:
Carles Fernandez
2026-09-13 13:21:29 +02:00
parent c943be70f5
commit 10bb8e31a6
@@ -308,9 +308,21 @@ bool pcps_acquisition::is_fdma()
void pcps_acquisition::update_local_carrier(own::span<gr_complex> carrier_vector, float freq) const
{
const auto fs_in = d_acq_parameters.use_automatic_resampler ? d_acq_parameters.resampled_fs : d_acq_parameters.fs_in;
const auto phase_step_rad = static_cast<float>(TWO_PI) * freq / static_cast<float>(fs_in);
std::array<float, 1> _phase{};
volk_gnsssdr_s32f_sincos_32fc(carrier_vector.data(), -phase_step_rad, _phase.data(), carrier_vector.size());
const double phase_step_rad = TWO_PI * static_cast<double>(freq) / static_cast<double>(fs_in);
const gr_complex phase_inc(static_cast<float>(std::cos(phase_step_rad)), static_cast<float>(-std::sin(phase_step_rad)));
gr_complex phase(1.0F, 0.0F);
auto* out = carrier_vector.data();
const auto num_samples = carrier_vector.size();
for (std::size_t i = 0; i < num_samples; i++)
{
out[i] = phase;
phase *= phase_inc;
if ((i & 511U) == 511U)
{
// keep the rotator on the unit circle
phase /= std::abs(phase);
}
}
}