fix(gps): synchronize complete raw navigation epochs
This commit is contained in:
@@ -909,15 +909,16 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
|
||||
stage_metrics->candidate_total += candidate_count;
|
||||
|
||||
gps_nav_epoch_selection_t selection;
|
||||
bool selected = gps_nav_epoch_select(
|
||||
bool selected = gps_nav_epoch_validate_complete(
|
||||
candidates, candidate_count, EPOCH_SAMPLE_WINDOW,
|
||||
EPOCH_TOW_TOLERANCE_SEC, &selection);
|
||||
unsigned selected_count = selected ? selection.selected_count : 0u;
|
||||
unsigned selected_count = selection.selected_count;
|
||||
stage_metrics->selected_total += selected_count;
|
||||
stage_metrics->epoch_exclusions += candidate_count - selected_count;
|
||||
|
||||
if (stage == STAGE_DELAYED_CHANNEL &&
|
||||
(selected_count != 7u ||
|
||||
(selected ||
|
||||
selected_count != 7u ||
|
||||
(selection.selected_mask & (1u << 7)) != 0u)) {
|
||||
delayed_selection_failures++;
|
||||
}
|
||||
@@ -930,7 +931,7 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
|
||||
postcommit_join_count_failures++;
|
||||
}
|
||||
|
||||
if (selected_count < STATE_DIM) {
|
||||
if (!selected || selected_count < STATE_DIM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -983,14 +984,14 @@ static void run_general_synthetic_scenario(const double receiver_truth[3],
|
||||
CHECK(metrics[STAGE_THREE_DROPOUTS].fixes >= 58u,
|
||||
"five remaining satellites must continue producing fixes");
|
||||
CHECK(metrics[STAGE_MINIMUM_FOUR].fixes == 40u,
|
||||
"four satellites must remain a publishable minimum fix");
|
||||
"four satellites must remain a raw publishable minimum fix");
|
||||
CHECK(metrics[STAGE_OUTAGE_THREE].fixes == 0u && below_minimum_fixes == 0u,
|
||||
"three satellites must suppress the position solution");
|
||||
CHECK(metrics[STAGE_REACQUIRE].fixes >= 58u,
|
||||
"position must recover after satellite reacquisition");
|
||||
CHECK(delayed_selection_failures == 0u &&
|
||||
metrics[STAGE_DELAYED_CHANNEL].fixes >= 58u,
|
||||
"delayed/newest outlier channel must be excluded without blocking fixes");
|
||||
metrics[STAGE_DELAYED_CHANNEL].fixes == 0u,
|
||||
"incoherent delayed channel must reject the complete raw epoch");
|
||||
CHECK(precommit_join_count_failures == 0u,
|
||||
"joining channel must remain excluded before matching SF1/SF2/SF3");
|
||||
CHECK(postcommit_join_count_failures == 0u,
|
||||
|
||||
@@ -665,7 +665,7 @@ GPS_TRK_MEASUREMENT_STALE_TICKS
|
||||
- `NAV_SOLVE_INTR_EN=1`,
|
||||
- `NAV_SOLVE_FLAG_EN=1`.
|
||||
- Ilk pozisyon dahil butun pozisyon hesaplari, kanallardan senkron nav-solve tick ve `ABS_SAMPLE` geldikten sonra olusturulan coherent nav-solve snapshot'i ile yapilir.
|
||||
- Nav-bit ve nav-solve handler'lari ephemeris deposunu ortak kullansa da corrected `ABS_SAMPLE` nav-solve icin node icinde ayri saklanir. Pozisyon snapshot'i bu ayri alani kullanir; araya giren nav-bit interrupt'i pozisyon olcum epoch'unu degistiremez.
|
||||
- Nav-bit ve nav-solve handler'lari canli ephemeris deposunu ortak kullansa da her kabul edilen nav-solve event'i corrected `ABS_SAMPLE`, TOW ve o anda commit edilmis ephemerisin tam kopyasini aktif epoch snapshot'ina birlikte yazar. WLS daha sonra canli node listesinden ephemeris okumaz; araya giren nav-bit interrupt'i eski olcumle yeni ephemerisi karistiramaz.
|
||||
- Coherent snapshot olusturulurken 32-bit raw `ABS_SAMPLE` degerleri en yeni sample etrafinda modulo fark ile unwrap edilir. Sayac wrap sinirinin iki tarafindaki uydular ayni pseudorange hesabinda kullanilabilir.
|
||||
- Nav bits akisi ilk tutarli subframe 1..3 seti commit edildikten sonra kapatilmaz. Boylece yeni subframe 1..3 verisi ephemerisi, subframe 4 page 18 verisi iyonosfer/UTC parametrelerini guncel tutar.
|
||||
- Nav-solve interrupt'lari acilmadan once TOW, parity kontrolunden gecen nav-bit HOW alanindan kurulur. Her yeni HOW'un GPS zaman farki receiver sample-counter farkiyla karsilastirilir; tek bir sureksiz HOW saglikli anchor'i degistiremez. Iki ardışık aday kendi aralarinda tutarliysa kanal reset gerektirmeden yeni zaman cizgisine gecerek toparlanir. Subframe 1 gelmeden onceki gecici hafta, ilk gecerli WN ile atomik olarak guncellenir.
|
||||
@@ -679,9 +679,9 @@ GPS_TRK_MEASUREMENT_STALE_TICKS
|
||||
- Initial position sonrasinda subframe'lerini tamamlayan yeni tracking kanallarinda da nav bits acik tutularak nav-solve otomatik etkinlestirilir.
|
||||
- Coherence kontrolu sadece software systick ile yapilmaz; periodic snapshot icin tracking node'da saklanan nav-solve interrupt sonrasi raw `ABS_SAMPLE` kullanilir. `GPS_NAV_ABS_SAMPLE_SYNC_WINDOW_MS=25` oldugu icin 4 satellite measurement'i 25 ms'den fazla ayriksa navigation solve calistirilmaz. Bu pencere interrupt skew'den cok uydu path delay farkini kapsamak icindir: 20 ms yaklasik 6000 km, 25 ms yaklasik 7500 km pseudorange farkina denk gelir. 25 ms, dusuk-elevation uydu geometrisi ve tracking jitter icin pay birakir ama 50 ms kadar gevsek degildir.
|
||||
- Interrupt'lar epoch'a GPS TOW ile degil, butun kanallarda ortak olan 64-bit PPS capture counter ile atanir. Ilk olcum sabit expected maskeyi ve 30 ms bounded holdoff'u baslatir. 25 ms pencereye giren kanallar ayni fiziksel epoch'a katilir; yarim nav periyodundan uzaktaki eski/yeni capture'lar stale veya yeni epoch olarak siniflanir. Boylece bozuk bir gelecek HOW degeri epoch assembler'i kilitleyemez.
|
||||
- Tam veya en az dort uydulu timeout snapshot'i sabit boyutlu iki buffer arasinda immutable olarak foreground'a devredilir. Hardware mailbox birden fazla pulse'u kacirdiysa sequence farki yalniz diagnostic sayacidir; elde olmayan ara olcumler sentetik olarak uretilmez. Tamamlanan snapshot WLS sonucu basarili veya basarisiz olsa da yalniz bir kez denenir.
|
||||
- Immutable snapshot icindeki adaylar heap kullanmayan selector'a verilir. Selector TOW ve modulo-32-bit `ABS_SAMPLE` kosullarini ikili olarak saglayan en buyuk coherent alt kumeyi secer; tek bir gecikmis veya bozuk kanal diger en az 4 uyduyu bloke etmez. Esit buyuklukte ayrik kumelerde daha yeni sample epoch'u tercih edilir.
|
||||
- Navigation snapshot ayni SVID/PRN'i ikinci kez kabul etmez. Ayni uydu iki tracking IP uzerinden ready gorunurse ikinci kopya sessizce snapshot disinda birakilir; ayrinti sadece `DEBUG_TRACE` seviyesinde gorulur.
|
||||
- Yalniz frozen expected maskenin tamami geldiginde snapshot sabit boyutlu iki buffer arasinda immutable olarak foreground'a devredilir. 30 ms holdoff dolan eksik epoch ve daha yeni counter geldigi anda yarim kalan eski epoch butunuyle discard edilir. Hardware mailbox birden fazla pulse'u kacirdiysa sequence/counter farki tutarli olan en yeni gercek olcum kabul edilir; elde olmayan ara olcumler sentetik olarak uretilmez. Sequence ile 64-bit capture counter uyusmazsa event yalniz rebase icin kullanilir ve epoch'a girmez.
|
||||
- Immutable snapshot icindeki butun adaylar heap kullanmayan selector ile ikili TOW ve modulo-32-bit `ABS_SAMPLE` coherence kontrolunden gecirilir. En buyuk coherent kume frozen aday sayisindan bir eksik bile olsa tum epoch reddedilir; WLS icin alt kumeye dusulmez. Tamamlanan snapshot WLS sonucu basarili veya basarisiz olsa da yalniz bir kez denenir.
|
||||
- Navigation snapshot ayni SVID/PRN'i ikinci kez kabul etmez. Ayni uydu iki tracking IP uzerinden frozen ready maskeye girerse aday sayisi maskeyle uyusmayacagi icin tum epoch reddedilir; ayni uydu iki kez WLS'e giremez.
|
||||
- UART debug satirindaki `coherent_nav=` degeri, o anda ayni nav-solve penceresinde kullanilabilir kanal sayisini gosterir. 600 ms pozisyon gelmiyorsa `busy channels`, `ready channels` ve `coherent_nav` birlikte kontrol edilmelidir.
|
||||
- Tracking ISR event queue dolarsa, her tracking IP icin son event shadow buffer'a alinir. Shadow overwrite sayaci WARN olarak loglanir; bu log gorulurse CPU nav hesaplamasi veya UART debug cikisi tracking interrupt isleme hizini geciktiriyor olabilir.
|
||||
- Bir tracking kanali shadow buffer'a dustukten sonra shadow main context tarafindan tuketilene kadar ayni kanalin yeni event'leri tekrar ana queue'ya alinmaz; en yeni shadow snapshot'i korunur. Boylece daha yeni queue event'inin daha eski shadow event'inden once islenmesi ve raw sample zamaninin geriye gitmesi engellenir.
|
||||
@@ -728,8 +728,8 @@ weight = 1 / variance
|
||||
- `computeRange()` icinde aktif parametreler `psTVLTime`, `sat_no`, `eph`, `clockCorr` ve `satellitePositions`'dir. `rho`, `ionoutc`, `xyz`, `tempPos` ve `velocities` eski genis API'den kalmistir. Ionosphere correction artik receiver azimut/elevasyonu hazir oldugunda WLS icinde uygulanir.
|
||||
- Ephemeris alanlarinin mevcut isimleri ile ICD anlamlari su sekildedir: `omgdot` gercekte `Omega0`, `omg0` gercekte argument of perigee `omega`, `omgkdot` gercekte `OmegaDot` degeridir. `satpos()` bunlari dogru matematiksel yerde kullanir; konu su an hesap hatasi degil, ileride yapilacak kontrollu isim duzeltmesidir.
|
||||
- `POSTPONE_TOW=0.160` donanim/pipeline zaman kalibrasyonunun bir parcasidir; kaynagi dogrulanmadan kaldirilmamalidir.
|
||||
- Ilk nav-solve interrupt'i geldiginde o anda allocated, hardware busy ve nav-solve interrupt'i etkin olan kanallar epoch'un sabit bekleme maskesine alinir. Unlock interrupt'i gelen kanal bekleme maskesinden cikarilir; epoch ortasinda navigation-ready olan yeni kanal ise aktif maskeyi buyutmez ve sonraki epoch'tan itibaren beklenir. Butun beklenen kanallar gelirse snapshot hemen, 30 ms holdoff doldugunda ise elde en az dort kanal varsa eksik kanalla yayinlanir. Dortten az kanal discard edilir. Daha yeni bir fiziksel epoch basladiginda kullanilabilir eski snapshot once yayinlanir; eski ve yeni olcumler birlestirilmez.
|
||||
- Interrupt bekleme maskesi tamamlandiktan sonra en az dort uyduluk en buyuk ortak TOW/`ABS_SAMPLE` kumesi WLS girdisi olarak kullanilir; kume disi kanallar `epoch_excluded` olarak raporlanir ve sonraki donanim epoch'unda yeniden katilabilir.
|
||||
- Ilk nav-solve interrupt'i geldiginde o anda allocated, hardware busy ve nav-solve interrupt'i etkin olan kanallar epoch'un sabit bekleme maskesine alinir. Unlock interrupt'i gelen kanal artik tracked olmadigi icin bekleme maskesinden cikarilir; epoch ortasinda navigation-ready olan yeni kanal ise aktif maskeyi buyutmez ve sonraki epoch'tan itibaren beklenir. Kalan beklenen kanallarin tamami gelirse snapshot son interrupt'ta hemen yayinlanir. 30 ms holdoff dolarsa veya daha yeni fiziksel epoch baslarsa eksik eski epoch WLS calistirilmadan discard edilir; eski ve yeni olcumler birlestirilmez.
|
||||
- Interrupt bekleme maskesi tamamlandiktan sonra frozen kanallarin tumu ortak TOW/`ABS_SAMPLE` kumesinde degilse epoch reddedilir. Selector'in buldugu daha kucuk alt kume yalniz diagnostic bilgidir ve WLS girdisi olamaz.
|
||||
- Dort uydu 3D konum + receiver clock bias icin minimum ve yeterlidir. Bu durumda
|
||||
`dof=N-4=0` oldugu icin WLS konumu publish edilir fakat residual chi-square
|
||||
integrity bayragi false kalir. Bes veya daha fazla uyduda publish icin mevcut
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "gps.h"
|
||||
#include "gps/gps_ip_defines.h"
|
||||
#include "gps/gps_nav_epoch.h"
|
||||
#include "gps/matrixLib.h"
|
||||
|
||||
typedef struct gps_nav_solution {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
typedef struct {
|
||||
uint32_t channel_mask;
|
||||
uint32_t prn[GPS_TRCK_IP_COUNT];
|
||||
gps_ephemeris_t ephemeris[GPS_TRCK_IP_COUNT];
|
||||
gpstime_t tow[GPS_TRCK_IP_COUNT];
|
||||
uint32_t raw_sample[GPS_TRCK_IP_COUNT];
|
||||
double corrected_abs_sample[GPS_TRCK_IP_COUNT];
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// | || __/ | |_| | |___| _| | || |\ | |___ ___) |
|
||||
// |___|_| |____/|_____|_| |___|_| \_|_____|____/
|
||||
|
||||
#define MIN_NEEDED_SATELLITE 4
|
||||
#define GPS_TRCK_UNLOCK_TIMEOUT_TICKS 500000000u
|
||||
#define GPS_PRN_COUNT 32u
|
||||
#define GPS_PRN_INVALID 0xffu
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Four pseudoranges are the algebraic minimum for receiver x/y/z plus clock
|
||||
* bias. They leave no residual degree of freedom, so a bad measurement can
|
||||
* produce an arbitrary position while fitting perfectly. The raw receiver
|
||||
* still exposes that minimum WLS fix and its covariance; the separate
|
||||
* residual/integrity flags tell downstream fusion that no redundancy exists.
|
||||
*/
|
||||
#define GPS_NAV_MIN_WLS_SATELLITES 4u
|
||||
|
||||
/*
|
||||
* The current receiver exposes nine tracking channels. Keep the epoch
|
||||
* selector bounded so its exhaustive, allocation-free search has a fixed
|
||||
@@ -11,6 +20,10 @@
|
||||
*/
|
||||
#define GPS_NAV_EPOCH_MAX_CANDIDATES 9u
|
||||
|
||||
_Static_assert(GPS_NAV_MIN_WLS_SATELLITES <=
|
||||
GPS_NAV_EPOCH_MAX_CANDIDATES,
|
||||
"GPS WLS minimum exceeds epoch capacity");
|
||||
|
||||
typedef struct {
|
||||
int week;
|
||||
double tow_sec;
|
||||
@@ -50,6 +63,32 @@ bool gps_nav_epoch_select(
|
||||
double tow_tolerance_sec,
|
||||
gps_nav_epoch_selection_t* selection);
|
||||
|
||||
/*
|
||||
* Receiver publication is stricter than diagnostic clustering: every
|
||||
* candidate in the frozen ready mask must belong to one coherent epoch.
|
||||
* selection is still populated with the largest cluster on failure so the
|
||||
* caller can report why the complete epoch was rejected.
|
||||
*/
|
||||
bool gps_nav_epoch_validate_complete(
|
||||
const gps_nav_epoch_key_t candidates[GPS_NAV_EPOCH_MAX_CANDIDATES],
|
||||
unsigned int candidate_count,
|
||||
uint32_t sample_window,
|
||||
double tow_tolerance_sec,
|
||||
gps_nav_epoch_selection_t* selection);
|
||||
|
||||
/*
|
||||
* Validate a newest-only hardware mailbox after one or more capture events
|
||||
* were coalesced. sequence_gap receives the number of real elapsed epochs;
|
||||
* missing epochs are never synthesized.
|
||||
*/
|
||||
bool gps_nav_capture_sequence_consistent(
|
||||
uint64_t previous_counter,
|
||||
uint32_t previous_sequence,
|
||||
uint64_t current_counter,
|
||||
uint32_t current_sequence,
|
||||
uint64_t capture_period_ticks,
|
||||
uint32_t* sequence_gap);
|
||||
|
||||
void gps_nav_epoch_assembly_reset(gps_nav_epoch_assembly_t* assembly);
|
||||
|
||||
gps_nav_epoch_event_result_t gps_nav_epoch_assembly_push(
|
||||
|
||||
@@ -636,7 +636,7 @@ static bool least_squares_candidate(mat_type* satpos, mat_type* obs, int ActiveS
|
||||
if((satpos == NULL) || (obs == NULL) || (az == NULL) || (el == NULL) ||
|
||||
(ura_indices == NULL) || (solution_out == NULL) ||
|
||||
(satpos->matrix == NULL) || (obs->matrix == NULL) ||
|
||||
(ActiveSatellitesNumber < MIN_NEEDED_SATELLITE) ||
|
||||
(ActiveSatellitesNumber < (int)GPS_NAV_MIN_WLS_SATELLITES) ||
|
||||
((unsigned int)ActiveSatellitesNumber > GPS_TRCK_IP_COUNT))
|
||||
{
|
||||
GPS_LOG(DEBUG_TRACE, GPS_LOG_CAT_NAV, "least squares skipped invalid inputs, sats=%d\r\n",
|
||||
@@ -1201,11 +1201,14 @@ static bool least_squares_candidate(mat_type* satpos, mat_type* obs, int ActiveS
|
||||
solution_out->fde_applied = false;
|
||||
/* Four observations exactly determine x/y/z plus receiver clock bias. With
|
||||
* zero residual DOF no chi-square integrity statement is possible, but the
|
||||
* finite WLS solution and geometry covariance are still a valid minimum fix. */
|
||||
* raw finite WLS solution and covariance remain available to downstream
|
||||
* fusion, which performs its own temporal innovation test. */
|
||||
solution_out->solution_valid = solution_out->candidate_valid &&
|
||||
(((solution_out->used_satellite_count == 4u) &&
|
||||
(((solution_out->used_satellite_count ==
|
||||
GPS_NAV_MIN_WLS_SATELLITES) &&
|
||||
(solution_out->degrees_of_freedom == 0u)) ||
|
||||
(solution_out->residual_test_valid && solution_out->chi_square_pass));
|
||||
(solution_out->residual_test_valid &&
|
||||
solution_out->chi_square_pass));
|
||||
solution_out->iteration_count = iteration_count;
|
||||
solution_out->candidate_cycles = gps_read_cycle_counter() - candidate_start_cycles;
|
||||
|
||||
@@ -1392,7 +1395,8 @@ bool leastSquaresSTD(mat_type* satpos, mat_type* obs, int ActiveSatellitesNumber
|
||||
? (gps_read_cycle_counter() - fde_start_cycles) : 0u;
|
||||
selected_solution.total_wls_cycles = gps_read_cycle_counter() - total_wls_start_cycles;
|
||||
selected_solution.solution_valid = selected_solution.candidate_valid &&
|
||||
(((selected_solution.used_satellite_count == 4u) &&
|
||||
(((selected_solution.used_satellite_count ==
|
||||
GPS_NAV_MIN_WLS_SATELLITES) &&
|
||||
(selected_solution.degrees_of_freedom == 0u)) ||
|
||||
(selected_solution.residual_test_valid &&
|
||||
selected_solution.chi_square_pass));
|
||||
@@ -1406,8 +1410,7 @@ bool leastSquaresSTD(mat_type* satpos, mat_type* obs, int ActiveSatellitesNumber
|
||||
}
|
||||
*solution_out = selected_solution;
|
||||
|
||||
/* Print only publishable fixes. A four-satellite minimum fix is publishable,
|
||||
* although its separate integrity flag remains false because residual DOF=0. */
|
||||
/* Print every finite raw fix. Integrity remains a separate downstream input. */
|
||||
if(solution_out->solution_valid)
|
||||
{
|
||||
GPS_LOG(DEBUG_TRACE, GPS_LOG_CAT_NAV,
|
||||
|
||||
@@ -269,7 +269,7 @@ static void gps_nav_publish_active_snapshot(uint32_t channel_mask,
|
||||
bool reset_assembly)
|
||||
{
|
||||
channel_mask &= gps_nav_active_snapshot.channel_mask;
|
||||
if(gps_mask_count(channel_mask) < MIN_NEEDED_SATELLITE)
|
||||
if(gps_mask_count(channel_mask) < GPS_NAV_MIN_WLS_SATELLITES)
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_incomplete_total++;
|
||||
gps_nav_discard_active_snapshot(reset_assembly);
|
||||
@@ -299,17 +299,11 @@ bool gps_take_nav_epoch_snapshot(gps_nav_epoch_snapshot_t* snapshot)
|
||||
now_local_ticks))
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_incomplete_total++;
|
||||
if(gps_nav_epoch_assembly_usable(&gps_nav_epoch_assembly,
|
||||
MIN_NEEDED_SATELLITE))
|
||||
{
|
||||
gps_nav_publish_active_snapshot(
|
||||
gps_nav_epoch_assembly.received_mask, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* An epoch is an all-or-nothing hardware measurement set. Publishing
|
||||
* a timed-out subset changes its satellite geometry according to IRQ
|
||||
* timing and was the source of large raw-position jumps. */
|
||||
gps_nav_discard_active_snapshot(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool ready = gps_nav_completed_snapshot_pending;
|
||||
if(ready)
|
||||
@@ -1195,29 +1189,17 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
if(event->pps_capture_valid)
|
||||
{
|
||||
uint32_t previous_capture_sequence = node->gps_last_pps_capture_sequence;
|
||||
if(previous_capture_sequence != 0u)
|
||||
if(node->gps_last_nav_solve_pps_counter != 0u)
|
||||
{
|
||||
uint32_t sequence_delta =
|
||||
event->pps_capture_sequence - previous_capture_sequence;
|
||||
if(sequence_delta == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint32_t sequence_delta = 0u;
|
||||
bool sequence_consistent =
|
||||
event->pps_counter > node->gps_last_nav_solve_pps_counter;
|
||||
if(sequence_consistent)
|
||||
{
|
||||
uint64_t counter_delta =
|
||||
event->pps_counter -
|
||||
node->gps_last_nav_solve_pps_counter;
|
||||
uint64_t expected_delta =
|
||||
(counter_delta + (GPS_NAV_CAPTURE_PERIOD_TICKS / 2u)) /
|
||||
GPS_NAV_CAPTURE_PERIOD_TICKS;
|
||||
sequence_consistent =
|
||||
(expected_delta != 0u) &&
|
||||
(expected_delta <= UINT32_MAX) &&
|
||||
(sequence_delta == (uint32_t)expected_delta);
|
||||
}
|
||||
gps_nav_capture_sequence_consistent(
|
||||
node->gps_last_nav_solve_pps_counter,
|
||||
previous_capture_sequence,
|
||||
event->pps_counter,
|
||||
event->pps_capture_sequence,
|
||||
GPS_NAV_CAPTURE_PERIOD_TICKS,
|
||||
&sequence_delta);
|
||||
if(sequence_consistent)
|
||||
{
|
||||
captured_epoch_count = sequence_delta;
|
||||
@@ -1236,6 +1218,13 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
previous_capture_sequence,
|
||||
event->pps_capture_sequence,
|
||||
gps_runtime_diagnostics.pps_sequence_discontinuity_total);
|
||||
/* Rebase the newest-only transport metadata, but never let
|
||||
* an inconsistent counter/sequence pair enter an epoch. */
|
||||
node->gps_last_nav_solve_pps_counter =
|
||||
event->pps_counter;
|
||||
node->gps_last_pps_capture_sequence =
|
||||
event->pps_capture_sequence;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Capture sequencing is transport metadata. Rebase it even if the
|
||||
@@ -1310,14 +1299,10 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
if(assembly_result == GPS_NAV_EPOCH_EVENT_RESTARTED)
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_incomplete_total++;
|
||||
if(gps_mask_count(previous_received) >= MIN_NEEDED_SATELLITE)
|
||||
{
|
||||
gps_nav_publish_active_snapshot(previous_received, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The first interrupt from a newer period proves the old expected
|
||||
* set will never complete. Drop it rather than solving a partial
|
||||
* satellite set. */
|
||||
gps_nav_discard_active_snapshot(false);
|
||||
}
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"NAV capture epoch advanced: old_expected=0x%x old_received=0x%x new_counter=%lu first_trk=%u\r\n",
|
||||
previous_expected, previous_received,
|
||||
@@ -1331,6 +1316,8 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
uint32_t channel_mask = 1u << trck_ip_no;
|
||||
gps_nav_active_snapshot.channel_mask |= channel_mask;
|
||||
gps_nav_active_snapshot.prn[trck_ip_no] = event->prn;
|
||||
gps_nav_active_snapshot.ephemeris[trck_ip_no] =
|
||||
gps_frame_parsed->gps_ephems;
|
||||
gps_nav_active_snapshot.tow[trck_ip_no].week = tow_week;
|
||||
gps_nav_active_snapshot.tow[trck_ip_no].sec = tow_sec;
|
||||
gps_nav_active_snapshot.raw_sample[trck_ip_no] =
|
||||
|
||||
@@ -18,6 +18,17 @@
|
||||
_Static_assert(GPS_TRCK_IP_COUNT <= GPS_NAV_EPOCH_MAX_CANDIDATES,
|
||||
"GPS nav epoch selector capacity is smaller than the tracking channel count");
|
||||
|
||||
static unsigned int gps_nav_mask_count(uint32_t mask)
|
||||
{
|
||||
unsigned int count = 0u;
|
||||
while(mask != 0u)
|
||||
{
|
||||
count += mask & 1u;
|
||||
mask >>= 1u;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
volatile gps_acq_t* const acq0_ip = (volatile gps_acq_t*)ACQ0_IP_BASE_ADDR;
|
||||
volatile gps_acq_t* acq_ips[GPS_ACQ_IP_COUNT];
|
||||
unsigned int acq_ips_intr_lines[GPS_ACQ_IP_COUNT];
|
||||
@@ -127,7 +138,6 @@ static bool gps_snapshot_has_svid(const gps_ephemeris_t snapshot[GPS_TRCK_IP_COU
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t gps_nav_epoch_excluded_total;
|
||||
static uint32_t gps_nav_epoch_reject_total;
|
||||
|
||||
static int32_t gps_sign_extend(uint32_t value, unsigned int bits)
|
||||
@@ -507,6 +517,21 @@ static bool gps_node_has_committed_ephemeris(volatile gps_ephem_ll_t* node)
|
||||
node->data_p->ephemeris_valid;
|
||||
}
|
||||
|
||||
static bool gps_ephemeris_ready_for_position(
|
||||
const gps_ephemeris_t* eph,
|
||||
unsigned int expected_svid)
|
||||
{
|
||||
return (eph != NULL) &&
|
||||
eph->ephemeris_valid &&
|
||||
(eph->SVID == expected_svid) &&
|
||||
(eph->update == 0u) &&
|
||||
(eph->svhlth == 0u) &&
|
||||
(eph->sva < 15u) &&
|
||||
(eph->A > 0.0) &&
|
||||
(eph->ecc >= 0.0) &&
|
||||
(eph->ecc < 1.0);
|
||||
}
|
||||
|
||||
static bool gps_node_ready_for_position(volatile gps_ephem_ll_t* node)
|
||||
{
|
||||
if((node == NULL) || (node->data_p == NULL) || (node->tracking_ip_no >= GPS_TRCK_IP_COUNT))
|
||||
@@ -530,14 +555,9 @@ static bool gps_node_ready_for_position(volatile gps_ephem_ll_t* node)
|
||||
return false;
|
||||
}
|
||||
|
||||
gps_ephemeris_t* eph = (gps_ephemeris_t*)node->data_p;
|
||||
return (eph->SVID == (unsigned char)trck_ips[node->tracking_ip_no]->PRN_NUMBER) &&
|
||||
(eph->update == 0u) &&
|
||||
(eph->svhlth == 0u) &&
|
||||
(eph->sva < 15u) &&
|
||||
(eph->A > 0.0) &&
|
||||
(eph->ecc >= 0.0) &&
|
||||
(eph->ecc < 1.0);
|
||||
return gps_ephemeris_ready_for_position(
|
||||
(const gps_ephemeris_t*)node->data_p,
|
||||
trck_ips[node->tracking_ip_no]->PRN_NUMBER);
|
||||
}
|
||||
|
||||
static bool gps_channel_nav_solve_enabled(unsigned char trck_ip_no)
|
||||
@@ -655,14 +675,9 @@ static unsigned int gps_collect_completed_epoch_locked(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
volatile gps_ephem_ll_t* node =
|
||||
get_element_by_ip_number(&tracking_node, ip_no);
|
||||
gps_ephemeris_t* eph =
|
||||
(node != NULL) ? (gps_ephemeris_t*)node->data_p : NULL;
|
||||
if((node == NULL) || (eph == NULL) ||
|
||||
!gps_node_ready_for_position(node) ||
|
||||
!gps_channel_nav_solve_enabled(ip_no) ||
|
||||
(eph->SVID != epoch->prn[ip_no]) ||
|
||||
const gps_ephemeris_t* eph = &epoch->ephemeris[ip_no];
|
||||
if(!gps_ephemeris_ready_for_position(
|
||||
eph, epoch->prn[ip_no]) ||
|
||||
gps_snapshot_has_svid(snapshot, count, eph->SVID))
|
||||
{
|
||||
continue;
|
||||
@@ -710,7 +725,8 @@ static unsigned int gps_select_coherent_nav_snapshot(
|
||||
}
|
||||
|
||||
gps_nav_epoch_selection_t selection;
|
||||
if(!gps_nav_epoch_select(candidates, candidate_count,
|
||||
if(!gps_nav_epoch_validate_complete(
|
||||
candidates, candidate_count,
|
||||
GPS_NAV_ABS_SAMPLE_SYNC_WINDOW_SAMPLES,
|
||||
GPS_NAV_TOW_CLUSTER_TOLERANCE_SEC,
|
||||
&selection))
|
||||
@@ -783,7 +799,7 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
gps_nav_solution_t* solution_out)
|
||||
{
|
||||
if((ephemeris == NULL) || (solution_out == NULL) ||
|
||||
(satellite_count < MIN_NEEDED_SATELLITE))
|
||||
(satellite_count < GPS_NAV_MIN_WLS_SATELLITES))
|
||||
{
|
||||
GPS_LOG(DEBUG_TRACE, GPS_LOG_CAT_NAV, "nav solve skipped, usable satellites=%u\r\n", satellite_count);
|
||||
return false;
|
||||
@@ -1089,7 +1105,7 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
tracked_count = gps_collect_tracked_channels_locked(
|
||||
GPS_DEBUG_ENABLED(DEBUG_CHANNELS) ? tracked_prns : NULL);
|
||||
|
||||
if(ready_count < MIN_NEEDED_SATELLITE)
|
||||
if(ready_count < GPS_NAV_MIN_WLS_SATELLITES)
|
||||
{
|
||||
gps_initial_position_valid = false;
|
||||
}
|
||||
@@ -1105,6 +1121,18 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
snapshot, snapshot_ips, snapshot_pps_counters,
|
||||
snapshot_pps_sequences,
|
||||
snapshot_local_timestamps, snapshot_raw_samples);
|
||||
unsigned int completed_channel_count =
|
||||
gps_nav_mask_count(completed_epoch.channel_mask);
|
||||
if(candidate_count != completed_channel_count)
|
||||
{
|
||||
gps_nav_epoch_reject_total++;
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"immutable NAV snapshot rejected: captured=%u valid=%u mask=0x%x rejects=%u\r\n",
|
||||
completed_channel_count, candidate_count,
|
||||
completed_epoch.channel_mask,
|
||||
gps_nav_epoch_reject_total);
|
||||
candidate_count = 0u;
|
||||
}
|
||||
initial_run = !gps_initial_position_valid;
|
||||
}
|
||||
}
|
||||
@@ -1119,26 +1147,16 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
snapshot_local_timestamps, snapshot_raw_samples,
|
||||
candidate_count);
|
||||
coherent_nav_count = satellite_count;
|
||||
run_nav = satellite_count >= MIN_NEEDED_SATELLITE;
|
||||
run_nav = satellite_count >= GPS_NAV_MIN_WLS_SATELLITES;
|
||||
|
||||
if(run_nav && (satellite_count < candidate_count))
|
||||
if(!run_nav)
|
||||
{
|
||||
unsigned int excluded_count = candidate_count - satellite_count;
|
||||
gps_nav_epoch_excluded_total += excluded_count;
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"NAV coherent subset selected: candidates=%u selected=%u excluded=%u excluded_total=%u\r\n",
|
||||
candidate_count, satellite_count, excluded_count,
|
||||
gps_nav_epoch_excluded_total);
|
||||
}
|
||||
else if(!run_nav)
|
||||
{
|
||||
if(candidate_count >= MIN_NEEDED_SATELLITE)
|
||||
if(candidate_count >= GPS_NAV_MIN_WLS_SATELLITES)
|
||||
{
|
||||
gps_nav_epoch_reject_total++;
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"NAV epoch has no coherent four-satellite subset: candidates=%u best=%u rejects=%u\r\n",
|
||||
candidate_count, satellite_count,
|
||||
gps_nav_epoch_reject_total);
|
||||
"complete NAV epoch rejected as incoherent: candidates=%u rejects=%u\r\n",
|
||||
candidate_count, gps_nav_epoch_reject_total);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1210,7 +1228,7 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
"math_clock_corr=%.6lf "
|
||||
"isr_overflow=%u plic_repeat=%u drain_limit=%u coalesced=%u lost=%u "
|
||||
"pps_discontinuity=%u epoch_incomplete=%u snapshot_overwrite=%u how_reject=%u "
|
||||
"epoch_excluded=%u epoch_rejected=%u\r\n",
|
||||
"epoch_rejected=%u\r\n",
|
||||
GPS_NAV_SOLVE_PERIOD_MS,
|
||||
(unsigned long)GPS_PROFILE_CPU_HZ,
|
||||
(unsigned long)foreground_cycles,
|
||||
@@ -1237,7 +1255,6 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
runtime_diagnostics.nav_epoch_incomplete_total,
|
||||
runtime_diagnostics.nav_epoch_snapshot_overwrite_total,
|
||||
runtime_diagnostics.how_anchor_rejected_total,
|
||||
gps_nav_epoch_excluded_total,
|
||||
gps_nav_epoch_reject_total);
|
||||
(void)foreground_cycles;
|
||||
(void)nav_software_cycles;
|
||||
@@ -1380,7 +1397,6 @@ void gps_startup_initialization(unsigned int nsp_tick)
|
||||
gps_pps_init(nsp_tick);
|
||||
gps_ionoutc_valid = false;
|
||||
gps_last_ionoutc = (ionoutc_t){0};
|
||||
gps_nav_epoch_excluded_total = 0u;
|
||||
gps_nav_epoch_reject_total = 0u;
|
||||
for(unsigned int i = 0 ; i < GPS_ACQ_IP_COUNT ; i++)
|
||||
{
|
||||
|
||||
@@ -179,6 +179,62 @@ bool gps_nav_epoch_select(
|
||||
return selection->selected_count != 0u;
|
||||
}
|
||||
|
||||
bool gps_nav_epoch_validate_complete(
|
||||
const gps_nav_epoch_key_t candidates[GPS_NAV_EPOCH_MAX_CANDIDATES],
|
||||
unsigned int candidate_count,
|
||||
uint32_t sample_window,
|
||||
double tow_tolerance_sec,
|
||||
gps_nav_epoch_selection_t* selection)
|
||||
{
|
||||
if(!gps_nav_epoch_select(candidates, candidate_count, sample_window,
|
||||
tow_tolerance_sec, selection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint32_t complete_mask = (1u << candidate_count) - 1u;
|
||||
return (selection->selected_count == candidate_count) &&
|
||||
(selection->selected_mask == complete_mask);
|
||||
}
|
||||
|
||||
bool gps_nav_capture_sequence_consistent(
|
||||
uint64_t previous_counter,
|
||||
uint32_t previous_sequence,
|
||||
uint64_t current_counter,
|
||||
uint32_t current_sequence,
|
||||
uint64_t capture_period_ticks,
|
||||
uint32_t* sequence_gap)
|
||||
{
|
||||
if(sequence_gap != NULL)
|
||||
{
|
||||
*sequence_gap = 0u;
|
||||
}
|
||||
if((previous_counter == 0u) || (current_counter <= previous_counter) ||
|
||||
(capture_period_ticks == 0u))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t sequence_delta = current_sequence - previous_sequence;
|
||||
if(sequence_delta == 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint64_t counter_delta = current_counter - previous_counter;
|
||||
uint64_t expected_delta =
|
||||
(counter_delta + (capture_period_ticks / 2u)) /
|
||||
capture_period_ticks;
|
||||
if((expected_delta == 0u) || (expected_delta > UINT32_MAX) ||
|
||||
(sequence_delta != (uint32_t)expected_delta))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(sequence_gap != NULL)
|
||||
{
|
||||
*sequence_gap = sequence_delta;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void gps_nav_epoch_assembly_reset(gps_nav_epoch_assembly_t* assembly)
|
||||
{
|
||||
if(assembly == NULL)
|
||||
|
||||
@@ -6,7 +6,7 @@ TARGETS := test_imu_calibration test_gps_nav_epoch \
|
||||
test_gps_nav_epoch_coalescing test_gps_time_anchor \
|
||||
test_plic_warm_reset
|
||||
|
||||
.PHONY: all run clean
|
||||
.PHONY: all run sanitize clean
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
@@ -32,5 +32,22 @@ run: $(TARGETS)
|
||||
./test_gps_time_anchor
|
||||
./test_plic_warm_reset
|
||||
|
||||
sanitize:
|
||||
$(CC) -O1 -g -std=c11 -Wall -Wextra -Werror \
|
||||
-fsanitize=address,undefined -fno-omit-frame-pointer \
|
||||
-I$(ROOT)/header test_gps_nav_epoch.c \
|
||||
$(ROOT)/src/gps/gps_nav_epoch.c -lm \
|
||||
-o test_gps_nav_epoch_san
|
||||
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
|
||||
UBSAN_OPTIONS=halt_on_error=1 ./test_gps_nav_epoch_san
|
||||
$(CC) -O1 -g -std=c11 -Wall -Wextra -Werror \
|
||||
-fsanitize=address,undefined -fno-omit-frame-pointer \
|
||||
-I$(ROOT)/header test_gps_nav_epoch_coalescing.c \
|
||||
$(ROOT)/src/gps/gps_nav_epoch.c -lm \
|
||||
-o test_gps_nav_epoch_coalescing_san
|
||||
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \
|
||||
UBSAN_OPTIONS=halt_on_error=1 ./test_gps_nav_epoch_coalescing_san
|
||||
|
||||
clean:
|
||||
rm -f $(TARGETS) test_gps_nav_epoch_coalescing_repro
|
||||
rm -f $(TARGETS) test_gps_nav_epoch_coalescing_repro \
|
||||
test_gps_nav_epoch_san test_gps_nav_epoch_coalescing_san
|
||||
|
||||
@@ -32,6 +32,17 @@ static gps_nav_epoch_selection_t select_epoch(
|
||||
return selection;
|
||||
}
|
||||
|
||||
static bool validate_complete_epoch(
|
||||
const gps_nav_epoch_key_t* candidates,
|
||||
unsigned int count,
|
||||
gps_nav_epoch_selection_t* selection)
|
||||
{
|
||||
memset(selection, 0, sizeof(*selection));
|
||||
return gps_nav_epoch_validate_complete(
|
||||
candidates, count, SAMPLE_WINDOW,
|
||||
TOW_TOLERANCE, selection);
|
||||
}
|
||||
|
||||
static gps_nav_epoch_event_result_t push_capture(
|
||||
gps_nav_epoch_assembly_t* assembly,
|
||||
uint64_t capture_counter,
|
||||
@@ -59,8 +70,8 @@ static void local_capture_assembly_completes(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint32_t ready_mask = 0xafu;
|
||||
const unsigned int order[] = {7u, 0u, 5u, 3u, 2u, 1u};
|
||||
const uint32_t ready_mask = 0x7fu;
|
||||
const unsigned int order[] = {4u, 0u, 6u, 2u, 5u, 1u, 3u};
|
||||
const uint64_t base = 810102900000ULL;
|
||||
|
||||
for(unsigned int i = 0u ; i < sizeof(order) / sizeof(order[0]) ; i++)
|
||||
@@ -81,6 +92,52 @@ static void local_capture_assembly_completes(void)
|
||||
"coherent group exceeded its capture span");
|
||||
}
|
||||
|
||||
static void coalesced_capture_sequence_validation(void)
|
||||
{
|
||||
uint32_t gap = 0u;
|
||||
const uint64_t period = CAPTURE_PERIOD_TICKS;
|
||||
const uint64_t base = 5000000000ULL;
|
||||
|
||||
CHECK(gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base + period, 11u, period, &gap) &&
|
||||
(gap == 1u),
|
||||
"single capture sequence advance was rejected");
|
||||
CHECK(gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base + 5u * period, 15u, period, &gap) &&
|
||||
(gap == 5u),
|
||||
"coalesced newest capture did not preserve its real epoch gap");
|
||||
CHECK(gps_nav_capture_sequence_consistent(
|
||||
base, UINT32_MAX - 1u, base + 3u * period, 1u,
|
||||
period, &gap) &&
|
||||
(gap == 3u),
|
||||
"capture sequence wrap was not handled modulo uint32");
|
||||
CHECK(gps_nav_capture_sequence_consistent(
|
||||
base, UINT32_MAX, base + period, 0u, period, &gap) &&
|
||||
(gap == 1u),
|
||||
"capture sequence value zero at wrap was treated as uninitialized");
|
||||
CHECK(gps_nav_capture_sequence_consistent(
|
||||
base, 0u, base + period, 1u, period, &gap) &&
|
||||
(gap == 1u),
|
||||
"capture sequence did not continue after wrapping through zero");
|
||||
CHECK(gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base + period + period / 3u,
|
||||
11u, period, &gap),
|
||||
"bounded hardware-counter phase jitter was rejected");
|
||||
|
||||
CHECK(!gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base + period, 10u, period, &gap),
|
||||
"duplicate capture sequence entered a new epoch");
|
||||
CHECK(!gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base - 1u, 11u, period, &gap),
|
||||
"backward hardware counter was accepted");
|
||||
CHECK(!gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base + 2u * period, 11u, period, &gap),
|
||||
"counter/sequence disagreement was accepted");
|
||||
CHECK(!gps_nav_capture_sequence_consistent(
|
||||
base, 10u, base + period, 11u, 0u, &gap),
|
||||
"zero capture period was accepted");
|
||||
}
|
||||
|
||||
static void missing_channel_expires_as_usable_subset(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
@@ -99,7 +156,8 @@ static void missing_channel_expires_as_usable_subset(void)
|
||||
}
|
||||
CHECK(!gps_nav_epoch_assembly_complete(&assembly),
|
||||
"missing ready channel completed before holdoff");
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
CHECK(gps_nav_epoch_assembly_usable(
|
||||
&assembly, GPS_NAV_MIN_WLS_SATELLITES),
|
||||
"five-channel partial epoch was not usable");
|
||||
CHECK(!gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks - 1u),
|
||||
@@ -109,6 +167,32 @@ static void missing_channel_expires_as_usable_subset(void)
|
||||
"epoch did not expire at its deadline");
|
||||
}
|
||||
|
||||
static void incomplete_epoch_never_becomes_complete(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
const uint32_t ready_mask = 0x1fu;
|
||||
const uint64_t base = 950000000ULL;
|
||||
|
||||
for(unsigned int channel = 0u ; channel < 4u ; channel++)
|
||||
{
|
||||
CHECK(push_capture(&assembly,
|
||||
base + (uint64_t)channel * 100000u,
|
||||
250000u + channel,
|
||||
channel, ready_mask) ==
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"four-channel partial epoch rejected a coherent channel");
|
||||
}
|
||||
CHECK(gps_nav_epoch_assembly_usable(
|
||||
&assembly, GPS_NAV_MIN_WLS_SATELLITES),
|
||||
"four coherent channels must remain algebraically solvable");
|
||||
CHECK(!gps_nav_epoch_assembly_complete(&assembly),
|
||||
"timed-out subset incorrectly completed the expected epoch");
|
||||
CHECK(gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks),
|
||||
"incomplete epoch did not reach its discard deadline");
|
||||
}
|
||||
|
||||
static void local_outlier_cannot_poison_epoch(void)
|
||||
{
|
||||
gps_nav_epoch_assembly_t assembly;
|
||||
@@ -213,8 +297,12 @@ static void randomized_capture_liveness(void)
|
||||
(result == GPS_NAV_EPOCH_EVENT_COMPLETE),
|
||||
"random coherent capture was rejected");
|
||||
}
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"random stream lost four-channel liveness");
|
||||
CHECK(gps_nav_epoch_assembly_usable(
|
||||
&assembly, GPS_NAV_MIN_WLS_SATELLITES),
|
||||
"random stream lost algebraic four-channel liveness");
|
||||
CHECK(gps_nav_epoch_assembly_complete(&assembly) ==
|
||||
(delivered_count == ready_count),
|
||||
"random partial epoch claimed all expected channels");
|
||||
if(delivered_count != ready_count)
|
||||
{
|
||||
CHECK(gps_nav_epoch_assembly_expired(
|
||||
@@ -238,6 +326,8 @@ static void all_coherent_across_sample_wrap(void)
|
||||
"sample/week wrap split a coherent epoch");
|
||||
CHECK(selection.selected_mask == 0x0fu,
|
||||
"sample/week wrap selected the wrong rows");
|
||||
CHECK(validate_complete_epoch(candidates, 4u, &selection),
|
||||
"fully coherent week/sample-wrap epoch was rejected");
|
||||
}
|
||||
|
||||
static void tow_outlier_does_not_block_good_cluster(void)
|
||||
@@ -254,6 +344,10 @@ static void tow_outlier_does_not_block_good_cluster(void)
|
||||
"live 45.6-hour TOW outlier blocked the good cluster");
|
||||
CHECK(selection.selected_mask == 0x1bu,
|
||||
"live TOW outlier was not excluded");
|
||||
CHECK(!validate_complete_epoch(candidates, 5u, &selection),
|
||||
"TOW outlier incorrectly allowed a partial receiver epoch");
|
||||
CHECK(selection.selected_count == 4u,
|
||||
"strict rejection lost diagnostic largest-cluster metadata");
|
||||
}
|
||||
|
||||
static void newest_sample_outlier_does_not_define_the_cluster(void)
|
||||
@@ -272,6 +366,8 @@ static void newest_sample_outlier_does_not_define_the_cluster(void)
|
||||
"newest raw-sample outlier displaced the largest cluster");
|
||||
CHECK(selection.selected_mask == 0x3fu,
|
||||
"newest raw-sample outlier was not excluded");
|
||||
CHECK(!validate_complete_epoch(candidates, 7u, &selection),
|
||||
"sample outlier incorrectly allowed a partial receiver epoch");
|
||||
}
|
||||
|
||||
static void equal_clusters_prefer_newer_epoch(void)
|
||||
@@ -291,6 +387,8 @@ static void equal_clusters_prefer_newer_epoch(void)
|
||||
"equal epoch clusters returned the wrong size");
|
||||
CHECK(selection.selected_mask == 0xf0u,
|
||||
"equal epoch clusters did not prefer the newer epoch");
|
||||
CHECK(!validate_complete_epoch(candidates, 8u, &selection),
|
||||
"two complete-looking epoch clusters were mixed");
|
||||
}
|
||||
|
||||
static void nonfinite_candidate_is_excluded(void)
|
||||
@@ -307,12 +405,16 @@ static void nonfinite_candidate_is_excluded(void)
|
||||
"invalid TOW candidate blocked valid measurements");
|
||||
CHECK(selection.selected_mask == 0x0fu,
|
||||
"invalid TOW candidate was selected");
|
||||
CHECK(!validate_complete_epoch(candidates, 5u, &selection),
|
||||
"nonfinite TOW incorrectly allowed a partial receiver epoch");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
local_capture_assembly_completes();
|
||||
coalesced_capture_sequence_validation();
|
||||
missing_channel_expires_as_usable_subset();
|
||||
incomplete_epoch_never_becomes_complete();
|
||||
local_outlier_cannot_poison_epoch();
|
||||
duplicate_join_and_disconnect_are_bounded();
|
||||
randomized_capture_liveness();
|
||||
|
||||
@@ -59,8 +59,11 @@ static void live_fault_recovery(void)
|
||||
((1u << 0) | (1u << 2) | (1u << 3) |
|
||||
(1u << 4) | (1u << 7)),
|
||||
"live capture mask differs from the expected five channels");
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"live five-channel group was not usable");
|
||||
CHECK(gps_nav_epoch_assembly_usable(
|
||||
&assembly, GPS_NAV_MIN_WLS_SATELLITES),
|
||||
"live five-channel group lost its diagnostic measurements");
|
||||
CHECK(!gps_nav_epoch_assembly_complete(&assembly),
|
||||
"missing ready channel produced a publishable raw epoch");
|
||||
CHECK(gps_nav_epoch_assembly_expired(
|
||||
&assembly, assembly.deadline_local_ticks),
|
||||
"missing channel did not release the live group at holdoff");
|
||||
@@ -83,8 +86,8 @@ static void overwritten_mailbox_remains_bounded(void)
|
||||
gps_nav_epoch_assembly_reset(&assembly);
|
||||
uint64_t first_epoch = 900000000000ULL;
|
||||
|
||||
/* Channel 4's first mailbox value is overwritten. The other five still
|
||||
* form a usable bounded snapshot instead of waiting indefinitely. */
|
||||
/* Channel 4's first mailbox value is overwritten. The other five remain
|
||||
* bounded diagnostics, but must never become a publishable raw epoch. */
|
||||
for(unsigned int channel = 0u ; channel < 6u ; channel++)
|
||||
{
|
||||
if(channel == 4u)
|
||||
@@ -96,8 +99,11 @@ static void overwritten_mailbox_remains_bounded(void)
|
||||
GPS_NAV_EPOCH_EVENT_ACCEPTED,
|
||||
"partial mailbox epoch rejected");
|
||||
}
|
||||
CHECK(gps_nav_epoch_assembly_usable(&assembly, 4u),
|
||||
"mailbox overwrite blocked the usable subset");
|
||||
CHECK(gps_nav_epoch_assembly_usable(
|
||||
&assembly, GPS_NAV_MIN_WLS_SATELLITES),
|
||||
"mailbox overwrite lost the received diagnostic subset");
|
||||
CHECK(!gps_nav_epoch_assembly_complete(&assembly),
|
||||
"overwritten mailbox produced a partial raw epoch");
|
||||
|
||||
uint32_t previous_mask = assembly.received_mask;
|
||||
CHECK(push(&assembly, first_epoch + PERIOD_TICKS,
|
||||
@@ -105,7 +111,7 @@ static void overwritten_mailbox_remains_bounded(void)
|
||||
GPS_NAV_EPOCH_EVENT_RESTARTED,
|
||||
"new mailbox epoch did not advance");
|
||||
CHECK(previous_mask == 0x2fu,
|
||||
"partial mailbox mask was not preserved for publication");
|
||||
"partial mailbox diagnostic mask changed before discard");
|
||||
|
||||
for(unsigned int channel = 1u ; channel < 6u ; channel++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user