fix(gps): adapt Doppler bandwidth from board validation

This commit is contained in:
2026-07-26 03:15:52 +03:00
parent 23650be635
commit 156cfc8b77
24 changed files with 592 additions and 49 deletions
+24
View File
@@ -125,6 +125,20 @@ grafik bu farkı açıkça gösterir.
Raw `b/c` değerini PI phase error olarak doğrudan kullanmak doğru değildir.
Klasik GNSS denklemindeki `c*delta_t_r` clock bias ile buradaki `b` aynı datum
değildir: capture'ların ortak receiver zamanı önceden çıkarılmıştır. Doppler
velocity WLS'nin dördüncü state'i uydulara ortak receiver clock/frequency
drift'idir; fakat bu göreli `b` değerinin doğrudan türevi olarak kullanılmaz.
PPS modeli datumun değişimini seçilen referans uydunun geometrik range-rate'i,
broadcast satellite clock drift'i ve Doppler-WLS receiver velocity'sinden
hesaplar:
```text
bias_drift =
-reference_geometric_range_rate
+ c * reference_satellite_clock_drift
```
## İlk HOW ve PPS başlangıcı
`parse_subframe()` HOW Z-count alanını `6 s` ile çarpıp `tow.sec` yapar. Bu,
@@ -256,6 +270,16 @@ NaN, dört uydunun altına düşme, eşik sınırları ve histerezisi;
`test_gps_pps_time_filter.c` drift prediction, gap/re-anchor ve hard-outlier
reddini sınar.
2026-07-26 Genesys2 logu ile kalibre edilen aynı-datum A/B testinde position
WLS clock-bias gürültüsü `15.87 m`, referans-datum drift'i yaklaşık
`291.75 m/s`, Doppler drift gürültüsü `3.47 m/s` ve oscillator hatası
`1.592 ppm` olarak modele verildi. Linux C fixed-point testinde Doppler
predictor clock-bias RMS'i `2.75 m`, yalnız son bias'i tutan fallback ise
`21.22 m` verdi. Python modeli sekiz seed'de hem phase RMS hem 125 MHz grid'e
quantize edilmiş edge-jitter p95 için predictor'un fallback'ten iyi kalmasını
regresyon koşulu yapar. Kart status register phase'i fiziksel pin jitter'i
değildir; `15-20 ns` çıkış hedefi son olarak scope/TIC ile ölçülmelidir.
İki zaman eşiği bilinçli olarak ayrıdır: iki saniyelik watchdog gözlem yokken
PPS validliğini holdover'a indirir; aynı referans SVID için Doppler-predicted
clock-bias ve PI state'i 30 saniyeye kadar korunur. Modelde 15 saniyelik WLS
@@ -59,6 +59,90 @@ def noisy_observations(
class AdaptivePpsTests(unittest.TestCase):
def test_board_calibrated_doppler_predictor_reduces_pps_error(self):
"""Genesys2 WLS statistics: predictor-on must beat fallback-only."""
quality_metrics = QualityMetrics(
1.50, 2.92, 15.87, 40.3, 34.8, 0.35, 0.23, 7
)
aided_phase_rms = []
fallback_phase_rms = []
aided_edge_jitter = []
fallback_edge_jitter = []
for seed in range(8):
rng = random.Random(7300 + seed)
observations = []
for update in range(900):
time_s = 0.2 * update
long_omega = 2.0 * math.pi / 75.0
short_omega = 2.0 * math.pi / 19.0
varying_drift_mps = (
5.0 * math.sin(long_omega * time_s)
+ 2.0 * math.sin(short_omega * time_s)
)
varying_bias_m = (
5.0 * (1.0 - math.cos(long_omega * time_s)) / long_omega
+ 2.0 * (1.0 - math.cos(short_omega * time_s))
/ short_omega
)
# 15.87 m / c = 52.94 ns; 3.47 m/s is the measured
# board standard deviation of the reference bias drift.
noise_s = rng.gauss(0.0, 15.87 / 299_792_458.0)
if update % 113 == 0 and update != 0:
noise_s += rng.choice((-1.0, 1.0)) * 120.0e-9
observations.append(
Observation(
time_s,
noise_s,
varying_drift_mps + rng.gauss(0.0, 3.47),
quality_metrics,
True,
-291.75,
1,
varying_bias_m,
)
)
aided = run_observations(
observations,
finish_time_s=180.0,
oscillator_ppm=1.592,
)
fallback = run_observations(
[
Observation(
item.time_s,
item.timing_error_s,
item.clock_drift_error_mps,
item.quality,
False,
item.reference_range_rate_mps,
item.reference_id,
item.reference_datum_offset_m,
)
for item in observations
],
finish_time_s=180.0,
oscillator_ppm=1.592,
)
aided_phase_rms.append(aided.phase_rms(after_s=60.0))
fallback_phase_rms.append(fallback.phase_rms(after_s=60.0))
aided_edge_jitter.append(
aided.one_second_edge_jitter_rms(after_s=60.0)
)
fallback_edge_jitter.append(
fallback.one_second_edge_jitter_rms(after_s=60.0)
)
self.assertEqual(aided.unlock_events, 0)
self.assertLess(
sum(aided_phase_rms) / len(aided_phase_rms),
0.35 * sum(fallback_phase_rms) / len(fallback_phase_rms),
)
self.assertLess(
sum(aided_edge_jitter) / len(aided_edge_jitter),
0.70 * sum(fallback_edge_jitter) / len(fallback_edge_jitter),
)
self.assertLess(max(aided_phase_rms), 30.0e-9)
def test_quality_hysteresis_rejects_metric_chatter(self):
selector = QualitySelector()
for _ in range(7):