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
@@ -91,14 +91,11 @@ static void profile_coefficients(
int64_t* ki_q46,
int64_t* max_slew_q32)
{
const double pi = 3.14159265358979323846;
double wn = 2.0 * pi * profile.natural_frequency_hz;
*kp_q46 = (int64_t)llround(
(2.0 * profile.damping * wn / TRACKING_HZ) * Q46_SCALE);
*ki_q46 = (int64_t)llround(
(wn * wn * UPDATE_PERIOD_SEC / TRACKING_HZ) * Q46_SCALE);
*max_slew_q32 = (int64_t)llround(
profile.max_slew_ppm * 1.0e-6 * Q32_SCALE);
uint32_t max_slew = 0u;
assert(gps_pps_loop_coefficients(
profile, UPDATE_PERIOD_SEC, TRACKING_HZ,
kp_q46, ki_q46, &max_slew));
*max_slew_q32 = max_slew;
}
static void synthetic_rtl_coarse(synthetic_rtl_t* rtl, int64_t reference_q32)
@@ -107,6 +107,69 @@ static void test_profile_parameters_are_ordered(void)
assert(robust.lock_release_ticks >= precision.lock_release_ticks);
}
static void test_period_scaled_hysteresis_and_settling(void)
{
gps_pps_quality_selector_t selector;
gps_pps_quality_selector_init_period(&selector, 0.5);
assert(selector.upgrade_updates == 4u);
assert(selector.degrade_updates == 2u);
gps_pps_loop_profile_t precision =
gps_pps_loop_profile_for_period(GPS_PPS_PROFILE_PRECISION, 0.5);
gps_pps_loop_profile_t robust =
gps_pps_loop_profile_for_period(GPS_PPS_PROFILE_ROBUST, 0.5);
assert(precision.lock_acquire_updates == 2u);
assert(precision.software_settle_updates == 5u);
assert(robust.lock_acquire_updates == 4u);
assert(robust.software_settle_updates == 7u);
gps_pps_quality_selector_init_period(&selector, 1.0);
assert(selector.upgrade_updates == 2u);
assert(selector.degrade_updates == 2u);
precision = gps_pps_loop_profile_for_period(
GPS_PPS_PROFILE_PRECISION, 1.0);
robust = gps_pps_loop_profile_for_period(
GPS_PPS_PROFILE_ROBUST, 1.0);
assert(precision.lock_acquire_updates == 2u);
assert(precision.software_settle_updates == 3u);
assert(robust.lock_acquire_updates == 2u);
assert(robust.software_settle_updates == 4u);
}
static void assert_coefficients(
gps_pps_profile_t profile,
double period_sec,
int64_t expected_kp,
int64_t expected_ki)
{
int64_t kp = 0;
int64_t ki = 0;
uint32_t max_slew = 0u;
gps_pps_loop_profile_t parameters =
gps_pps_loop_profile_for_period(profile, period_sec);
assert(gps_pps_loop_coefficients(
parameters, period_sec, 125000000.0,
&kp, &ki, &max_slew));
assert(kp == expected_kp);
assert(ki == expected_ki);
assert(max_slew == 85899u);
}
static void test_period_scaled_discrete_coefficients(void)
{
/* The production 200 ms coefficients remain bit-for-bit unchanged. */
assert_coefficients(GPS_PPS_PROFILE_ACQUIRE, 0.2, 764018, 64006);
assert_coefficients(GPS_PPS_PROFILE_PRECISION, 0.2, 222838, 5445);
assert_coefficients(GPS_PPS_PROFILE_ROBUST, 0.2, 84891, 640);
/* Other cadences exponentiate those proven discrete poles in time. */
assert_coefficients(GPS_PPS_PROFILE_ACQUIRE, 0.5, 615780, 127492);
assert_coefficients(GPS_PPS_PROFILE_PRECISION, 0.5, 209783, 12804);
assert_coefficients(GPS_PPS_PROFILE_ROBUST, 0.5, 82980, 1564);
assert_coefficients(GPS_PPS_PROFILE_ACQUIRE, 1.0, 447388, 178038);
assert_coefficients(GPS_PPS_PROFILE_PRECISION, 1.0, 190239, 23149);
assert_coefficients(GPS_PPS_PROFILE_ROBUST, 1.0, 79923, 3011);
}
static void test_quality_boundaries_and_recovery(void)
{
gps_pps_quality_selector_t selector;
@@ -167,6 +230,8 @@ int main(void)
test_hysteretic_profile_transitions();
test_alternating_quality_does_not_upgrade();
test_profile_parameters_are_ordered();
test_period_scaled_hysteresis_and_settling();
test_period_scaled_discrete_coefficients();
test_quality_boundaries_and_recovery();
puts("gps_pps_quality tests passed");
return 0;
@@ -6,6 +6,30 @@
#define COUNTER_HZ 125000000.0
static void test_elapsed_time_gain_preserves_filter_poles(void)
{
gps_pps_time_filter_t filter;
gps_pps_time_filter_init(&filter);
double filtered = 0.0;
double innovation = 0.0;
assert(gps_pps_time_filter_update(
&filter, 0u, COUNTER_HZ, 0.0, true, 0.0, 1.0,
1.0 / 32.0, 1u, &filtered, &innovation) ==
GPS_PPS_TIME_FILTER_COARSE);
assert(gps_pps_time_filter_update(
&filter, (uint64_t)COUNTER_HZ, COUNTER_HZ, 100.0, true, 100.0,
1.0, 1.0 / 32.0, 1u, &filtered, &innovation) ==
GPS_PPS_TIME_FILTER_TRACKING);
/* One 1 s update has exactly the pole of five baseline 200 ms updates. */
double expected_bias_gain = 1.0 - pow(1.0 - 1.0 / 32.0, 5.0);
double expected_drift_gain = 1.0 - pow(1.0 - 1.0 / 4.0, 5.0);
assert(fabs(filtered - 100.0 * expected_bias_gain) < 1.0e-12);
assert(fabs(filter.clock_drift_mps - 100.0 * expected_drift_gain) <
1.0e-12);
}
static void test_drift_prediction_rejects_bias_noise(void)
{
static const double noise_m[] = {
@@ -298,6 +322,7 @@ static void test_reference_selection_is_stable(void)
int main(void)
{
test_elapsed_time_gain_preserves_filter_poles();
test_drift_prediction_rejects_bias_noise();
test_quality_gap_and_outlier_handling();
test_bias_ramp_without_doppler_uses_bounded_fallback();