Preserve the candidate anchor until its expected confirmation point.
Replace it only after confirmation is missed, preventing payload
correlations from repeatedly displacing a valid preamble.
test_main.cc #included every unit test into a single translation unit, which made GCC exceed its variable tracking limit and required several GB of RAM to compile on small machines.
Use radian angles and the correct GEO inclination for BeiDou almanacs,
and convert GPST to BDT before propagating BeiDou navigation data.
Require a sample-clock-anchored PVT fix no older than one second for
Doppler prediction. Clear previous assistance when retrying acquisition
after tracking loss.
With set_doppler_num_bins(), acq_grid holds the Doppler candidate columns
followed by 0, 1 or 2 noise-reference columns placed at doppler_center
+/- the configured doppler_max, outside the linear Doppler axis. Nothing
in the dump said how many columns were candidates, so the readers either
rejected the file (Acquisition_Dump_Reader) or plotted the reference
columns on a wrong Doppler axis (plot_acq_grid.py, plot_acq_grid.m).
Write a new doppler_num_candidates variable to the dump and use it in
the readers to keep only the candidate columns. Dumps without it are
read as before, except that the reference column of older narrowed
dumps is also skipped. The Python and MATLAB scripts plot a
single-candidate (narrowed) grid as a code-delay line instead of an
empty surface.
Add a regression test that reads back a full-grid dump with two
reference columns and a narrowed N = 5 dump.
A narrowed search of N < d_num_doppler_bins candidates adds up to two
noise-reference rows, so N = d_num_doppler_bins - 1 needs
d_num_doppler_bins + 1 rows. The step-1 grid buffers were sized to the
full grid's own row count, which has no reference rows when the
peak-ratio statistic is used or the CFAR wraparound distance suffices,
so that request wrote one row past the end of d_grid_doppler_wipeoffs
and d_magnitude_grid.
Size the step-1 buffers (and the CUDA engine) for d_num_doppler_bins + 2
rows, which covers every row count set_doppler_num_bins() can produce,
and add a regression test.
Conflict resolution: keep next's CUDA engine hooks, sample-buffer renames
(d_buffer_sample_count) and full_grid_search option; adopt the PR's
set_doppler_num_bins() members. CUDA capacity now uses
d_num_doppler_bins_full_grid_active and update_grid_doppler_wipeoffs()
uploads the unified grid to the GPU.
- Order code and carrier uploads on the compute stream and wait before
returning so callers can safely reuse host buffers.
- Verify CUDA readiness and completed grid counts in adapter tests,
rejecting unexpected CPU fallback.
- Exercise host-buffer reuse in engine parity tests.
- Correct the Jetson helper's test executable path.
- Use ArgsProduct for compatibility with Google Benchmark 1.9.2–1.9.5.
Build system:
- Drop hardcoded sm_30 (Kepler) nvcc flags rejected by CUDA >= 11; honour
CMAKE_CUDA_ARCHITECTURES, auto-detect Jetson SoCs from the device tree
(Orin 87, Xavier 72, TX2 62, Nano 53) or use "native" with CMake >= 3.24.
- CUDA standard follows the host C++ standard (C++17).
- find_package(CUDAToolkit) and explicit CUDA::cudart / CUDA::cufft links.
- Define CUDA_GPU_ACCEL project-wide; keep -Wno-psabi away from nvcc.
Acquisition:
- New CudaPcpsEngine (acquisition/libs/cuda_pcps_engine.{h,cu}): batched
cuFFT evaluation of the Doppler x code-phase grid, one stream per block.
- pcps_acquisition::doppler_grid() dispatches to the engine when
Acquisition_XX.use_cuda=true (or GNSS-SDR.use_cuda_acquisition=true),
falling back to the CPU loop on any failure. Peak search and statistics
are unchanged.
Tracking (GPS_L1_CA_DLL_PLL_Tracking_GPU):
- Split the racy single-launch wipe-off + correlator kernel into two
launches on the same stream.
- cudaHostAllocMapped || cudaHostAllocWriteCombined -> cudaHostAllocMapped.
- No cudaDeviceReset() in the per-channel teardown; do not cudaFree()
device aliases of host-mapped buffers; const-correct GPU_Complex.
Tests/docs:
- CudaPcpsEngineTest (GPU grid vs CPU reference incl. accumulation,
bit-transition mode, 4 ms integration) and GpsL1CaPcpsAcquisitionCudaTest
(full adapter on a real capture, CPU vs GPU estimates).
- benchmark_pcps_grid: CPU baseline vs GPU sweep.
- docs/JETSON.md build/verify/benchmark guide; README, changelog, sample
config updates.
Signed-off-by: Phillip Vu <36169227+phillipvu@users.noreply.github.com>
Hybrid_Observables:
- Interpolate only between consecutive tracking samples whose TOW advanced
as much as the receiver time: after a gap in the valid words of a
channel, the last sample before it and the first one after it were used
together, giving a meaningless carrier phase and, if the TOW had stepped,
a first pseudorange off by up to hundreds of seconds. Fix the TOW
rollover in the interpolation and in the pseudorange computation, which
added a week to any travel time longer than 302.4 s.
- Set the receiver time from the largest group of channels agreeing on the
TOW, and set it again when such a group contradicts it, instead of
trusting the latest TOW of the first valid epoch. Channels contradicting
the receiver time do not deliver valid pseudoranges. Make the clock
correction from the PVT block safe at the TOW rollover.
- Size the tracking history from the receiver clock buffer and the
observables interval instead of a fixed count, which was too short for
1 ms symbols (Galileo E6) at long intervals and 4x too large at the
default one. Find the nearest sample with a binary search.
- Use the mean Doppler of the epoch in the cycle-slip detector: the final
value raised false slips with high Doppler rates and long intervals.
- Make the static helpers private and remove their unit test.
RTKLIB_PVT: do not count the epochs without enough satellites as solver
errors, and send the reset to the observables once per outage: each reset
emptied the tracking history of every channel every two seconds.
and reloading it from the XML assistance files, which could replace a
fresher, live-decoded almanac with a stale snapshot from disk, and the
handler now uses the visible-satellite list it computed instead of
prioritizing an empty one.
Visibility-aware acquisition search (GNSS-SDR.enable_visibility_aware_search):
- Classify GLONASS and QZSS satellites too. GLONASS positions come from the
GNAV almanac (new Glonass_Gnav_Almanac::satellite_position with the
standard perturbation model), so the search order benefits before any
GLONASS ephemeris is decoded.
- PVT publishes a lock-free navigation snapshot (ephemeris, almanac, UTC
models per system) that the classifier reads instead of locking the PVT
maps on every recompute.
- Recompute on new navigation data, on position changes larger than
visibility_recompute_position_threshold_m, every
visibility_recompute_interval_s, and when the almanac ages out; fix the
periodic and position-triggered recomputes that never fired, and the
off-by-one that excluded satellites in [mask, mask+1) degrees.
- Enter the start-up standby only when the feature is enabled, so the
default search order is untouched.
Secondary-frequency acquisition assistance:
- Do not start an acquisition on the PRN 0 placeholder left on assisted
channels at connect time; it threw inside the control thread and left the
flowgraph running headless with the secondary band never acquired.
- Match GLONASS assistance by FDMA frequency channel: the search pool keeps
one slot per channel while tracked L1 channels report the decoded slot, so
L2 was never picked.
- Assist from locked channels before their telemetry is decoded: the
observables status now reports tracking-only channels and the flowgraph
keeps a separate tracking map for the assisted search.
set_doppler_num_bins(1) (an exactly-known Doppler, e.g. from a
dual-frequency assist) previously honored a widening exception: if
Acquisition_<Sig>.enable_assisted_doppler_narrowing was set, a
literal N == 1 request could get silently expanded into
doppler_narrowing_num_bins candidate bins instead of searched as
requested. That was never asked for and contradicts the whole point
of this unification -- N is supposed to be authoritative, full stop.
A caller that actually wants a safety margin around its own estimate
should just request that many bins directly (set_doppler_num_bins(3),
not set_doppler_num_bins(1) plus a config flag that silently changes
what "1" means).
Removes both config parameters entirely (enable_assisted_doppler_narrowing,
doppler_narrowing_num_bins) along with the widening branch in
set_doppler_num_bins(). Dual-frequency-assisted secondary acquisition
now always searches exactly 1 bin, unconditionally -- no config knob
changes that. Does not reintroduce next's original enable_doppler_narrowing
name/semantics either (a master on/off switch that could fall back an
assisted request to the full grid): under the unified API, the caller's
N is never second-guessed by a flag, matching this PR's whole premise.
Test file updated to match: init() drops both removed parameters, and
the two tests that specifically exercised the widening path
(NarrowingWithMultipleCandidateBinsRecoversOffsetEstimate,
NarrowedDumpMetadataMultipleCandidateBins) now request their multi-bin
coverage directly (set_doppler_num_bins(5)) instead of relying on
config-driven expansion from N == 1 -- same coverage, no removed
feature required to exercise it.
Verified: full local test suite (105 tests) passes against a
from-scratch build, isolated from other in-progress work.
set_doppler_uncertainty() only ever supported two states: the full
configured grid, or a single exactly-known bin (assisted narrowing's
2-bin variant). Regular searches, primary-frequency-assisted secondary
signals, and any future partial-uncertainty search (e.g. a visibility-
aware Doppler prediction with residual error) all had to be shoehorned
into one of those two cases instead of directly stating how many
candidate bins they actually need.
Replaces it with set_doppler_num_bins(uint32_t): 0 is a sentinel for
"the full configured range" (the only value a caller can't supply
directly, since that count is otherwise private to this class); any
other value is the literal candidate bin count, from 1 (exactly known,
as when assisted by an already-tracked primary frequency) up to the
full grid. Regular and narrowed searches now go through the exact same
code path -- update_grid_doppler_wipeoffs(), compute_statistics(), the
two statistic functions -- differing only in candidate_count.
CFAR reference-bin placement is generalized the same way:
needs_extra_reference_row(candidate_bins) reproduces both today's
full-grid conditional extra-row behavior (only when the in-grid
wraparound distance can't clear reference_bin_min_sidelobes) and
narrowed mode's unconditional extra-row assumption, for any candidate
count. d_min_reference_separation_hz is derived automatically from the
new reference_bin_min_sidelobes config parameter (default 4) and
coherent_integration_time_ms, rather than needing to be set directly.
Also fixes a latent asymmetry: whenever a dedicated reference row is
needed for more than one real candidate, two rows are now computed --
at +doppler_max and -doppler_max -- and the statistic picks whichever
sits opposite the winning candidate's side of center. Previously the
reference was always fixed at +doppler_max regardless of which side
the signal landed on; a candidate near the edge of the search range
could end up right next to its own reference sample. With exactly one
real candidate (always at offset 0, no side to mirror), a single fixed
row is kept, matching prior behavior.
The detection threshold (d_threshold_active) is recalibrated by
set_doppler_num_bins() whenever the active candidate count changes,
since compute_threshold() folds the bin count into the false-alarm
probability.
Propagates the rename through AcquisitionInterface,
PcpsAcquisitionAdapter, ChannelInterface, Channel, and
GNSSFlowgraph's two assist_acquisition_doppler() call sites (0 ->
literal full-grid sentinel for the regular/unassisted case, 1 for the
dual-frequency-assisted case -- an exact inversion of the old
uncertainty-flag convention).
Verified: full local test suite (acq_test, trk_test, gnss_block_test,
flowgraph_test -- 105 tests) passes against a from-scratch build off
upstream/next, isolated from other in-progress work in this tree.
Follow-ups to the frequency-error-reduction merge (tracking state 5):
- Anchor Flag_PLL_180_deg_phase_locked to the sign of the last Prompt
symbol. The differential bit/secondary-code detector reconstructs bits
relative to an arbitrary first bit, so the previous corr_value sign rule
was independent of the actual carrier polarity.
- Do not feed the deliberate Doppler bin hops of the scan into the
high_dyn carrier/code phase-rate smoothers: clear the histories and
zero the rate steps while scanning.
- Start the pull_in_time_s and bit_synchronization_time_limit_s budgets
at the first sample after the scan instead of at acquisition, so long
scans no longer consume the FLL pull-in or the sync time limit. Scan
completion re-arms pull-in and resets the lock-fail counters. The
non-scan path keeps the acquisition time origin.
- Clamp f_error_accumulation to at least 1, both when parsed from the
configuration and in the block constructor, to avoid an out-of-bounds
write and a scan that never advances.
- Add unit tests for polarity, rate suppression, timers, and a full L2C
scan, and document the new options in the CHANGELOG.
Reset CNAV2 state on channel reassignment and reject PRN-mismatched
frames. Assemble MT10/MT11/clock into a candidate set keyed by broadcast
IODE/IODC instead of sticky arrival flags. Store CNAV2 independently of
CNAV1, set the B2a wavelength on the occupied RTKLIB slot, and write
RINEX 4 CNV2 (omit CNAV2 on RINEX 3).
Signed-off-by: huangchuhan <huangchh37@mail2.sysu.edu.cn>
Follow-up to the galileo-navtype-fallback merge. The original fallback let
E5a/E5b/E6 observations use the other service's ephemeris, but the BGD index
for those signals is fixed by frequency, so an E5a range modeled with an
I/NAV clock would be off by BGD(E1,E5a) - BGD(E1,E5b). Only E1 selects its
BGD from the ephemeris clock model, so only E1 may fall back.
Drop the per-PRN locked set: I/NAV and F/NAV carry the same IODnav/toe,
so they expire together, and an E1 switch between services costs only BGD
quantization. select_galileo_ephemeris() is const again and simply prefers
the primary service while its ephemeris is fresh.
Also normalize a legacy Unknown provenance to the primary service on the
returned ephemeris, and allow E1 Reduced CED in F/NAV-primary receivers.
Introduce the 5D signal chain: ICD BPSK(10) replica, PCPS acquisition,
DLL-PLL tracking (1 ms data component), and a first-cut B-CNAV2 decoder
(preamble + CRC-24Q, MT10/MT11/MT30, systematic 288 bits, no 64-ary
LDPC). GEO and BDS-2 PRNs are not assigned B2a channels. B2a-only SPP
remaps 5D onto RTKLIB slot 0 with lam[0]=c/FREQ5.
Signed-off-by: huangchuhan <huangchh37@mail2.sysu.edu.cn>
Covers Dll_Pll_Conf::SetFromConfiguration() parsing of f_error_step_num
(default disabled, odd values pass through, even values round up to
odd, zero stays disabled) and f_error_accumulation/f_error_doppler_step
pass-through, plus dll_pll_veml_tracking::f_error_bin_multiplier()'s
bin-index-to-Doppler-offset mapping, including that step_num=5
reproduces the original fixed 5-bin scan order.
f_error_bin_multiplier() is made public so it can be exercised
directly from the test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: joebre <joel.brenner@saphyrion.ch>