feat(gps): split Doppler paths for ESKF and PPS
This commit is contained in:
@@ -27,12 +27,13 @@ void gps_system_uart_publish_location(
|
||||
GPS_LOG(DEBUG_STATUS, GPS_LOG_CAT_LOCATION,
|
||||
"source=%s lat=%.20lf lon=%.20lf height=%.3lf "
|
||||
"gdop=%.10lf pdop=%.10lf hdop=%.10lf vdop=%.10lf tdop=%.10lf "
|
||||
"tracked=%u used=%u velocity_valid=%u speed=%.3lf "
|
||||
"tracked=%u used=%u velocity_valid=%u velocity_fde=%u speed=%.3lf "
|
||||
"velocity_rms=%.3lf\r\n",
|
||||
source, latitude_deg, longitude_deg, height_m,
|
||||
gps->gdop, gps->pdop, gps->hdop, gps->vdop, gps->tdop,
|
||||
gps->tracked_satellite_count, gps->used_satellite_count,
|
||||
gps->velocity_valid ? 1u : 0u,
|
||||
gps->velocity_fde_applied ? 1u : 0u,
|
||||
gps->speed_mps, gps->velocity_residual_rms_mps);
|
||||
}
|
||||
|
||||
|
||||
@@ -156,13 +156,13 @@ static void gps_update_tracking_measurement_isr(
|
||||
* A 0.05 pole at the 50 Hz navigation-bit cadence has an approximately
|
||||
* 0.4 s time constant while retaining real receiver dynamics.
|
||||
*/
|
||||
measurement.doppler_hz =
|
||||
measurement.filtered_doppler_hz =
|
||||
GPS_TRACK_DOPPLER_SMOOTH_ALPHA * measurement.doppler_hz +
|
||||
(1.0 - GPS_TRACK_DOPPLER_SMOOTH_ALPHA) *
|
||||
previous->doppler_hz;
|
||||
measurement.pseudorange_rate_mps =
|
||||
previous->filtered_doppler_hz;
|
||||
measurement.filtered_pseudorange_rate_mps =
|
||||
gps_tracking_doppler_to_range_rate_mps(
|
||||
measurement.doppler_hz);
|
||||
measurement.filtered_doppler_hz);
|
||||
}
|
||||
measurement.local_timestamp_ticks = local_timestamp_ticks;
|
||||
gps_latest_tracking_measurement[tracking_index] = measurement;
|
||||
|
||||
@@ -853,6 +853,9 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
bool ok = false;
|
||||
bool pps_clock_bias_drift_valid = false;
|
||||
double pps_clock_bias_drift_mps = 0.0;
|
||||
bool pps_velocity_valid = false;
|
||||
double pps_velocity_residual_rms_mps = 0.0;
|
||||
uint32_t pps_velocity_excluded_row_mask = 0u;
|
||||
gps_nav_solution_t solution;
|
||||
memset(&solution, 0, sizeof(solution));
|
||||
memset(solution_out, 0, sizeof(*solution_out));
|
||||
@@ -954,6 +957,10 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
solution.doppler_hz[row] = measurements[row].doppler_hz;
|
||||
solution.pseudorange_rate_mps[row] =
|
||||
measurements[row].pseudorange_rate_mps;
|
||||
solution.filtered_doppler_hz[row] =
|
||||
measurements[row].filtered_doppler_hz;
|
||||
solution.filtered_pseudorange_rate_mps[row] =
|
||||
measurements[row].filtered_pseudorange_rate_mps;
|
||||
}
|
||||
if(ok && solution.solution_valid)
|
||||
{
|
||||
@@ -976,7 +983,7 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
velocity_measurements[row].valid = false;
|
||||
}
|
||||
}
|
||||
solution.velocity_valid = gps_tracking_velocity_wls(
|
||||
solution.velocity_valid = gps_tracking_velocity_wls_robust(
|
||||
solution.ecef_m,
|
||||
satellite_positions_array,
|
||||
satellite_velocities_array,
|
||||
@@ -986,7 +993,10 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
solution.velocity_ecef_mps,
|
||||
solution.velocity_covariance_ecef_mps2,
|
||||
&solution.clock_drift_mps,
|
||||
&solution.velocity_residual_rms_mps);
|
||||
&solution.velocity_residual_rms_mps,
|
||||
&solution.velocity_excluded_row_mask);
|
||||
solution.velocity_fde_applied =
|
||||
solution.velocity_excluded_row_mask != 0u;
|
||||
if(solution.velocity_valid)
|
||||
{
|
||||
solution.speed_mps =
|
||||
@@ -996,15 +1006,57 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
solution.velocity_ecef_mps[1] +
|
||||
solution.velocity_ecef_mps[2] *
|
||||
solution.velocity_ecef_mps[2]);
|
||||
}
|
||||
|
||||
/*
|
||||
* PPS keeps the existing slow Doppler path as a low-noise derivative
|
||||
* predictor for the position-WLS reference datum. This result is not
|
||||
* published to fusion; fusion receives the raw robust solution above.
|
||||
*/
|
||||
for(unsigned int row = 0u; row < satellite_count; row++)
|
||||
{
|
||||
velocity_measurements[row].doppler_hz =
|
||||
measurements[row].filtered_doppler_hz;
|
||||
velocity_measurements[row].pseudorange_rate_mps =
|
||||
measurements[row].filtered_pseudorange_rate_mps;
|
||||
}
|
||||
double pps_velocity_ecef_mps[3];
|
||||
double pps_velocity_covariance_ecef_mps2[3][3];
|
||||
double pps_receiver_clock_drift_mps = 0.0;
|
||||
pps_velocity_valid = gps_tracking_velocity_wls_robust(
|
||||
solution.ecef_m,
|
||||
satellite_positions_array,
|
||||
satellite_velocities_array,
|
||||
clockDrift,
|
||||
velocity_measurements,
|
||||
satellite_count,
|
||||
pps_velocity_ecef_mps,
|
||||
pps_velocity_covariance_ecef_mps2,
|
||||
&pps_receiver_clock_drift_mps,
|
||||
&pps_velocity_residual_rms_mps,
|
||||
&pps_velocity_excluded_row_mask);
|
||||
if(pps_velocity_valid)
|
||||
{
|
||||
pps_clock_bias_drift_valid =
|
||||
gps_pps_reference_bias_drift(
|
||||
solution.ecef_m,
|
||||
solution.velocity_ecef_mps,
|
||||
pps_velocity_ecef_mps,
|
||||
satellite_positions_array[reference_index],
|
||||
satellite_velocities_array[reference_index],
|
||||
clockDrift[reference_index],
|
||||
&pps_clock_bias_drift_mps);
|
||||
}
|
||||
if(solution.velocity_fde_applied ||
|
||||
(pps_velocity_excluded_row_mask != 0u))
|
||||
{
|
||||
GPS_LOG(DEBUG_EVENTS, GPS_LOG_CAT_NAV,
|
||||
"velocity FDE raw_mask=0x%02lx pps_mask=0x%02lx "
|
||||
"raw_rms=%.3lf pps_rms=%.3lf\r\n",
|
||||
(unsigned long)solution.velocity_excluded_row_mask,
|
||||
(unsigned long)pps_velocity_excluded_row_mask,
|
||||
solution.velocity_residual_rms_mps,
|
||||
pps_velocity_residual_rms_mps);
|
||||
}
|
||||
}
|
||||
if(ok && solution.fde_applied)
|
||||
{
|
||||
@@ -1056,12 +1108,13 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
(cn0_count != 0u) ? minimum_cn0 : NAN;
|
||||
pps_quality.chi_square_ratio = solution.chi_square_ratio;
|
||||
pps_quality.velocity_residual_rms_mps =
|
||||
solution.velocity_valid ?
|
||||
solution.velocity_residual_rms_mps : 0.0;
|
||||
pps_velocity_valid ? pps_velocity_residual_rms_mps : 0.0;
|
||||
pps_quality.used_satellites = solution.used_satellite_count;
|
||||
pps_quality.residual_test_valid = solution.residual_test_valid;
|
||||
pps_quality.velocity_valid = solution.velocity_valid;
|
||||
pps_quality.fde_applied = solution.fde_applied;
|
||||
pps_quality.velocity_valid = pps_velocity_valid;
|
||||
pps_quality.fde_applied =
|
||||
solution.fde_applied ||
|
||||
(pps_velocity_excluded_row_mask != 0u);
|
||||
}
|
||||
bool pps_profile_changed = false;
|
||||
/*
|
||||
@@ -1141,7 +1194,8 @@ static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
"drift=%.3lf tdop=%.2lf\r\n",
|
||||
gps_pps_profile_name(pps_profile),
|
||||
solution.clock_bias_m,
|
||||
solution.clock_drift_mps,
|
||||
pps_clock_bias_drift_valid ?
|
||||
pps_clock_bias_drift_mps : NAN,
|
||||
solution.tdop);
|
||||
goto pps_anchor_done;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#define GPS_TRACK_MEASUREMENT_MIN_SATELLITES 4u
|
||||
#define GPS_TRACK_MEASUREMENT_MAX_SATELLITES GPS_TRCK_IP_COUNT
|
||||
#define GPS_TRACK_MEASUREMENT_EPSILON 1.0e-12
|
||||
#define GPS_TRACK_VELOCITY_FDE_MIN_SATELLITES 6u
|
||||
#define GPS_TRACK_VELOCITY_FDE_SIGMA_GATE 3.5
|
||||
|
||||
static double clamp_double(double value, double minimum, double maximum)
|
||||
{
|
||||
@@ -135,6 +137,10 @@ bool gps_tracking_measurement_read(
|
||||
measurement_out->pseudorange_rate_mps =
|
||||
gps_tracking_doppler_to_range_rate_mps(
|
||||
measurement_out->doppler_hz);
|
||||
measurement_out->filtered_doppler_hz =
|
||||
measurement_out->doppler_hz;
|
||||
measurement_out->filtered_pseudorange_rate_mps =
|
||||
measurement_out->pseudorange_rate_mps;
|
||||
|
||||
unsigned int version =
|
||||
meta >> GPS_TRCK_MEAS_META_VERSION_SHIFT;
|
||||
@@ -149,7 +155,9 @@ bool gps_tracking_measurement_read(
|
||||
(measurement_out->cn0_sample_count == 20u) &&
|
||||
cn0_valid &&
|
||||
isfinite(measurement_out->doppler_hz) &&
|
||||
isfinite(measurement_out->pseudorange_rate_mps);
|
||||
isfinite(measurement_out->pseudorange_rate_mps) &&
|
||||
isfinite(measurement_out->filtered_doppler_hz) &&
|
||||
isfinite(measurement_out->filtered_pseudorange_rate_mps);
|
||||
return measurement_out->valid;
|
||||
}
|
||||
|
||||
@@ -261,7 +269,94 @@ static bool invert_four_by_four(double input[4][4], double inverse[4][4])
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gps_tracking_velocity_wls(
|
||||
typedef struct {
|
||||
double solution[4];
|
||||
double normal_inverse[4][4];
|
||||
double residual_mps[GPS_TRACK_MEASUREMENT_MAX_SATELLITES];
|
||||
double residual_square_sum;
|
||||
double weighted_residual_square_sum;
|
||||
unsigned int row_count;
|
||||
} gps_velocity_wls_candidate_t;
|
||||
|
||||
static bool gps_velocity_solve_candidate(
|
||||
const double design[][4],
|
||||
const double observation[],
|
||||
const double weights[],
|
||||
unsigned int used,
|
||||
unsigned int excluded_row,
|
||||
gps_velocity_wls_candidate_t* candidate_out)
|
||||
{
|
||||
if((design == NULL) || (observation == NULL) || (weights == NULL) ||
|
||||
(candidate_out == NULL) ||
|
||||
(used < GPS_TRACK_MEASUREMENT_MIN_SATELLITES) ||
|
||||
(used > GPS_TRACK_MEASUREMENT_MAX_SATELLITES))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double normal[4][4] = {{0.0}};
|
||||
double rhs[4] = {0.0};
|
||||
unsigned int selected = 0u;
|
||||
for(unsigned int measurement_row = 0u;
|
||||
measurement_row < used;
|
||||
measurement_row++)
|
||||
{
|
||||
if(measurement_row == excluded_row)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double weight = weights[measurement_row];
|
||||
for(unsigned int row = 0u; row < 4u; row++)
|
||||
{
|
||||
rhs[row] += weight * design[measurement_row][row] *
|
||||
observation[measurement_row];
|
||||
for(unsigned int column = 0u; column < 4u; column++)
|
||||
{
|
||||
normal[row][column] +=
|
||||
weight * design[measurement_row][row] *
|
||||
design[measurement_row][column];
|
||||
}
|
||||
}
|
||||
selected++;
|
||||
}
|
||||
if(selected < GPS_TRACK_MEASUREMENT_MIN_SATELLITES)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(candidate_out, 0, sizeof(*candidate_out));
|
||||
if(!solve_four_by_four(normal, rhs, candidate_out->solution) ||
|
||||
!invert_four_by_four(normal, candidate_out->normal_inverse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(unsigned int measurement_row = 0u;
|
||||
measurement_row < used;
|
||||
measurement_row++)
|
||||
{
|
||||
candidate_out->residual_mps[measurement_row] = NAN;
|
||||
if(measurement_row == excluded_row)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double predicted = 0.0;
|
||||
for(unsigned int column = 0u; column < 4u; column++)
|
||||
{
|
||||
predicted += design[measurement_row][column] *
|
||||
candidate_out->solution[column];
|
||||
}
|
||||
double residual = observation[measurement_row] - predicted;
|
||||
candidate_out->residual_mps[measurement_row] = residual;
|
||||
candidate_out->residual_square_sum += residual * residual;
|
||||
candidate_out->weighted_residual_square_sum +=
|
||||
weights[measurement_row] * residual * residual;
|
||||
}
|
||||
candidate_out->row_count = selected;
|
||||
return isfinite(candidate_out->residual_square_sum) &&
|
||||
isfinite(candidate_out->weighted_residual_square_sum);
|
||||
}
|
||||
|
||||
static bool gps_tracking_velocity_wls_internal(
|
||||
const double receiver_ecef_m[3],
|
||||
const double satellite_positions_m[][3],
|
||||
const double satellite_velocities_mps[][3],
|
||||
@@ -271,8 +366,14 @@ bool gps_tracking_velocity_wls(
|
||||
double receiver_velocity_ecef_mps_out[3],
|
||||
double receiver_velocity_covariance_ecef_mps2_out[3][3],
|
||||
double* receiver_clock_drift_mps_out,
|
||||
double* residual_rms_mps_out)
|
||||
double* residual_rms_mps_out,
|
||||
bool robust,
|
||||
uint32_t* excluded_measurement_mask_out)
|
||||
{
|
||||
if(excluded_measurement_mask_out != NULL)
|
||||
{
|
||||
*excluded_measurement_mask_out = 0u;
|
||||
}
|
||||
if((receiver_ecef_m == NULL) || (satellite_positions_m == NULL) ||
|
||||
(satellite_velocities_mps == NULL) ||
|
||||
(satellite_clock_drifts_sps == NULL) ||
|
||||
@@ -287,11 +388,10 @@ bool gps_tracking_velocity_wls(
|
||||
return false;
|
||||
}
|
||||
|
||||
double normal[4][4] = {{0.0}};
|
||||
double rhs[4] = {0.0};
|
||||
double design[GPS_TRACK_MEASUREMENT_MAX_SATELLITES][4];
|
||||
double observation[GPS_TRACK_MEASUREMENT_MAX_SATELLITES];
|
||||
double weights[GPS_TRACK_MEASUREMENT_MAX_SATELLITES];
|
||||
unsigned int measurement_index[GPS_TRACK_MEASUREMENT_MAX_SATELLITES];
|
||||
unsigned int used = 0u;
|
||||
|
||||
for(unsigned int satellite = 0u; satellite < count; satellite++)
|
||||
@@ -342,17 +442,7 @@ bool gps_tracking_velocity_wls(
|
||||
sigma_rate_mps = clamp_double(sigma_rate_mps, 0.05, 5.0);
|
||||
double weight = 1.0 / (sigma_rate_mps * sigma_rate_mps);
|
||||
weights[used] = weight;
|
||||
for(unsigned int row = 0u; row < 4u; row++)
|
||||
{
|
||||
rhs[row] +=
|
||||
weight * design[used][row] * observation[used];
|
||||
for(unsigned int column = 0u; column < 4u; column++)
|
||||
{
|
||||
normal[row][column] +=
|
||||
weight * design[used][row] *
|
||||
design[used][column];
|
||||
}
|
||||
}
|
||||
measurement_index[used] = satellite;
|
||||
used++;
|
||||
}
|
||||
|
||||
@@ -361,27 +451,47 @@ bool gps_tracking_velocity_wls(
|
||||
return false;
|
||||
}
|
||||
|
||||
double solution[4];
|
||||
double normal_inverse[4][4];
|
||||
if(!solve_four_by_four(normal, rhs, solution) ||
|
||||
!invert_four_by_four(normal, normal_inverse))
|
||||
gps_velocity_wls_candidate_t full;
|
||||
if(!gps_velocity_solve_candidate(
|
||||
design, observation, weights, used,
|
||||
GPS_TRACK_MEASUREMENT_MAX_SATELLITES, &full))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double residual_square_sum = 0.0;
|
||||
double weighted_residual_square_sum = 0.0;
|
||||
for(unsigned int row = 0u; row < used; row++)
|
||||
gps_velocity_wls_candidate_t selected = full;
|
||||
unsigned int excluded_row = GPS_TRACK_MEASUREMENT_MAX_SATELLITES;
|
||||
if(robust && (used >= GPS_TRACK_VELOCITY_FDE_MIN_SATELLITES))
|
||||
{
|
||||
double predicted = 0.0;
|
||||
for(unsigned int column = 0u; column < 4u; column++)
|
||||
double maximum_normalized_residual = 0.0;
|
||||
for(unsigned int row = 0u; row < used; row++)
|
||||
{
|
||||
predicted += design[row][column] * solution[column];
|
||||
double normalized =
|
||||
fabs(full.residual_mps[row]) * sqrt(weights[row]);
|
||||
maximum_normalized_residual =
|
||||
fmax(maximum_normalized_residual, normalized);
|
||||
}
|
||||
double residual = observation[row] - predicted;
|
||||
residual_square_sum += residual * residual;
|
||||
weighted_residual_square_sum +=
|
||||
weights[row] * residual * residual;
|
||||
if(maximum_normalized_residual > GPS_TRACK_VELOCITY_FDE_SIGMA_GATE)
|
||||
{
|
||||
double best_score = INFINITY;
|
||||
for(unsigned int trial_row = 0u; trial_row < used; trial_row++)
|
||||
{
|
||||
gps_velocity_wls_candidate_t trial;
|
||||
if(gps_velocity_solve_candidate(
|
||||
design, observation, weights, used, trial_row, &trial) &&
|
||||
(trial.weighted_residual_square_sum < best_score))
|
||||
{
|
||||
best_score = trial.weighted_residual_square_sum;
|
||||
selected = trial;
|
||||
excluded_row = trial_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if((excluded_row < used) && (excluded_measurement_mask_out != NULL))
|
||||
{
|
||||
*excluded_measurement_mask_out =
|
||||
1u << measurement_index[excluded_row];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -392,17 +502,19 @@ bool gps_tracking_velocity_wls(
|
||||
* the poor geometry appear directly in the inverse normal matrix.
|
||||
*/
|
||||
double covariance_scale = 1.0;
|
||||
if(used > 4u)
|
||||
if(selected.row_count > 4u)
|
||||
{
|
||||
covariance_scale = fmax(
|
||||
1.0, weighted_residual_square_sum / (double)(used - 4u));
|
||||
1.0,
|
||||
selected.weighted_residual_square_sum /
|
||||
(double)(selected.row_count - 4u));
|
||||
}
|
||||
for(unsigned int row = 0u; row < 3u; row++)
|
||||
{
|
||||
for(unsigned int column = 0u; column < 3u; column++)
|
||||
{
|
||||
receiver_velocity_covariance_ecef_mps2_out[row][column] =
|
||||
covariance_scale * normal_inverse[row][column];
|
||||
covariance_scale * selected.normal_inverse[row][column];
|
||||
if(!isfinite(
|
||||
receiver_velocity_covariance_ecef_mps2_out[row][column]))
|
||||
{
|
||||
@@ -410,10 +522,53 @@ bool gps_tracking_velocity_wls(
|
||||
}
|
||||
}
|
||||
}
|
||||
memcpy(receiver_velocity_ecef_mps_out, solution,
|
||||
3u * sizeof(solution[0]));
|
||||
*receiver_clock_drift_mps_out = solution[3];
|
||||
memcpy(receiver_velocity_ecef_mps_out, selected.solution,
|
||||
3u * sizeof(selected.solution[0]));
|
||||
*receiver_clock_drift_mps_out = selected.solution[3];
|
||||
*residual_rms_mps_out =
|
||||
sqrt(residual_square_sum / (double)used);
|
||||
sqrt(selected.residual_square_sum / (double)selected.row_count);
|
||||
return isfinite(*residual_rms_mps_out);
|
||||
}
|
||||
|
||||
bool gps_tracking_velocity_wls(
|
||||
const double receiver_ecef_m[3],
|
||||
const double satellite_positions_m[][3],
|
||||
const double satellite_velocities_mps[][3],
|
||||
const double satellite_clock_drifts_sps[],
|
||||
const gps_tracking_measurement_t measurements[],
|
||||
unsigned int count,
|
||||
double receiver_velocity_ecef_mps_out[3],
|
||||
double receiver_velocity_covariance_ecef_mps2_out[3][3],
|
||||
double* receiver_clock_drift_mps_out,
|
||||
double* residual_rms_mps_out)
|
||||
{
|
||||
return gps_tracking_velocity_wls_internal(
|
||||
receiver_ecef_m, satellite_positions_m,
|
||||
satellite_velocities_mps, satellite_clock_drifts_sps,
|
||||
measurements, count, receiver_velocity_ecef_mps_out,
|
||||
receiver_velocity_covariance_ecef_mps2_out,
|
||||
receiver_clock_drift_mps_out, residual_rms_mps_out,
|
||||
false, NULL);
|
||||
}
|
||||
|
||||
bool gps_tracking_velocity_wls_robust(
|
||||
const double receiver_ecef_m[3],
|
||||
const double satellite_positions_m[][3],
|
||||
const double satellite_velocities_mps[][3],
|
||||
const double satellite_clock_drifts_sps[],
|
||||
const gps_tracking_measurement_t measurements[],
|
||||
unsigned int count,
|
||||
double receiver_velocity_ecef_mps_out[3],
|
||||
double receiver_velocity_covariance_ecef_mps2_out[3][3],
|
||||
double* receiver_clock_drift_mps_out,
|
||||
double* residual_rms_mps_out,
|
||||
uint32_t* excluded_measurement_mask_out)
|
||||
{
|
||||
return gps_tracking_velocity_wls_internal(
|
||||
receiver_ecef_m, satellite_positions_m,
|
||||
satellite_velocities_mps, satellite_clock_drifts_sps,
|
||||
measurements, count, receiver_velocity_ecef_mps_out,
|
||||
receiver_velocity_covariance_ecef_mps2_out,
|
||||
receiver_clock_drift_mps_out, residual_rms_mps_out,
|
||||
true, excluded_measurement_mask_out);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user