feat(pps): preserve loop dynamics across nav periods

This commit is contained in:
2026-07-29 00:45:16 +03:00
parent a6bb080270
commit 347e1c671d
19 changed files with 889 additions and 111 deletions
@@ -55,6 +55,7 @@
#endif
#define GPS_NAV_BITS_PER_WEEK 30240000u
#define GPS_NAV_SOLVE_PERIOD_MS (GPS_NAV_SOLVE_TIMER_TICKS * GPS_NAV_SOLVE_TIMER_TICK_MS)
#define GPS_NAV_BITS_PER_SUBFRAME 300u
/* Keep integrity statistics active, but do not let leave-one-out FDE
* replace the full-WLS position until the real measurement-noise model
* is calibrated. The live stationary test produced a 22.7% exclusion
@@ -83,6 +84,8 @@
#error "GPS_NAV_SOLVE_TIMER_TICKS must be non-zero"
#elif ((GPS_NAV_BITS_PER_WEEK % GPS_NAV_SOLVE_TIMER_TICKS) != 0u)
#error "GPS_NAV_SOLVE_TIMER_TICKS must divide 30240000 GPS navigation bits per week"
#elif ((GPS_NAV_BITS_PER_SUBFRAME % GPS_NAV_SOLVE_TIMER_TICKS) != 0u)
#error "GPS_NAV_SOLVE_TIMER_TICKS must divide the 300-bit LNAV subframe"
#endif
_Static_assert((GPS_NAV_ENABLE_FDE == 0u) || (GPS_NAV_ENABLE_FDE == 1u),
"GPS_NAV_ENABLE_FDE must be zero or one");
@@ -2,6 +2,9 @@
#define GPS_PPS_QUALITY_H
#include <stdbool.h>
#include <stdint.h>
#define GPS_PPS_BASELINE_UPDATE_PERIOD_SEC 0.200
typedef enum {
GPS_PPS_PROFILE_HOLDOVER = 0,
@@ -29,6 +32,8 @@ typedef struct {
gps_pps_profile_t profile;
gps_pps_profile_t candidate;
unsigned int candidate_updates;
unsigned int upgrade_updates;
unsigned int degrade_updates;
} gps_pps_quality_selector_t;
typedef struct {
@@ -43,13 +48,17 @@ typedef struct {
} gps_pps_loop_profile_t;
void gps_pps_quality_selector_init(gps_pps_quality_selector_t* selector);
void gps_pps_quality_selector_init_period(
gps_pps_quality_selector_t* selector,
double update_period_sec);
/*
* Select a bounded loop profile from timing geometry, code/carrier quality
* and integrity. Upgrades require sustained evidence and degradations require
* three consecutive observations. A single rejected WLS epoch is skipped
* without resetting the loop; sustained loss enters holdover, while the
* independent reference watchdog remains the final stale-data guard.
* and integrity. Upgrades and degradations require sustained evidence; their
* update counts are scaled from the proven 200 ms timing when period-aware
* initialization is used. A single rejected WLS epoch is skipped without
* resetting the loop; sustained loss enters holdover, while the independent
* reference watchdog remains the final stale-data guard.
*/
gps_pps_profile_t gps_pps_quality_selector_update(
gps_pps_quality_selector_t* selector,
@@ -57,6 +66,22 @@ gps_pps_profile_t gps_pps_quality_selector_update(
bool* profile_changed_out);
gps_pps_loop_profile_t gps_pps_loop_profile(gps_pps_profile_t profile);
gps_pps_loop_profile_t gps_pps_loop_profile_for_period(
gps_pps_profile_t profile,
double update_period_sec);
/*
* Map the proven 200 ms discrete poles to another update period. The pole
* exponentiation preserves the current loop's continuous-time settling and
* damping while returning exactly the legacy coefficients at 200 ms.
*/
bool gps_pps_loop_coefficients(
gps_pps_loop_profile_t parameters,
double update_period_sec,
double clock_frequency_hz,
int64_t* kp_q46_out,
int64_t* ki_q46_out,
uint32_t* max_slew_q32_out);
const char* gps_pps_profile_name(gps_pps_profile_t profile);
#endif
@@ -58,7 +58,9 @@ bool gps_pps_reference_bias_drift(
* linear oscillator drift. The Doppler WLS clock-drift state predicts the
* bias at the new capture epoch; only the residual bias noise is low-passed.
* If Doppler is temporarily unavailable, a bounded, slower finite-difference
* drift estimate keeps the filter usable in the robust profile.
* drift estimate keeps the filter usable in the robust profile. All three
* baseline per-update gains are converted from the actual elapsed time, so
* changing the NAV solve period does not change their real-time poles.
*
* COARSE is returned during initial settling, a reference-datum change, or
* after a gap too long for bounded Doppler prediction. The independent