feat(gps): add timer-based assisted reacquisition

Remove the fixed pseudorange start offset, preserve per-PRN navigation assistance across RF loss and power cycles, and seed coherent navigation measurement phase on reacquisition. Add full LNAV almanac visibility scheduling, power-safe QSPI journaling, RF-domain reset isolation, and PPS Gray CDC hardening with Linux, sanitizer, RTL, and end-to-end coverage.
This commit is contained in:
onur
2026-08-04 02:52:35 +03:00
parent 347e1c671d
commit 30179df8e6
69 changed files with 6162 additions and 247 deletions
+10 -8
View File
@@ -30,11 +30,13 @@ davranışlarını taklit eder:
Model ve C ayni 8 ns integer mailbox yuvarlamasini kullanir.
- `gps_unwrap_corrected_abs_sample()` ile aynı signed modulo-32 unwrap.
- `gps_ip.c` ile aynı pseudorange datum'u:
- `gps_ip.c` ile aynı free-counter pseudorange datum'u:
```text
precise_ms_i = abs_i / 16367.981 - reference + 68.802
P_i = precise_ms_i * c / 1000 + satellite_clock_i * c
S_i = unwrap32(ABS_SAMPLE_i) - REM_CODE_PHASE_i / 1.023e6
P_i/c = (S_i - S_ref) / 16.367981e6
- (transmit_i - transmit_ref)
+ satellite_clock_i
```
- `NavEquations.c` ile aynı dört state:
@@ -132,13 +134,13 @@ Mevcut observation denkleminde WLS bias doğrudan PPS fazı değildir. İlk
ilişki:
```text
b/c = STARTOFFSET - signal_flight_time_ref
b/c = -signal_flight_time_ref + satellite_clock_ref
```
Bu nedenle doğru GPS referansı:
```text
GPS_TIME_AT_CAPTURE = transmit_epoch_TOW + STARTOFFSET - b/c
GPS_TIME_AT_CAPTURE = transmit_epoch_TOW - b/c
```
ile kurulur ve referans kanala ait `tracking_tick_125` ile eşlenir. Referans
@@ -176,7 +178,7 @@ teslim edilen subframe için:
```text
transmit_epoch_TOW = HOW_TOW + POSTPONE_TOW
GPS_TIME_AT_CAPTURE = transmit_epoch_TOW + STARTOFFSET - b/c
GPS_TIME_AT_CAPTURE = transmit_epoch_TOW - b/c
```
`POSTPONE_TOW` signal flight time veya receiver clock bias değildir; hardware
@@ -205,8 +207,8 @@ bu üç etkiyi ayrı gösterir.
ilk hardware delivery epoch'u için 180 ms sabit readout düzeltmesi ayrıca
eklenir.
3. İlk coherent WLS çözümünde minimum corrected-ABS kanalı datum olarak
seçilir; aynı SVID mevcut kaldığı sürece onun uçuş süresi
`STARTOFFSET - b/c` ile elde edilir.
seçilir; aynı SVID mevcut kaldığı sürece onun uçuş süresi `-b/c` ile elde
edilir (referans satellite clock correction'i dahil).
4. Bu receive GPS zamanı aynı kanalın yakalanmış 125 MHz timestamp'ıyla
eşlenerek PPS accumulator coarse-align edilir.
5. Sonraki çözümler aynı eşleşmeden residual phase error üretip PI'yi besler.
+18 -18
View File
@@ -300,10 +300,10 @@ def page_executive(report: Report) -> None:
0.07,
y,
"Bu projede konum WLS'i doğru çalışırken elde edilen dördüncü state, doğrudan "
"125 MHz receiver clock fazı değildir. Pseudorange'lar her epoch'ta minimum "
"ABS_SAMPLE ile normalize edildiği için RF sayacının mutlak fazı observation'dan "
"çıkar. WLS bias; 68.802 ms sentetik datum ile minimum kanalın gözlenen uçuş "
"süresi arasındaki ortak farkı taşır.",
"125 MHz receiver clock fazı değildir. Pseudorange'lar her epoch'ta corrected RF "
"sample ve satellite transmit-time farklarıyla kurulduğu için sayacın mutlak fazı "
"observation'dan çıkar. WLS bias, referans kanalın negatif uçuş süresini ve "
"satellite clock correction'ini taşıyan göreli datumdur.",
width=100,
fontsize=10.2,
)
@@ -556,7 +556,7 @@ def page_project_map(report: Report) -> None:
(0.77, 0.54, 0.17, 0.10, "Tracking ISR", "event queue +\nshadow counter", LIGHT, NAVY),
(0.54, 0.54, 0.18, 0.10, "Linked-list state", "TOW owner\nsequence + sample", LIGHT, NAVY),
(0.28, 0.54, 0.20, 0.10, "Coherent snapshot", "25 ms window\nmodulo unwrap", PALE_GREEN, GREEN),
(0.06, 0.54, 0.17, 0.10, "Pseudorange", "minimum +\n68.802 ms", PALE_GREEN, GREEN),
(0.06, 0.54, 0.17, 0.10, "Pseudorange", "ΔS_RF −\nΔT transmit", PALE_GREEN, GREEN),
(0.06, 0.34, 0.17, 0.10, "Satellite model", "TOW + 180 ms\nephemeris/clock", PALE_BLUE, BLUE),
(0.32, 0.34, 0.23, 0.10, "Weighted LS", "[X Y Z b]\niono + tropo + URA/el", PALE_GREEN, GREEN),
(0.64, 0.34, 0.30, 0.10, "Bugünkü çıkış", "position log + static last_solution[4]\nclock bias dışarı taşınmıyor", "#FBEAEA", RED),
@@ -651,14 +651,14 @@ def page_pseudorange(report: Report) -> None:
y = 0.84
ax.text(0.08, y, "1 Fractional RF sample düzeltmesi", fontsize=11, color=NAVY, weight="bold")
y = report.equation(ax, y - 0.025, r"A_i^{corr}=A_i^{raw}-\frac{REM_i}{1.023\times10^6}")
ax.text(0.08, y, "2 Wrap-safe coherent unwrap ve minimum seçimi", fontsize=11, color=NAVY, weight="bold")
y = report.equation(ax, y - 0.025, r"A_{min}=\min_i\left(A_i^{corr}\right)")
ax.text(0.08, y, "3 C kodundaki sentetik propagation datum'u", fontsize=11, color=NAVY, weight="bold")
ax.text(0.08, y, "2 Corrected RF sample ve transmit-time farkı", fontsize=11, color=NAVY, weight="bold")
y = report.equation(ax, y - 0.025, r"\Delta t_i=\frac{S_i-S_{ref}}{16.367981\,MHz}-(T_{tx,i}-T_{tx,ref})")
ax.text(0.08, y, "3 Offset-free göreli pseudorange datum'u", fontsize=11, color=NAVY, weight="bold")
y = report.equation(
ax,
y - 0.025,
r"P'_i=c\left[\frac{A_i^{corr}-A_{min}}{16.368\,MHz}+S\right]+c\,\delta t_{SV,i}",
"S = 68.802 ms; gps_ip.c içinde ms formunda hesaplanır",
r"P'_i=c\left[\Delta t_i+\delta t_{SV,i}\right]",
"Free-running counter başlangıcı ve ortak uzun-dönem drift farkta yok olur",
face=PALE_ORANGE,
)
report.box(
@@ -667,8 +667,8 @@ def page_pseudorange(report: Report) -> None:
0.17,
0.40,
0.14,
"Minimum neden var?",
"Raw ABS_SAMPLE'ın receiver açılışına bağlı büyük ortak sayısını çıkarır. Uydular arası arrival-time farkları korunur; konum geometrisi bu farklarda bulunur.",
"Referans neden var?",
"RF counter'ın receiver açılışına bağlı ortak sayısını wrap-safe çıkarır. Uydular arası arrival/transmit-time farkları korunur; konum geometrisi bu farklarda bulunur.",
face=PALE_BLUE,
)
report.box(
@@ -677,8 +677,8 @@ def page_pseudorange(report: Report) -> None:
0.17,
0.40,
0.14,
"68.802 ms neden var?",
"Göreli range'leri fiziksel 20–26 bin km büyüklüğüne taşır ve WLS common bias'ı sıfıra yakın tutar. Yeni bilgi eklemez; datum seçer.",
"Sabit offset neden yok?",
"WLS dördüncü state göreli datumun büyük ortak bias'ını zaten çözer. Fiziksel konum için kalibre edilmiş nominal uçuş süresi gerekmez.",
face=PALE_GREEN,
edge="#9BC9AF",
title_color=GREEN,
@@ -1136,7 +1136,7 @@ def page_sim_method(report: Report) -> None:
("Navigation", "Ortak 20 ms transmit-bit epoch; NAV_SOLVE_TIMER=10 → 200 ms"),
("Satellite", "Sentetik gerçekçi ECEF/az-el geometrisi, satellite clock, path delay"),
("Corrections", "Earth rotation, Klobuchar, aktif C troposfer modeli, URA/elevation noise"),
("Firmware", "Modulo unwrap, minimum + 68.802 ms, clock correction, cached four-state WLS"),
("Firmware", "Raw-counter/transmit-time difference, clock correction, cached four-state WLS"),
("Latency", "Satellite arrival span, WLS compute, scheduler stall, APB jitter"),
("PPS", "125 MHz raw oscillator, coarse mapping, timestamped/untagged PI, 8 ns edge grid"),
]
@@ -1264,7 +1264,7 @@ def page_verification(report: Report) -> None:
fig, ax = report.new_page("Doğrulama matrisi ve beklenen sonuç")
tests = [
("ABS common +5 ms", "XYZ aynı; normalized b aynı", "Observation common phase iptalini kanıtlar"),
("STARTOFFSET +1 ms", "XYZ aynı; b +c×1 ms", "Datum dönüşüm işaretini kanıtlar"),
("RF counter common shift", "XYZ ve göreli b aynı", "RF counter origin iptalini kanıtlar"),
("32-bit ABS wrap", "Pseudorange süreklidir", "Signed modulo unwrap"),
("Reference SVID değişimi", "Raw b step; mapped GPS time continuous", "Min datum ownership"),
("APB +50 ms delay", "Timestamped PPS fazı değişmez", "Epoch association"),
@@ -1340,7 +1340,7 @@ def page_conclusion(report: Report) -> None:
("1", "Raw clocks", "16.368 MHz measurement + 125 MHz monotonic counter"),
("2", "Paired capture", "ABS / REM / C125 / sequence aynı event"),
("3", "Coherent WLS", "minimum datum + all-satellite [XYZ b]"),
("4", "GPS receive time", "T_TX(epoch-aligned) + 68.802 ms − b/c"),
("4", "GPS receive time", "T_TX(epoch-aligned) − b/c"),
("5", "Atomic mapping", "(C125,k, TGPS,k, seq) shadow + commit"),
("6", "Disciplined PPS", "immutable raw counter + low-bandwidth PI accumulator"),
]
@@ -1414,7 +1414,7 @@ def generate(output_pdf: Path) -> None:
report,
"İlk HOW neden PPS receive-now değildir?",
required_plots[4],
"Sentetik acquisition/subframe Monte Carlo: mevcut hardware delivery, dokuzuncu takip eden bit nedeniyle HOW sınırından 180 ms ve signal flight time kadar sonradır. HOW +180 ms +68.802 ms kaba yaklaşım bırakır; WLS flight-time + paired timestamp doğru handover'dır.",
"Sentetik acquisition/subframe Monte Carlo: mevcut hardware delivery, dokuzuncu takip eden bit nedeniyle HOW sınırından 180 ms ve signal flight time kadar sonradır. HOW +180 ms tek başına uçuş süresi kadar hata bırakır; offset-free WLS flight-time + paired timestamp doğru handover'dır.",
)
plot_page(
report,
+10 -8
View File
@@ -5,7 +5,8 @@ mirrors the conventions used by gps_system/src/gps_ip.c and NavEquations.c:
* corrected_abs = raw_abs - rem_code_phase / CODE_FREQ_BASIS
* modulo-32 ABS_SAMPLE values are unwrapped around the newest channel
* obs_i = c * ((abs_i - min(abs)) / SAMPLES_PER_CODE + STARTOFFSET) / 1000
* obs_i/c = (corrected_abs_i - corrected_abs_ref) / RF_HZ
- (transmit_i - transmit_ref)
* satellite clock correction is added to the observation
* [x, y, z, common_bias_m] are solved together with URA/elevation weights
@@ -27,8 +28,6 @@ RF_HZ = 16_367_981.0
TRACKING_HZ = 125_000_000.0
SAMPLES_PER_CODE = 16_367.981 # physical RF samples/ms used by the C code
CODE_FREQ_BASIS = 1.023e6
START_OFFSET_MS = 68.802
START_OFFSET_S = START_OFFSET_MS / 1000.0
NAV_SOLVE_PERIOD_S = 10 * 0.020
POSTPONE_TOW_S = 0.180
OMEGA_EARTH = 7.2921151467e-5
@@ -680,10 +679,14 @@ class ReceiverEpochSimulator:
corrected_abs = np.asarray(
[measurement.corrected_abs_unwrapped for measurement in measurements]
)
ps_tvl_ms = corrected_abs / SAMPLES_PER_CODE
reference_index = int(np.argmin(ps_tvl_ms))
precise_tx_ms = ps_tvl_ms - ps_tvl_ms[reference_index] + START_OFFSET_MS
observations_m = precise_tx_ms * (C / 1000.0)
reference_index = int(np.argmin(corrected_abs))
# All channels in this synthetic epoch share one transmit time. Mirror
# firmware's wrap-safe corrected RF-sample difference. The fractional
# code residue removes the 61 ns integer-sample quantization; the
# separately captured 125 MHz counter is used only for PPS epoch mapping.
observations_m = (
corrected_abs - corrected_abs[reference_index]
) * (C / RF_HZ)
observations_m += C * np.asarray(
[measurement.satellite_clock_s for measurement in measurements]
)
@@ -711,7 +714,6 @@ class ReceiverEpochSimulator:
gps_reference_estimate = (
self.gps_tow_start_s
+ transmit_true_s
+ START_OFFSET_S
- wls.clock_bias_m / C
)
gps_reference_true = self.gps_tow_start_s + reference.arrival_true_s
+6 -8
View File
@@ -22,7 +22,6 @@ try:
DEFAULT_IONO,
POSTPONE_TOW_S,
RF_HZ,
START_OFFSET_S,
TRACKING_HZ,
ReceiverEpochSimulator,
build_clock_trajectory,
@@ -41,7 +40,6 @@ except ImportError: # allow: python3 pps_python/simulate_pps.py
DEFAULT_IONO,
POSTPONE_TOW_S,
RF_HZ,
START_OFFSET_S,
TRACKING_HZ,
ReceiverEpochSimulator,
build_clock_trajectory,
@@ -321,7 +319,7 @@ def simulate_startup_tow(
"first_flight_s": first_flight,
# Parsed HOW labels transmit subframe time, not receive-now.
"how_as_receive_error_s": -(POSTPONE_TOW_S + first_flight),
"how_plus_pipeline_startoffset_error_s": START_OFFSET_S - first_flight,
"how_plus_pipeline_without_wls_error_s": -first_flight,
"wls_aligned_error_s": wls_error,
# Old receive reference written as if it were valid at APB-now.
"apb_now_error_s": wls_error - sampled_latency,
@@ -349,13 +347,13 @@ def plot_startup_tow(output_dir: Path, startup: dict[str, np.ndarray]) -> None:
axes[0, 1].grid(True, alpha=0.3)
axes[1, 0].hist(
startup["how_plus_pipeline_startoffset_error_s"] * 1.0e3,
startup["how_plus_pipeline_without_wls_error_s"] * 1.0e3,
bins=35,
color="tab:orange",
alpha=0.8,
)
axes[1, 0].set_xlabel("Phase error [ms]")
axes[1, 0].set_title("Provisional: HOW + 180 ms + 68.802 ms")
axes[1, 0].set_title("Still wrong: HOW + 180 ms without WLS flight time")
axes[1, 0].grid(True, alpha=0.3)
axes[1, 1].hist(
@@ -398,7 +396,7 @@ def build_summary(
f"derived GPS reference RMS error: {finite_rms(timing_error)*1e9:.3f} ns",
f"capture-to-commit delay mean/p99: {np.mean(latency)*1e3:.3f} / {np.percentile(latency, 99)*1e3:.3f} ms",
f"first HOW used as receive-now, median phase error: {np.median(startup['how_as_receive_error_s'])*1e3:.3f} ms",
f"first HOW + 180 ms + 68.802 ms, median phase error: {np.median(startup['how_plus_pipeline_startoffset_error_s'])*1e3:.3f} ms",
f"first HOW + 180 ms without WLS, median phase error: {np.median(startup['how_plus_pipeline_without_wls_error_s'])*1e3:.3f} ms",
]
for start, stop, label in [
(0.0, 20.0, "4 satellites"),
@@ -426,8 +424,8 @@ def build_summary(
[
"",
"Interpretation:",
"- bias/c is millisecond-scale because it contains the 68.802 ms synthetic datum and the minimum satellite path.",
"- epoch-aligned TOW (first delivery: HOW + 180 ms) + STARTOFFSET - bias/c reconstructs GPS time at the paired timestamp.",
"- bias/c is millisecond-scale because the timer-difference datum contains the negative reference-satellite path.",
"- epoch-aligned TOW (first delivery: HOW + 180 ms) - bias/c reconstructs GPS time at the paired timestamp.",
"- treating that old reference as valid at APB arrival converts software latency into PPS phase error.",
"- timestamping removes that phase misassociation; control application delay remains only as loop delay.",
"- baremetal applies the fixed 180 ms readout correction once; the PPS peripheral must not add it again.",