fix(gps): harden navigation epoch recovery
This commit is contained in:
@@ -25,7 +25,6 @@ typedef struct {
|
||||
uint32_t rem_code_phase;
|
||||
double corrected_abs_sample;
|
||||
uint32_t nav_solve_timer;
|
||||
uint32_t nav_solve_event_count;
|
||||
uint64_t local_timestamp_ticks;
|
||||
uint64_t pps_counter;
|
||||
uint32_t pps_capture_sequence;
|
||||
@@ -40,7 +39,6 @@ typedef struct {
|
||||
bool nav_solve_pending;
|
||||
bool misc_pending;
|
||||
unsigned int nav_bits_overwrite_count;
|
||||
uint32_t nav_solve_event_count;
|
||||
gps_isr_event_t drop_event;
|
||||
gps_isr_event_t nav_bits_event;
|
||||
gps_isr_event_t nav_solve_event;
|
||||
@@ -71,14 +69,40 @@ static volatile unsigned int gps_plic_drain_limit_count = 0;
|
||||
static volatile unsigned int gps_plic_repeat_count = 0;
|
||||
static volatile bool gps_acq_schedule_pending = true;
|
||||
static volatile bool gps_timer_maintenance_pending = true;
|
||||
static volatile bool gps_nav_solve_period_pending = false;
|
||||
static gps_nav_epoch_assembly_t gps_nav_epoch_assembly;
|
||||
static gps_nav_epoch_snapshot_t gps_nav_active_snapshot;
|
||||
static gps_nav_epoch_snapshot_t gps_nav_completed_snapshot;
|
||||
static bool gps_nav_completed_snapshot_pending;
|
||||
static volatile gps_runtime_diagnostics_t gps_runtime_diagnostics;
|
||||
static unsigned int gps_acq_round_robin_prn = 0;
|
||||
static unsigned int gps_external_irq_id = 0u;
|
||||
static gps_external_irq_handler_t gps_external_irq_handler = NULL;
|
||||
|
||||
#define GPS_PLIC_DRAIN_LIMIT (GPS_TRCK_IP_COUNT + GPS_ACQ_IP_COUNT + 4u)
|
||||
#define GPS_NAV_CAPTURE_PERIOD_TICKS \
|
||||
(((uint64_t)GPS_PPS_CLOCK_HZ * GPS_NAV_SOLVE_PERIOD_MS) / 1000u)
|
||||
#define GPS_NAV_CAPTURE_WINDOW_TICKS \
|
||||
(((uint64_t)GPS_PPS_CLOCK_HZ * GPS_NAV_ABS_SAMPLE_SYNC_WINDOW_MS) / 1000u)
|
||||
#define GPS_NAV_HOLDOFF_LOCAL_TICKS \
|
||||
(((uint64_t)BSP_CLINT_TIMEBASE_HZ * GPS_NAV_EPOCH_HOLDOFF_MS) / 1000u)
|
||||
|
||||
_Static_assert(GPS_NAV_ABS_SAMPLE_SYNC_WINDOW_MS <
|
||||
(GPS_NAV_SOLVE_PERIOD_MS / 2u),
|
||||
"NAV capture window must be below half the solve period");
|
||||
_Static_assert(GPS_NAV_EPOCH_HOLDOFF_MS >=
|
||||
GPS_NAV_ABS_SAMPLE_SYNC_WINDOW_MS,
|
||||
"NAV holdoff must cover the capture window");
|
||||
|
||||
static unsigned int gps_mask_count(uint32_t mask)
|
||||
{
|
||||
unsigned int count = 0u;
|
||||
while(mask != 0u)
|
||||
{
|
||||
count += mask & 1u;
|
||||
mask >>= 1u;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static const char* gps_satellite_state_name(gps_satellite_state_t state)
|
||||
{
|
||||
@@ -232,37 +256,66 @@ static bool gps_take_timer_maintenance_pending(void)
|
||||
return pending;
|
||||
}
|
||||
|
||||
bool gps_take_nav_solve_period_ready(void)
|
||||
static void gps_nav_discard_active_snapshot(bool reset_assembly)
|
||||
{
|
||||
bool tracking_shadow_pending = false;
|
||||
bool acquisition_shadow_pending = false;
|
||||
memset(&gps_nav_active_snapshot, 0, sizeof(gps_nav_active_snapshot));
|
||||
if(reset_assembly)
|
||||
{
|
||||
gps_nav_epoch_assembly_reset(&gps_nav_epoch_assembly);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_incomplete_total++;
|
||||
gps_nav_discard_active_snapshot(reset_assembly);
|
||||
return;
|
||||
}
|
||||
|
||||
if(gps_nav_completed_snapshot_pending)
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_snapshot_overwrite_total++;
|
||||
}
|
||||
gps_nav_active_snapshot.channel_mask = channel_mask;
|
||||
gps_nav_completed_snapshot = gps_nav_active_snapshot;
|
||||
gps_nav_completed_snapshot_pending = true;
|
||||
gps_nav_discard_active_snapshot(reset_assembly);
|
||||
}
|
||||
|
||||
bool gps_take_nav_epoch_snapshot(gps_nav_epoch_snapshot_t* snapshot)
|
||||
{
|
||||
if(snapshot == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t irq_state = bsp_irq_save();
|
||||
for(unsigned int i = 0 ; i < GPS_TRCK_IP_COUNT ; i++)
|
||||
uint64_t now_local_ticks = bsp_read64(BSP_CLINT_MTIME);
|
||||
if(gps_nav_epoch_assembly_expired(&gps_nav_epoch_assembly,
|
||||
now_local_ticks))
|
||||
{
|
||||
if(trk_shadow[i].drop_pending || trk_shadow[i].nav_bits_pending ||
|
||||
trk_shadow[i].nav_solve_pending || trk_shadow[i].misc_pending)
|
||||
gps_runtime_diagnostics.nav_epoch_incomplete_total++;
|
||||
if(gps_nav_epoch_assembly_usable(&gps_nav_epoch_assembly,
|
||||
MIN_NEEDED_SATELLITE))
|
||||
{
|
||||
tracking_shadow_pending = true;
|
||||
break;
|
||||
gps_nav_publish_active_snapshot(
|
||||
gps_nav_epoch_assembly.received_mask, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
gps_nav_discard_active_snapshot(true);
|
||||
}
|
||||
}
|
||||
for(unsigned int i = 0 ; i < GPS_ACQ_IP_COUNT ; i++)
|
||||
{
|
||||
if(acq_shadow_pending[i])
|
||||
{
|
||||
acquisition_shadow_pending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool pipeline_idle = (gps_event_head == gps_event_tail) &&
|
||||
!tracking_shadow_pending && !acquisition_shadow_pending;
|
||||
bool ready = gps_nav_solve_period_pending && pipeline_idle;
|
||||
|
||||
bool ready = gps_nav_completed_snapshot_pending;
|
||||
if(ready)
|
||||
{
|
||||
/* The complete epoch is handed to the foreground exactly once. Events
|
||||
* arriving while WLS runs start the next assembly. */
|
||||
gps_nav_solve_period_pending = false;
|
||||
gps_nav_epoch_assembly_reset(&gps_nav_epoch_assembly);
|
||||
*snapshot = gps_nav_completed_snapshot;
|
||||
gps_nav_completed_snapshot_pending = false;
|
||||
}
|
||||
bsp_irq_restore(irq_state);
|
||||
return ready;
|
||||
@@ -313,8 +366,10 @@ static void gps_runtime_state_init(void)
|
||||
gps_plic_repeat_count = 0;
|
||||
gps_acq_schedule_pending = true;
|
||||
gps_timer_maintenance_pending = true;
|
||||
gps_nav_solve_period_pending = false;
|
||||
gps_nav_epoch_assembly_reset(&gps_nav_epoch_assembly);
|
||||
memset(&gps_nav_active_snapshot, 0, sizeof(gps_nav_active_snapshot));
|
||||
memset(&gps_nav_completed_snapshot, 0, sizeof(gps_nav_completed_snapshot));
|
||||
gps_nav_completed_snapshot_pending = false;
|
||||
memset((void*)&gps_runtime_diagnostics, 0, sizeof(gps_runtime_diagnostics));
|
||||
gps_runtime_diagnostics.last_connected_prn = GPS_PRN_INVALID;
|
||||
gps_runtime_diagnostics.last_lost_prn = GPS_PRN_INVALID;
|
||||
@@ -708,9 +763,8 @@ void trck_intr_handler(volatile gps_trck_t* const trck_ip,
|
||||
event.rem_code_phase = trck_ip->REM_CODE_PHASE_REG;
|
||||
event.corrected_abs_sample = gps_correct_abs_sample(event.abs_sample_raw, event.rem_code_phase);
|
||||
event.nav_solve_timer = trck_ip->NAV_SOLVE_TIMER;
|
||||
event.nav_solve_event_count = (status_reg & (1u << GPS_TRCK_NAV_SOLVE_FLAG_POS)) ? 1u : 0u;
|
||||
event.local_timestamp_ticks = local_timestamp_ticks;
|
||||
if(event.nav_solve_event_count != 0u)
|
||||
if((status_reg & (1u << GPS_TRCK_NAV_SOLVE_FLAG_POS)) != 0u)
|
||||
{
|
||||
event.pps_capture_valid = gps_pps_read_capture(trck_ip_no,
|
||||
&event.pps_counter,
|
||||
@@ -741,6 +795,9 @@ void trck_intr_handler(volatile gps_trck_t* const trck_ip,
|
||||
if(status_reg & (1u << GPS_TRCK_STOP_UNLOCK_POS))
|
||||
{
|
||||
shadow->drop_event = event;
|
||||
shadow->drop_event.status &=
|
||||
~((1u << GPS_TRCK_NAV_BITS_RCV_POS) |
|
||||
(1u << GPS_TRCK_NAV_SOLVE_FLAG_POS));
|
||||
shadow->drop_pending = true;
|
||||
}
|
||||
if(status_reg & (1u << GPS_TRCK_NAV_BITS_RCV_POS))
|
||||
@@ -750,12 +807,17 @@ void trck_intr_handler(volatile gps_trck_t* const trck_ip,
|
||||
shadow->nav_bits_overwrite_count++;
|
||||
}
|
||||
shadow->nav_bits_event = event;
|
||||
shadow->nav_bits_event.status &=
|
||||
~((1u << GPS_TRCK_STOP_UNLOCK_POS) |
|
||||
(1u << GPS_TRCK_NAV_SOLVE_FLAG_POS));
|
||||
shadow->nav_bits_pending = true;
|
||||
}
|
||||
if(status_reg & (1u << GPS_TRCK_NAV_SOLVE_FLAG_POS))
|
||||
{
|
||||
shadow->nav_solve_event = event;
|
||||
shadow->nav_solve_event_count++;
|
||||
shadow->nav_solve_event.status &=
|
||||
~((1u << GPS_TRCK_STOP_UNLOCK_POS) |
|
||||
(1u << GPS_TRCK_NAV_BITS_RCV_POS));
|
||||
shadow->nav_solve_pending = true;
|
||||
}
|
||||
if((status_reg & ((1u << GPS_TRCK_STOP_UNLOCK_POS) |
|
||||
@@ -763,6 +825,10 @@ void trck_intr_handler(volatile gps_trck_t* const trck_ip,
|
||||
(1u << GPS_TRCK_NAV_SOLVE_FLAG_POS))) == 0u)
|
||||
{
|
||||
shadow->misc_event = event;
|
||||
shadow->misc_event.status &=
|
||||
~((1u << GPS_TRCK_STOP_UNLOCK_POS) |
|
||||
(1u << GPS_TRCK_NAV_BITS_RCV_POS) |
|
||||
(1u << GPS_TRCK_NAV_SOLVE_FLAG_POS));
|
||||
shadow->misc_pending = true;
|
||||
}
|
||||
}
|
||||
@@ -912,10 +978,17 @@ static void gps_process_tracking_drop(const gps_isr_event_t* event)
|
||||
unsigned char trck_ip_no = event->index;
|
||||
unsigned int dropped_prn = event->prn;
|
||||
|
||||
if(gps_nav_epoch_assembly_remove_channel(
|
||||
&gps_nav_epoch_assembly, trck_ip_no))
|
||||
bool epoch_complete = gps_nav_epoch_assembly_remove_channel(
|
||||
&gps_nav_epoch_assembly, trck_ip_no);
|
||||
gps_nav_active_snapshot.channel_mask &= ~(1u << trck_ip_no);
|
||||
if(epoch_complete)
|
||||
{
|
||||
gps_nav_solve_period_pending = true;
|
||||
gps_nav_publish_active_snapshot(
|
||||
gps_nav_epoch_assembly.received_mask, true);
|
||||
}
|
||||
else if(!gps_nav_epoch_assembly.active)
|
||||
{
|
||||
gps_nav_discard_active_snapshot(false);
|
||||
}
|
||||
|
||||
if(gps_prn_valid(dropped_prn))
|
||||
@@ -944,6 +1017,42 @@ static void gps_process_tracking_drop(const gps_isr_event_t* event)
|
||||
trck_ip_no, dropped_prn, event->status, event->control);
|
||||
}
|
||||
|
||||
static bool gps_how_candidate_consistent(
|
||||
int first_week,
|
||||
double first_tow_sec,
|
||||
uint32_t first_raw_sample,
|
||||
double first_corrected_sample,
|
||||
int second_week,
|
||||
double second_tow_sec,
|
||||
uint32_t second_raw_sample,
|
||||
double second_corrected_sample)
|
||||
{
|
||||
return gps_time_anchors_consistent(
|
||||
first_week, first_tow_sec,
|
||||
first_raw_sample, first_corrected_sample,
|
||||
second_week, second_tow_sec,
|
||||
second_raw_sample, second_corrected_sample,
|
||||
SAMPLES_PER_CODE * 1000.0,
|
||||
GPS_HOW_CONTINUITY_MAX_GAP_SEC,
|
||||
GPS_HOW_CONTINUITY_TOLERANCE_SEC);
|
||||
}
|
||||
|
||||
static void gps_commit_how_anchor(volatile gps_ephem_ll_t* node,
|
||||
int week,
|
||||
double tow_sec,
|
||||
const gps_isr_event_t* event,
|
||||
bool week_valid)
|
||||
{
|
||||
node->gps_how_tow.week = week;
|
||||
node->gps_how_tow.sec = tow_sec;
|
||||
node->gps_how_abs_sample = event->abs_sample_raw;
|
||||
node->gps_how_corrected_abs_sample = event->corrected_abs_sample;
|
||||
node->gps_how_tick = event->tick;
|
||||
node->gps_how_anchor_valid = true;
|
||||
node->gps_how_week_valid = week_valid;
|
||||
node->gps_pending_how_valid = false;
|
||||
}
|
||||
|
||||
static void gps_process_tracking_nav_bits(const gps_isr_event_t* event)
|
||||
{
|
||||
unsigned char trck_ip_no = event->index;
|
||||
@@ -980,39 +1089,89 @@ static void gps_process_tracking_nav_bits(const gps_isr_event_t* event)
|
||||
unsigned char subframe_id = parse_subframe((unsigned int*)gps_subframe,
|
||||
(gps_frame_t*)gps_frame_parsed,
|
||||
&how_tow_sec);
|
||||
if((subframe_id >= 1u) && (subframe_id <= 5u))
|
||||
if((subframe_id >= 1u) && (subframe_id <= 5u) &&
|
||||
(how_tow_sec < (unsigned int)GPS_TIME_WEEK_SECONDS))
|
||||
{
|
||||
node->gps_subframe_mask |= (1u << subframe_id);
|
||||
int fallback_week = gps_frame_parsed->gps_ephems.tow.week;
|
||||
if(subframe_id == 1u)
|
||||
{
|
||||
/* WN is ten bits in LNAV. Keeping it in the same modulo domain as
|
||||
* the ephemeris is sufficient for satellite-time differences. */
|
||||
fallback_week = (int)((gps_subframe[2] >> 20) & 0x3ffu);
|
||||
fallback_week = gps_time_extend_lnav_week(
|
||||
node->gps_how_anchor_valid,
|
||||
node->gps_how_tow.week,
|
||||
(gps_subframe[2] >> 20) & 0x3ffu);
|
||||
}
|
||||
/* A subframe-1 WN is authoritative in its native 10-bit domain. On
|
||||
* other subframes, resolve the HOW seconds rollover from the previous
|
||||
* anchor. This also upgrades a pre-SF1 temporary week immediately. */
|
||||
/* Subframe 1 supplies the modulo-1024 week. Other subframes resolve
|
||||
* HOW rollover from the previous monotonic anchor. */
|
||||
int anchor_week = (subframe_id == 1u) ? fallback_week :
|
||||
gps_time_resolve_how_week(node->gps_how_anchor_valid,
|
||||
node->gps_how_tow.week,
|
||||
node->gps_how_tow.sec,
|
||||
fallback_week,
|
||||
(double)how_tow_sec);
|
||||
node->gps_how_tow.week = anchor_week;
|
||||
node->gps_how_tow.sec = (double)how_tow_sec;
|
||||
node->gps_how_abs_sample = event->abs_sample_raw;
|
||||
node->gps_how_corrected_abs_sample = event->corrected_abs_sample;
|
||||
node->gps_how_tick = event->tick;
|
||||
node->gps_how_anchor_valid = true;
|
||||
GPS_LOG(DEBUG_TRACE, GPS_LOG_CAT_NAV,
|
||||
"HOW anchor: trk=%u prn=%u week=%d tow=%.3f abs=%u corrected=%.6f\r\n",
|
||||
trck_ip_no, event->prn, anchor_week, (double)how_tow_sec,
|
||||
event->abs_sample_raw, event->corrected_abs_sample);
|
||||
/* Before the first SF1, fallback_week is only a placeholder. Validate
|
||||
* TOW/sample continuity in that temporary domain, then atomically
|
||||
* upgrade to the parity-checked LNAV week carried by SF1. */
|
||||
int continuity_week =
|
||||
((subframe_id == 1u) && !node->gps_how_week_valid &&
|
||||
node->gps_how_anchor_valid) ?
|
||||
node->gps_how_tow.week : anchor_week;
|
||||
bool accept_anchor = !node->gps_how_anchor_valid ||
|
||||
gps_how_candidate_consistent(
|
||||
node->gps_how_tow.week, node->gps_how_tow.sec,
|
||||
node->gps_how_abs_sample,
|
||||
node->gps_how_corrected_abs_sample,
|
||||
continuity_week, (double)how_tow_sec,
|
||||
event->abs_sample_raw, event->corrected_abs_sample);
|
||||
bool recovered_anchor = false;
|
||||
if(!accept_anchor && node->gps_pending_how_valid)
|
||||
{
|
||||
accept_anchor = gps_how_candidate_consistent(
|
||||
node->gps_pending_how_tow.week,
|
||||
node->gps_pending_how_tow.sec,
|
||||
node->gps_pending_how_abs_sample,
|
||||
node->gps_pending_how_corrected_abs_sample,
|
||||
anchor_week, (double)how_tow_sec,
|
||||
event->abs_sample_raw, event->corrected_abs_sample);
|
||||
recovered_anchor = accept_anchor;
|
||||
}
|
||||
|
||||
if(accept_anchor)
|
||||
{
|
||||
gps_commit_how_anchor(node, anchor_week,
|
||||
(double)how_tow_sec, event,
|
||||
node->gps_how_week_valid ||
|
||||
(subframe_id == 1u));
|
||||
GPS_LOG(recovered_anchor ? DEBUG_WARNING : DEBUG_TRACE,
|
||||
GPS_LOG_CAT_NAV,
|
||||
"%s HOW anchor: trk=%u prn=%u week=%d tow=%.3f abs=%u corrected=%.6f\r\n",
|
||||
recovered_anchor ? "recovered" : "accepted",
|
||||
trck_ip_no, event->prn, anchor_week,
|
||||
(double)how_tow_sec, event->abs_sample_raw,
|
||||
event->corrected_abs_sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->gps_pending_how_tow.week = anchor_week;
|
||||
node->gps_pending_how_tow.sec = (double)how_tow_sec;
|
||||
node->gps_pending_how_abs_sample = event->abs_sample_raw;
|
||||
node->gps_pending_how_corrected_abs_sample =
|
||||
event->corrected_abs_sample;
|
||||
node->gps_pending_how_valid = true;
|
||||
gps_runtime_diagnostics.how_anchor_rejected_total++;
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"HOW discontinuity rejected: trk=%u prn=%u current=%d/%.3f candidate=%d/%.3f rejects=%u\r\n",
|
||||
trck_ip_no, event->prn,
|
||||
node->gps_how_tow.week, node->gps_how_tow.sec,
|
||||
anchor_week, (double)how_tow_sec,
|
||||
gps_runtime_diagnostics.how_anchor_rejected_total);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_TRK, "invalid subframe id: trk=%u subframe=%u\r\n", trck_ip_no, subframe_id);
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_TRK,
|
||||
"invalid subframe/HOW: trk=%u subframe=%u tow=%u\r\n",
|
||||
trck_ip_no, subframe_id, how_tow_sec);
|
||||
}
|
||||
|
||||
gps_mark_node_fresh(node, event->status, event->tick);
|
||||
@@ -1032,32 +1191,58 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
volatile gps_ephem_ll_t* node = get_element_by_ip_number(&tracking_node, trck_ip_no);
|
||||
if(node != NULL)
|
||||
{
|
||||
unsigned int nav_solve_event_count = event->nav_solve_event_count;
|
||||
unsigned int captured_epoch_count = 1u;
|
||||
if(event->pps_capture_valid)
|
||||
{
|
||||
uint32_t previous_capture_sequence = node->gps_last_pps_capture_sequence;
|
||||
if(previous_capture_sequence != 0u)
|
||||
{
|
||||
nav_solve_event_count = event->pps_capture_sequence - previous_capture_sequence;
|
||||
if(nav_solve_event_count == 0u)
|
||||
uint32_t sequence_delta =
|
||||
event->pps_capture_sequence - previous_capture_sequence;
|
||||
if(sequence_delta == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(nav_solve_event_count > event->nav_solve_event_count)
|
||||
bool sequence_consistent =
|
||||
event->pps_counter > node->gps_last_nav_solve_pps_counter;
|
||||
if(sequence_consistent)
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_coalesced_total +=
|
||||
nav_solve_event_count - event->nav_solve_event_count;
|
||||
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);
|
||||
}
|
||||
if(sequence_consistent)
|
||||
{
|
||||
captured_epoch_count = sequence_delta;
|
||||
if(sequence_delta > 1u)
|
||||
{
|
||||
gps_runtime_diagnostics.nav_epoch_coalesced_total +=
|
||||
sequence_delta - 1u;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gps_runtime_diagnostics.pps_sequence_discontinuity_total++;
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"recovered %u coalesced nav-solve epochs: trk=%u prn=%u\r\n",
|
||||
nav_solve_event_count, trck_ip_no, event->prn);
|
||||
"PPS capture sequence discontinuity: trk=%u prn=%u previous=%u current=%u total=%u\r\n",
|
||||
trck_ip_no, event->prn,
|
||||
previous_capture_sequence,
|
||||
event->pps_capture_sequence,
|
||||
gps_runtime_diagnostics.pps_sequence_discontinuity_total);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(node->gps_last_pps_capture_sequence != 0u)
|
||||
{
|
||||
/* Keep the sequence baseline moving on the rare APB capture race. A
|
||||
* later valid capture still recovers any epochs coalesced meanwhile. */
|
||||
node->gps_last_pps_capture_sequence += nav_solve_event_count;
|
||||
/* Capture sequencing is transport metadata. Rebase it even if the
|
||||
* decoded HOW below is unavailable or rejected. */
|
||||
node->gps_last_nav_solve_pps_counter = event->pps_counter;
|
||||
node->gps_last_pps_capture_sequence =
|
||||
event->pps_capture_sequence;
|
||||
}
|
||||
|
||||
if(!node->gps_how_anchor_valid)
|
||||
@@ -1091,56 +1276,94 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
gps_frame_parsed->gps_ephems.tow.week = tow_week;
|
||||
gps_frame_parsed->gps_ephems.tow.sec = tow_sec;
|
||||
gps_frame_parsed->gps_ephems.abs_sample = event->corrected_abs_sample;
|
||||
node->gps_nav_solve_sequence += nav_solve_event_count;
|
||||
/* A one-entry hardware mailbox carries only the newest measurement.
|
||||
* Missed capture pulses are diagnostic counts, not synthetic samples. */
|
||||
node->gps_nav_solve_sequence++;
|
||||
node->gps_last_nav_solve_tow.week = tow_week;
|
||||
node->gps_last_nav_solve_tow.sec = tow_sec;
|
||||
node->gps_last_nav_solve_abs_sample = event->abs_sample_raw;
|
||||
node->gps_last_nav_solve_corrected_abs_sample = event->corrected_abs_sample;
|
||||
node->gps_last_nav_solve_local_ticks = event->local_timestamp_ticks;
|
||||
if(event->pps_capture_valid)
|
||||
{
|
||||
node->gps_last_nav_solve_pps_counter = event->pps_counter;
|
||||
node->gps_last_pps_capture_sequence = event->pps_capture_sequence;
|
||||
}
|
||||
gps_mark_node_fresh(node, event->status, event->tick);
|
||||
|
||||
uint64_t epoch_id;
|
||||
if(!gps_nav_epoch_id(tow_week, tow_sec,
|
||||
GPS_NAV_SOLVE_PERIOD_MS, &epoch_id))
|
||||
if(!event->pps_capture_valid)
|
||||
{
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"nav solve has invalid epoch id: trk=%u week=%d tow=%.6f\r\n",
|
||||
trck_ip_no, tow_week, tow_sec);
|
||||
"nav solve omitted after incoherent PPS capture read: trk=%u prn=%u\r\n",
|
||||
trck_ip_no, event->prn);
|
||||
return;
|
||||
}
|
||||
uint64_t previous_epoch = gps_nav_epoch_assembly.epoch_id;
|
||||
|
||||
uint32_t previous_expected = gps_nav_epoch_assembly.expected_mask;
|
||||
uint32_t previous_received = gps_nav_epoch_assembly.received_mask;
|
||||
gps_nav_epoch_event_result_t assembly_result =
|
||||
gps_nav_epoch_assembly_push(
|
||||
&gps_nav_epoch_assembly, epoch_id, trck_ip_no,
|
||||
gps_nav_ready_channel_mask());
|
||||
&gps_nav_epoch_assembly,
|
||||
event->pps_counter,
|
||||
event->local_timestamp_ticks,
|
||||
trck_ip_no,
|
||||
gps_nav_ready_channel_mask(),
|
||||
GPS_NAV_CAPTURE_PERIOD_TICKS,
|
||||
GPS_NAV_CAPTURE_WINDOW_TICKS,
|
||||
GPS_NAV_HOLDOFF_LOCAL_TICKS);
|
||||
|
||||
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
|
||||
{
|
||||
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,
|
||||
(unsigned long)event->pps_counter, trck_ip_no);
|
||||
}
|
||||
|
||||
if((assembly_result == GPS_NAV_EPOCH_EVENT_ACCEPTED) ||
|
||||
(assembly_result == GPS_NAV_EPOCH_EVENT_RESTARTED) ||
|
||||
(assembly_result == GPS_NAV_EPOCH_EVENT_COMPLETE))
|
||||
{
|
||||
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.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] =
|
||||
event->abs_sample_raw;
|
||||
gps_nav_active_snapshot.corrected_abs_sample[trck_ip_no] =
|
||||
event->corrected_abs_sample;
|
||||
gps_nav_active_snapshot.pps_counter[trck_ip_no] =
|
||||
event->pps_counter;
|
||||
gps_nav_active_snapshot.pps_capture_sequence[trck_ip_no] =
|
||||
event->pps_capture_sequence;
|
||||
gps_nav_active_snapshot.local_timestamp_ticks[trck_ip_no] =
|
||||
event->local_timestamp_ticks;
|
||||
}
|
||||
|
||||
if(assembly_result == GPS_NAV_EPOCH_EVENT_COMPLETE)
|
||||
{
|
||||
gps_nav_solve_period_pending = true;
|
||||
gps_nav_publish_active_snapshot(
|
||||
gps_nav_epoch_assembly.received_mask, true);
|
||||
}
|
||||
else if(assembly_result == GPS_NAV_EPOCH_EVENT_STALE)
|
||||
{
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"stale nav epoch interrupt ignored: trk=%u epoch=%lu active=%lu\r\n",
|
||||
trck_ip_no, (unsigned long)epoch_id,
|
||||
(unsigned long)gps_nav_epoch_assembly.epoch_id);
|
||||
"stale NAV capture ignored: trk=%u counter=%lu active_min=%lu\r\n",
|
||||
trck_ip_no, (unsigned long)event->pps_counter,
|
||||
(unsigned long)gps_nav_epoch_assembly.minimum_capture_counter);
|
||||
}
|
||||
else if(assembly_result == GPS_NAV_EPOCH_EVENT_RESTARTED)
|
||||
else if(assembly_result == GPS_NAV_EPOCH_EVENT_OUTLIER)
|
||||
{
|
||||
/* The previous snapshot is no longer the active assembly. Do not
|
||||
* let an already-pending foreground hand-off consume mixed nodes. */
|
||||
gps_nav_solve_period_pending = false;
|
||||
GPS_LOG(DEBUG_WARNING, GPS_LOG_CAT_NAV,
|
||||
"NAV epoch restarted by newer interrupt: old=%lu expected=0x%x received=0x%x new=%lu first_trk=%u\r\n",
|
||||
(unsigned long)previous_epoch,
|
||||
previous_expected, previous_received,
|
||||
(unsigned long)epoch_id, trck_ip_no);
|
||||
"NAV capture outlier ignored: trk=%u counter=%lu active=[%lu,%lu]\r\n",
|
||||
trck_ip_no, (unsigned long)event->pps_counter,
|
||||
(unsigned long)gps_nav_epoch_assembly.minimum_capture_counter,
|
||||
(unsigned long)gps_nav_epoch_assembly.maximum_capture_counter);
|
||||
}
|
||||
GPS_LOG(DEBUG_TRACE, GPS_LOG_CAT_TRK,
|
||||
"nav solve: trk=%u abs_raw=%u rem=%u abs=%.6f timer=%u count=%u dt=%.6f tow=%f seq=%u\r\n",
|
||||
@@ -1149,7 +1372,7 @@ static void gps_process_tracking_nav_solve(const gps_isr_event_t* event)
|
||||
event->rem_code_phase,
|
||||
gps_frame_parsed->gps_ephems.abs_sample,
|
||||
event->nav_solve_timer,
|
||||
nav_solve_event_count,
|
||||
captured_epoch_count,
|
||||
tow_delta_sec,
|
||||
gps_frame_parsed->gps_ephems.tow.sec,
|
||||
node->gps_nav_solve_sequence);
|
||||
@@ -1243,7 +1466,6 @@ static void gps_process_shadow_tracking_events(void)
|
||||
}
|
||||
if(shadow.nav_solve_pending)
|
||||
{
|
||||
shadow.nav_solve_event.nav_solve_event_count = shadow.nav_solve_event_count;
|
||||
gps_process_tracking_event(&shadow.nav_solve_event);
|
||||
}
|
||||
if(shadow.misc_pending)
|
||||
|
||||
@@ -570,7 +570,6 @@ static void gps_enable_nav_solve_for_snapshot_locked(const unsigned char snapsho
|
||||
if(!gps_channel_nav_solve_enabled(ip_no))
|
||||
{
|
||||
gps_set_channel_nav_solve_mode(ip_no);
|
||||
node->gps_nav_solve_consumed_sequence = node->gps_nav_solve_sequence;
|
||||
GPS_LOG(DEBUG_INFO, GPS_LOG_CAT_TRK,
|
||||
"tracking channel %u enabled nav solve; nav bits remain enabled\r\n",
|
||||
ip_no);
|
||||
@@ -599,9 +598,7 @@ static unsigned int gps_collect_tracked_channels_locked(
|
||||
|
||||
static unsigned int gps_collect_nav_snapshot_locked(gps_ephemeris_t snapshot[GPS_TRCK_IP_COUNT],
|
||||
unsigned char snapshot_ips[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_raw_samples[GPS_TRCK_IP_COUNT],
|
||||
bool require_nav_solve_mode,
|
||||
bool require_nav_sequence)
|
||||
uint32_t snapshot_raw_samples[GPS_TRCK_IP_COUNT])
|
||||
{
|
||||
unsigned int count = 0;
|
||||
|
||||
@@ -611,12 +608,7 @@ static unsigned int gps_collect_nav_snapshot_locked(gps_ephemeris_t snapshot[GPS
|
||||
{
|
||||
unsigned char ip_no = iterator->tracking_ip_no;
|
||||
gps_ephemeris_t* eph = (gps_ephemeris_t*)iterator->data_p;
|
||||
if(gps_node_ready_for_position(iterator) &&
|
||||
(!require_nav_solve_mode || gps_channel_nav_solve_enabled(ip_no)) &&
|
||||
(!require_nav_sequence ||
|
||||
((eph != NULL) &&
|
||||
(iterator->gps_nav_solve_sequence !=
|
||||
iterator->gps_nav_solve_consumed_sequence))))
|
||||
if(gps_node_ready_for_position(iterator) && (eph != NULL))
|
||||
{
|
||||
if(gps_snapshot_has_svid(snapshot, count, eph->SVID))
|
||||
{
|
||||
@@ -626,19 +618,10 @@ static unsigned int gps_collect_nav_snapshot_locked(gps_ephemeris_t snapshot[GPS
|
||||
continue;
|
||||
}
|
||||
snapshot[count] = *eph;
|
||||
if(require_nav_sequence)
|
||||
if(snapshot_raw_samples != NULL)
|
||||
{
|
||||
/* Nav bits may update the shared ephemeris between nav-solve
|
||||
* epochs. Position measurements must use the timing latched by
|
||||
* the nav-solve event, not the shared nav-bit fields. */
|
||||
snapshot[count].tow = iterator->gps_last_nav_solve_tow;
|
||||
snapshot[count].abs_sample =
|
||||
iterator->gps_last_nav_solve_corrected_abs_sample;
|
||||
if(snapshot_raw_samples != NULL)
|
||||
{
|
||||
snapshot_raw_samples[count] =
|
||||
iterator->gps_last_nav_solve_abs_sample;
|
||||
}
|
||||
snapshot_raw_samples[count] =
|
||||
iterator->gps_last_nav_solve_abs_sample;
|
||||
}
|
||||
snapshot_ips[count] = ip_no;
|
||||
count++;
|
||||
@@ -651,21 +634,70 @@ static unsigned int gps_collect_nav_snapshot_locked(gps_ephemeris_t snapshot[GPS
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned int gps_collect_completed_epoch_locked(
|
||||
const gps_nav_epoch_snapshot_t* epoch,
|
||||
gps_ephemeris_t snapshot[GPS_TRCK_IP_COUNT],
|
||||
unsigned char snapshot_ips[GPS_TRCK_IP_COUNT],
|
||||
uint64_t snapshot_pps_counters[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_pps_sequences[GPS_TRCK_IP_COUNT],
|
||||
uint64_t snapshot_local_timestamps[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_raw_samples[GPS_TRCK_IP_COUNT])
|
||||
{
|
||||
if(epoch == NULL)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
unsigned int count = 0u;
|
||||
for(unsigned char ip_no = 0u ; ip_no < GPS_TRCK_IP_COUNT ; ip_no++)
|
||||
{
|
||||
if((epoch->channel_mask & (1u << ip_no)) == 0u)
|
||||
{
|
||||
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]) ||
|
||||
gps_snapshot_has_svid(snapshot, count, eph->SVID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
snapshot[count] = *eph;
|
||||
snapshot[count].tow = epoch->tow[ip_no];
|
||||
snapshot[count].abs_sample =
|
||||
epoch->corrected_abs_sample[ip_no];
|
||||
snapshot_ips[count] = ip_no;
|
||||
snapshot_pps_counters[count] =
|
||||
epoch->pps_counter[ip_no];
|
||||
snapshot_pps_sequences[count] =
|
||||
epoch->pps_capture_sequence[ip_no];
|
||||
snapshot_local_timestamps[count] =
|
||||
epoch->local_timestamp_ticks[ip_no];
|
||||
snapshot_raw_samples[count] =
|
||||
epoch->raw_sample[ip_no];
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned int gps_select_coherent_nav_snapshot(
|
||||
gps_ephemeris_t snapshot[GPS_TRCK_IP_COUNT],
|
||||
unsigned char snapshot_ips[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_sequences[GPS_TRCK_IP_COUNT],
|
||||
uint64_t snapshot_pps_counters[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_pps_sequences[GPS_TRCK_IP_COUNT],
|
||||
uint64_t snapshot_local_timestamps[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_raw_samples[GPS_TRCK_IP_COUNT],
|
||||
unsigned int candidate_count,
|
||||
uint32_t* selected_mask_out)
|
||||
unsigned int candidate_count)
|
||||
{
|
||||
if((candidate_count == 0u) ||
|
||||
(candidate_count > GPS_NAV_EPOCH_MAX_CANDIDATES))
|
||||
{
|
||||
*selected_mask_out = 0u;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
@@ -683,7 +715,6 @@ static unsigned int gps_select_coherent_nav_snapshot(
|
||||
GPS_NAV_TOW_CLUSTER_TOLERANCE_SEC,
|
||||
&selection))
|
||||
{
|
||||
*selected_mask_out = 0u;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
@@ -702,7 +733,6 @@ static unsigned int gps_select_coherent_nav_snapshot(
|
||||
{
|
||||
snapshot[destination] = snapshot[source];
|
||||
snapshot_ips[destination] = snapshot_ips[source];
|
||||
snapshot_sequences[destination] = snapshot_sequences[source];
|
||||
snapshot_pps_counters[destination] =
|
||||
snapshot_pps_counters[source];
|
||||
snapshot_pps_sequences[destination] =
|
||||
@@ -720,28 +750,9 @@ static unsigned int gps_select_coherent_nav_snapshot(
|
||||
destination++;
|
||||
}
|
||||
|
||||
*selected_mask_out = selection.selected_mask;
|
||||
return destination;
|
||||
}
|
||||
|
||||
static void gps_capture_nav_metadata_locked(const unsigned char snapshot_ips[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_sequences[GPS_TRCK_IP_COUNT],
|
||||
uint64_t snapshot_pps_counters[GPS_TRCK_IP_COUNT],
|
||||
uint32_t snapshot_pps_sequences[GPS_TRCK_IP_COUNT],
|
||||
uint64_t snapshot_local_timestamps[GPS_TRCK_IP_COUNT],
|
||||
unsigned int satellite_count)
|
||||
{
|
||||
for(unsigned int i = 0 ; i < satellite_count ; i++)
|
||||
{
|
||||
volatile gps_ephem_ll_t* node = get_element_by_ip_number(&tracking_node, snapshot_ips[i]);
|
||||
snapshot_sequences[i] = (node != NULL) ? node->gps_nav_solve_sequence : 0u;
|
||||
snapshot_pps_counters[i] = (node != NULL) ? node->gps_last_nav_solve_pps_counter : 0u;
|
||||
snapshot_pps_sequences[i] = (node != NULL) ? node->gps_last_pps_capture_sequence : 0u;
|
||||
snapshot_local_timestamps[i] =
|
||||
(node != NULL) ? node->gps_last_nav_solve_local_ticks : 0u;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t gps_nav_median_local_timestamp(
|
||||
const uint64_t timestamps[GPS_TRCK_IP_COUNT], unsigned int count)
|
||||
{
|
||||
@@ -765,48 +776,6 @@ static uint64_t gps_nav_median_local_timestamp(
|
||||
return (valid_count != 0u) ? sorted[valid_count / 2u] : 0u;
|
||||
}
|
||||
|
||||
static void gps_consume_nav_sequences_locked(const unsigned char snapshot_ips[GPS_TRCK_IP_COUNT],
|
||||
const uint32_t snapshot_sequences[GPS_TRCK_IP_COUNT],
|
||||
unsigned int satellite_count)
|
||||
{
|
||||
for(unsigned int i = 0 ; i < satellite_count ; i++)
|
||||
{
|
||||
volatile gps_ephem_ll_t* node = get_element_by_ip_number(&tracking_node, snapshot_ips[i]);
|
||||
if(node != NULL)
|
||||
{
|
||||
/* Consume only the epoch copied into this solution. A newer sequence,
|
||||
* if present, remains different from consumed and is solved next. */
|
||||
node->gps_nav_solve_consumed_sequence = snapshot_sequences[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gps_consume_rejected_nav_sequences_locked(
|
||||
const unsigned char candidate_ips[GPS_TRCK_IP_COUNT],
|
||||
const uint32_t candidate_sequences[GPS_TRCK_IP_COUNT],
|
||||
unsigned int candidate_count,
|
||||
uint32_t selected_mask)
|
||||
{
|
||||
for(unsigned int i = 0u ; i < candidate_count ; i++)
|
||||
{
|
||||
if((selected_mask & (1u << i)) != 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
volatile gps_ephem_ll_t* node =
|
||||
get_element_by_ip_number(&tracking_node, candidate_ips[i]);
|
||||
if(node != NULL)
|
||||
{
|
||||
/*
|
||||
* Consume exactly the rejected snapshot. If an interrupt has
|
||||
* already produced a newer sequence, it remains different from
|
||||
* this consumed value and is preserved for the next epoch.
|
||||
*/
|
||||
node->gps_nav_solve_consumed_sequence = candidate_sequences[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool nav_software_from_snapshot(const gps_ephemeris_t* ephemeris,
|
||||
const uint64_t pps_counters[GPS_TRCK_IP_COUNT],
|
||||
const uint32_t pps_capture_sequences[GPS_TRCK_IP_COUNT],
|
||||
@@ -1088,14 +1057,13 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
bool result_ready = false;
|
||||
gps_ephemeris_t snapshot[GPS_TRCK_IP_COUNT];
|
||||
unsigned char snapshot_ips[GPS_TRCK_IP_COUNT];
|
||||
uint32_t snapshot_sequences[GPS_TRCK_IP_COUNT];
|
||||
uint64_t snapshot_pps_counters[GPS_TRCK_IP_COUNT];
|
||||
uint32_t snapshot_pps_sequences[GPS_TRCK_IP_COUNT];
|
||||
uint64_t snapshot_local_timestamps[GPS_TRCK_IP_COUNT];
|
||||
uint32_t snapshot_raw_samples[GPS_TRCK_IP_COUNT];
|
||||
gps_nav_epoch_snapshot_t completed_epoch;
|
||||
unsigned int satellite_count = 0;
|
||||
unsigned int candidate_count = 0;
|
||||
uint32_t selected_candidate_mask = 0u;
|
||||
bool run_nav = false;
|
||||
bool initial_run = false;
|
||||
unsigned int ready_count;
|
||||
@@ -1110,13 +1078,14 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
#ifdef EKF_ENABLE
|
||||
(void)g_ekf;
|
||||
#endif
|
||||
bool nav_period_ready = gps_take_nav_solve_period_ready();
|
||||
bool nav_period_ready =
|
||||
gps_take_nav_epoch_snapshot(&completed_epoch);
|
||||
uint64_t nav_period_start_cycles = nav_period_ready ? gps_read_cycle_counter() : 0u;
|
||||
|
||||
uint64_t irq_state = bsp_irq_save();
|
||||
|
||||
ready_count = gps_collect_nav_snapshot_locked(
|
||||
snapshot, snapshot_ips, NULL, false, false);
|
||||
snapshot, snapshot_ips, NULL);
|
||||
tracked_count = gps_collect_tracked_channels_locked(
|
||||
GPS_DEBUG_ENABLED(DEBUG_CHANNELS) ? tracked_prns : NULL);
|
||||
|
||||
@@ -1131,13 +1100,11 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
gps_enable_nav_solve_for_snapshot_locked(snapshot_ips, ready_count);
|
||||
if(nav_period_ready)
|
||||
{
|
||||
candidate_count = gps_collect_nav_snapshot_locked(
|
||||
snapshot, snapshot_ips, snapshot_raw_samples, true, true);
|
||||
gps_capture_nav_metadata_locked(snapshot_ips, snapshot_sequences,
|
||||
snapshot_pps_counters,
|
||||
snapshot_pps_sequences,
|
||||
snapshot_local_timestamps,
|
||||
candidate_count);
|
||||
candidate_count = gps_collect_completed_epoch_locked(
|
||||
&completed_epoch,
|
||||
snapshot, snapshot_ips, snapshot_pps_counters,
|
||||
snapshot_pps_sequences,
|
||||
snapshot_local_timestamps, snapshot_raw_samples);
|
||||
initial_run = !gps_initial_position_valid;
|
||||
}
|
||||
}
|
||||
@@ -1146,22 +1113,11 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
|
||||
if(nav_period_ready && (candidate_count != 0u))
|
||||
{
|
||||
/*
|
||||
* Preserve the pre-compaction identity of rejected candidates so their
|
||||
* exact sequence can be retired without touching a newer interrupt.
|
||||
*/
|
||||
unsigned char candidate_ips[GPS_TRCK_IP_COUNT];
|
||||
uint32_t candidate_sequences[GPS_TRCK_IP_COUNT];
|
||||
memcpy(candidate_ips, snapshot_ips,
|
||||
candidate_count * sizeof(candidate_ips[0]));
|
||||
memcpy(candidate_sequences, snapshot_sequences,
|
||||
candidate_count * sizeof(candidate_sequences[0]));
|
||||
|
||||
satellite_count = gps_select_coherent_nav_snapshot(
|
||||
snapshot, snapshot_ips, snapshot_sequences,
|
||||
snapshot_pps_counters, snapshot_pps_sequences,
|
||||
snapshot, snapshot_ips, snapshot_pps_counters,
|
||||
snapshot_pps_sequences,
|
||||
snapshot_local_timestamps, snapshot_raw_samples,
|
||||
candidate_count, &selected_candidate_mask);
|
||||
candidate_count);
|
||||
coherent_nav_count = satellite_count;
|
||||
run_nav = satellite_count >= MIN_NEEDED_SATELLITE;
|
||||
|
||||
@@ -1169,11 +1125,6 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
{
|
||||
unsigned int excluded_count = candidate_count - satellite_count;
|
||||
gps_nav_epoch_excluded_total += excluded_count;
|
||||
uint64_t consume_irq_state = bsp_irq_save();
|
||||
gps_consume_rejected_nav_sequences_locked(
|
||||
candidate_ips, candidate_sequences, candidate_count,
|
||||
selected_candidate_mask);
|
||||
bsp_irq_restore(consume_irq_state);
|
||||
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,
|
||||
@@ -1181,10 +1132,6 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
}
|
||||
else if(!run_nav)
|
||||
{
|
||||
uint64_t consume_irq_state = bsp_irq_save();
|
||||
gps_consume_rejected_nav_sequences_locked(
|
||||
candidate_ips, candidate_sequences, candidate_count, 0u);
|
||||
bsp_irq_restore(consume_irq_state);
|
||||
if(candidate_count >= MIN_NEEDED_SATELLITE)
|
||||
{
|
||||
gps_nav_epoch_reject_total++;
|
||||
@@ -1202,8 +1149,15 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
(coherent_nav_count != last_nav_status_coherent_count) ||
|
||||
((now - last_nav_status_log_tick) >= GPS_NAV_STATUS_LOG_PERIOD_TICKS))
|
||||
{
|
||||
GPS_LOG(DEBUG_DEBUG, GPS_LOG_CAT_NAV, "tracked channels=%u ready channels=%u coherent_nav=%u\r\n",
|
||||
tracked_count, ready_count, coherent_nav_count);
|
||||
gps_runtime_diagnostics_t status_diagnostics;
|
||||
gps_get_runtime_diagnostics(&status_diagnostics);
|
||||
GPS_LOG(DEBUG_DEBUG, GPS_LOG_CAT_NAV,
|
||||
"tracked channels=%u ready channels=%u coherent_nav=%u incomplete=%u snapshot_overwrite=%u pps_discontinuity=%u how_reject=%u\r\n",
|
||||
tracked_count, ready_count, coherent_nav_count,
|
||||
status_diagnostics.nav_epoch_incomplete_total,
|
||||
status_diagnostics.nav_epoch_snapshot_overwrite_total,
|
||||
status_diagnostics.pps_sequence_discontinuity_total,
|
||||
status_diagnostics.how_anchor_rejected_total);
|
||||
last_nav_status_tracked_count = tracked_count;
|
||||
last_nav_status_ready_count = ready_count;
|
||||
last_nav_status_coherent_count = coherent_nav_count;
|
||||
@@ -1241,13 +1195,8 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
result_ready = true;
|
||||
}
|
||||
}
|
||||
/* A completed hardware epoch is attempted once. Retrying the same
|
||||
* measurements after a WLS failure would couple solution cadence to an
|
||||
* unrelated foreground wake-up. */
|
||||
uint64_t consume_irq_state = bsp_irq_save();
|
||||
gps_consume_nav_sequences_locked(
|
||||
snapshot_ips, snapshot_sequences, satellite_count);
|
||||
bsp_irq_restore(consume_irq_state);
|
||||
/* The completed snapshot is handed out exactly once. Retrying after a
|
||||
* WLS failure would couple solution cadence to a foreground wake-up. */
|
||||
uint64_t foreground_cycles = gps_read_cycle_counter() - nav_period_start_cycles;
|
||||
uint64_t period_budget_cycles = (GPS_PROFILE_CPU_HZ * GPS_NAV_SOLVE_PERIOD_MS) / 1000u;
|
||||
bool period_overrun = foreground_cycles >= period_budget_cycles;
|
||||
@@ -1260,6 +1209,7 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
"fde=%u valid=%u budget_cycles=%lu overrun=%u math_corr=%.6lf "
|
||||
"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",
|
||||
GPS_NAV_SOLVE_PERIOD_MS,
|
||||
(unsigned long)GPS_PROFILE_CPU_HZ,
|
||||
@@ -1283,6 +1233,10 @@ bool calculate_position(gps_nav_solution_t* solution_out,
|
||||
runtime_diagnostics.plic_drain_limit_total,
|
||||
runtime_diagnostics.nav_epoch_coalesced_total,
|
||||
runtime_diagnostics.satellite_lost_total,
|
||||
runtime_diagnostics.pps_sequence_discontinuity_total,
|
||||
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;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#define GPS_WEEK_SECONDS 604800.0
|
||||
#define GPS_HALF_WEEK_SECONDS 302400.0
|
||||
#define GPS_WEEK_MILLISECONDS 604800000ULL
|
||||
|
||||
static double gps_nav_epoch_tow_delta(
|
||||
const gps_nav_epoch_key_t* first,
|
||||
@@ -180,40 +179,16 @@ bool gps_nav_epoch_select(
|
||||
return selection->selected_count != 0u;
|
||||
}
|
||||
|
||||
bool gps_nav_epoch_id(
|
||||
int week,
|
||||
double tow_sec,
|
||||
unsigned int period_ms,
|
||||
uint64_t* epoch_id)
|
||||
{
|
||||
if((epoch_id == NULL) || (week < 0) || (period_ms == 0u) ||
|
||||
((GPS_WEEK_MILLISECONDS % period_ms) != 0u) ||
|
||||
!isfinite(tow_sec) || (tow_sec < 0.0) ||
|
||||
(tow_sec >= GPS_WEEK_SECONDS))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t epochs_per_week = GPS_WEEK_MILLISECONDS / period_ms;
|
||||
double epoch_in_week = (tow_sec * 1000.0) / (double)period_ms;
|
||||
uint64_t rounded_epoch = (uint64_t)llround(epoch_in_week);
|
||||
if(rounded_epoch == epochs_per_week)
|
||||
{
|
||||
week++;
|
||||
rounded_epoch = 0u;
|
||||
}
|
||||
*epoch_id = ((uint64_t)(unsigned int)week * epochs_per_week) +
|
||||
rounded_epoch;
|
||||
return true;
|
||||
}
|
||||
|
||||
void gps_nav_epoch_assembly_reset(gps_nav_epoch_assembly_t* assembly)
|
||||
{
|
||||
if(assembly == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
assembly->epoch_id = 0u;
|
||||
assembly->first_capture_counter = 0u;
|
||||
assembly->minimum_capture_counter = 0u;
|
||||
assembly->maximum_capture_counter = 0u;
|
||||
assembly->deadline_local_ticks = 0u;
|
||||
assembly->expected_mask = 0u;
|
||||
assembly->received_mask = 0u;
|
||||
assembly->active = false;
|
||||
@@ -230,12 +205,21 @@ bool gps_nav_epoch_assembly_complete(
|
||||
|
||||
gps_nav_epoch_event_result_t gps_nav_epoch_assembly_push(
|
||||
gps_nav_epoch_assembly_t* assembly,
|
||||
uint64_t epoch_id,
|
||||
uint64_t capture_counter,
|
||||
uint64_t local_timestamp_ticks,
|
||||
unsigned int channel,
|
||||
uint32_t ready_mask)
|
||||
uint32_t ready_mask,
|
||||
uint64_t capture_period_ticks,
|
||||
uint64_t sync_window_ticks,
|
||||
uint64_t holdoff_local_ticks)
|
||||
{
|
||||
if((assembly == NULL) ||
|
||||
(channel >= GPS_NAV_EPOCH_MAX_CANDIDATES))
|
||||
(channel >= GPS_NAV_EPOCH_MAX_CANDIDATES) ||
|
||||
(capture_counter == 0u) ||
|
||||
(capture_period_ticks == 0u) ||
|
||||
(sync_window_ticks == 0u) ||
|
||||
(sync_window_ticks >= (capture_period_ticks / 2u)) ||
|
||||
(holdoff_local_ticks == 0u))
|
||||
{
|
||||
return GPS_NAV_EPOCH_EVENT_INVALID;
|
||||
}
|
||||
@@ -252,20 +236,40 @@ gps_nav_epoch_event_result_t gps_nav_epoch_assembly_push(
|
||||
bool restarted = false;
|
||||
if(assembly->active)
|
||||
{
|
||||
if(epoch_id < assembly->epoch_id)
|
||||
uint64_t minimum = assembly->minimum_capture_counter;
|
||||
uint64_t maximum = assembly->maximum_capture_counter;
|
||||
uint64_t next_minimum =
|
||||
(capture_counter < minimum) ? capture_counter : minimum;
|
||||
uint64_t next_maximum =
|
||||
(capture_counter > maximum) ? capture_counter : maximum;
|
||||
if((next_maximum - next_minimum) > sync_window_ticks)
|
||||
{
|
||||
return GPS_NAV_EPOCH_EVENT_STALE;
|
||||
}
|
||||
if(epoch_id > assembly->epoch_id)
|
||||
{
|
||||
gps_nav_epoch_assembly_reset(assembly);
|
||||
restarted = true;
|
||||
uint64_t epoch_separation = capture_period_ticks / 2u;
|
||||
if((capture_counter > maximum) &&
|
||||
((capture_counter - maximum) >= epoch_separation))
|
||||
{
|
||||
gps_nav_epoch_assembly_reset(assembly);
|
||||
restarted = true;
|
||||
}
|
||||
else if((capture_counter < minimum) &&
|
||||
((minimum - capture_counter) >= epoch_separation))
|
||||
{
|
||||
return GPS_NAV_EPOCH_EVENT_STALE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GPS_NAV_EPOCH_EVENT_OUTLIER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!assembly->active)
|
||||
{
|
||||
assembly->epoch_id = epoch_id;
|
||||
assembly->first_capture_counter = capture_counter;
|
||||
assembly->minimum_capture_counter = capture_counter;
|
||||
assembly->maximum_capture_counter = capture_counter;
|
||||
assembly->deadline_local_ticks =
|
||||
local_timestamp_ticks + holdoff_local_ticks;
|
||||
assembly->expected_mask = ready_mask;
|
||||
assembly->received_mask = 0u;
|
||||
assembly->active = true;
|
||||
@@ -280,6 +284,14 @@ gps_nav_epoch_event_result_t gps_nav_epoch_assembly_push(
|
||||
return GPS_NAV_EPOCH_EVENT_DUPLICATE;
|
||||
}
|
||||
|
||||
if(capture_counter < assembly->minimum_capture_counter)
|
||||
{
|
||||
assembly->minimum_capture_counter = capture_counter;
|
||||
}
|
||||
if(capture_counter > assembly->maximum_capture_counter)
|
||||
{
|
||||
assembly->maximum_capture_counter = capture_counter;
|
||||
}
|
||||
assembly->received_mask |= channel_mask;
|
||||
if(gps_nav_epoch_assembly_complete(assembly))
|
||||
{
|
||||
@@ -310,3 +322,24 @@ bool gps_nav_epoch_assembly_remove_channel(
|
||||
}
|
||||
return gps_nav_epoch_assembly_complete(assembly);
|
||||
}
|
||||
|
||||
bool gps_nav_epoch_assembly_usable(
|
||||
const gps_nav_epoch_assembly_t* assembly,
|
||||
unsigned int minimum_channels)
|
||||
{
|
||||
if((assembly == NULL) || !assembly->active ||
|
||||
(minimum_channels == 0u))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return gps_nav_epoch_mask_count(assembly->received_mask) >=
|
||||
minimum_channels;
|
||||
}
|
||||
|
||||
bool gps_nav_epoch_assembly_expired(
|
||||
const gps_nav_epoch_assembly_t* assembly,
|
||||
uint64_t now_local_ticks)
|
||||
{
|
||||
return (assembly != NULL) && assembly->active &&
|
||||
(now_local_ticks >= assembly->deadline_local_ticks);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,28 @@ int gps_time_resolve_how_week(bool previous_valid,
|
||||
return week;
|
||||
}
|
||||
|
||||
int gps_time_extend_lnav_week(bool previous_valid,
|
||||
int previous_week,
|
||||
unsigned int lnav_week)
|
||||
{
|
||||
int week = (int)(lnav_week & 0x3ffu);
|
||||
if(!previous_valid || (previous_week < 0))
|
||||
{
|
||||
return week;
|
||||
}
|
||||
|
||||
int candidate = (previous_week & ~0x3ff) | week;
|
||||
if((candidate - previous_week) > 512)
|
||||
{
|
||||
candidate -= 1024;
|
||||
}
|
||||
else if((previous_week - candidate) > 512)
|
||||
{
|
||||
candidate += 1024;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
bool gps_time_from_sample_anchor(int anchor_week,
|
||||
double anchor_tow_sec,
|
||||
uint32_t anchor_raw_sample,
|
||||
@@ -86,3 +108,50 @@ bool gps_time_from_sample_anchor(int anchor_week,
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gps_time_anchors_consistent(int first_week,
|
||||
double first_tow_sec,
|
||||
uint32_t first_raw_sample,
|
||||
double first_corrected_sample,
|
||||
int second_week,
|
||||
double second_tow_sec,
|
||||
uint32_t second_raw_sample,
|
||||
double second_corrected_sample,
|
||||
double samples_per_second,
|
||||
double max_gap_sec,
|
||||
double tolerance_sec)
|
||||
{
|
||||
if((first_week < 0) || (second_week < 0) ||
|
||||
!isfinite(first_tow_sec) || !isfinite(second_tow_sec) ||
|
||||
!isfinite(first_corrected_sample) ||
|
||||
!isfinite(second_corrected_sample) ||
|
||||
(first_tow_sec < 0.0) ||
|
||||
(first_tow_sec >= GPS_TIME_WEEK_SECONDS) ||
|
||||
(second_tow_sec < 0.0) ||
|
||||
(second_tow_sec >= GPS_TIME_WEEK_SECONDS) ||
|
||||
!(samples_per_second > 0.0) || !(max_gap_sec > 0.0) ||
|
||||
!(tolerance_sec >= 0.0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t raw_delta = (int32_t)(second_raw_sample - first_raw_sample);
|
||||
double first_fraction =
|
||||
first_corrected_sample - (double)first_raw_sample;
|
||||
double second_fraction =
|
||||
second_corrected_sample - (double)second_raw_sample;
|
||||
double sample_elapsed =
|
||||
((double)raw_delta + second_fraction - first_fraction) /
|
||||
samples_per_second;
|
||||
if(!isfinite(sample_elapsed) || (sample_elapsed < -0.001) ||
|
||||
(sample_elapsed > max_gap_sec))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double gps_elapsed =
|
||||
((double)(second_week - first_week) * GPS_TIME_WEEK_SECONDS) +
|
||||
second_tow_sec - first_tow_sec;
|
||||
return isfinite(gps_elapsed) &&
|
||||
(fabs(gps_elapsed - sample_elapsed) <= tolerance_sec);
|
||||
}
|
||||
|
||||
@@ -66,8 +66,13 @@ bool insert_element(volatile gps_ephem_ll_t** node,
|
||||
new_node->gps_how_corrected_abs_sample = 0.0;
|
||||
new_node->gps_how_tick = 0u;
|
||||
new_node->gps_how_anchor_valid = false;
|
||||
new_node->gps_how_week_valid = false;
|
||||
new_node->gps_pending_how_tow.week = 0;
|
||||
new_node->gps_pending_how_tow.sec = 0.0;
|
||||
new_node->gps_pending_how_abs_sample = 0u;
|
||||
new_node->gps_pending_how_corrected_abs_sample = 0.0;
|
||||
new_node->gps_pending_how_valid = false;
|
||||
new_node->gps_nav_solve_sequence = 0u;
|
||||
new_node->gps_nav_solve_consumed_sequence = 0u;
|
||||
new_node->gps_last_nav_solve_tow.week = 0;
|
||||
new_node->gps_last_nav_solve_tow.sec = 0.0;
|
||||
new_node->gps_last_nav_solve_abs_sample = 0u;
|
||||
|
||||
Reference in New Issue
Block a user