Files

195 lines
6.0 KiB
C

#include <bsp/soc.h>
#include <stdbool.h>
#include <stdint.h>
#include "adapters/gps_fusion_publisher.h"
#include "adapters/gps_system_uart.h"
#include "adapters/imu_fusion_profile.h"
#include "fusion/navigation_fusion.h"
#include "gps/gps_receiver.h"
#include "gps_system.h"
#include "imu/imu_sensor.h"
#ifndef GPS_ESKF_ENABLE
#define GPS_ESKF_ENABLE 1
#endif
#if (GPS_ESKF_ENABLE != 0) && (GPS_ESKF_ENABLE != 1)
#error "GPS_ESKF_ENABLE must be 0 or 1"
#endif
#ifndef IMU_CORRECTED_PUBLISH_EVERY_N_SAMPLES
#define IMU_CORRECTED_PUBLISH_EVERY_N_SAMPLES 5u
#endif
#if (IMU_CORRECTED_PUBLISH_EVERY_N_SAMPLES < 1)
#error "IMU_CORRECTED_PUBLISH_EVERY_N_SAMPLES must be at least one"
#endif
#define IMU_SAMPLES_BEFORE_GPS_SERVICE 1u
#define IMU_SAMPLES_AFTER_GPS_SERVICE 3u
static void update_eskf_doppler_bandwidth(
const navigation_fusion_output_t* output)
{
if((output == NULL) || !output->local_valid)
{
return;
}
double alpha = 0.05;
if(output->motion_state == FUSION_MOTION_MOVING)
{
alpha = (output->linear_acceleration_norm_mps2 >= 2.5) ?
0.40 : 0.20;
}
gps_receiver_set_eskf_doppler_alpha(alpha);
}
static void initialize_sensor_fusion(gps_system_state_t* system)
{
system->fusion_enabled = (GPS_ESKF_ENABLE != 0);
if(!system->fusion_enabled)
{
return;
}
imu_sensor_config_t imu_config;
imu_sensor_default_config(&imu_config);
imu_config.odr_hz = IMU_ODR_HZ;
imu_config.accel_full_scale_g = IMU_ACCEL_FS_G;
imu_config.gyro_full_scale_dps = IMU_GYRO_FS_DPS;
system->imu_initialized = imu_sensor_init(&imu_config);
fusion_imu_quality_profile_t imu_quality;
bool quality_available = system->imu_initialized &&
imu_fusion_quality_profile_for_backend(
imu_sensor_active_backend(), &imu_quality);
system->fusion_initialized = quality_available &&
navigation_fusion_init(
BSP_CLINT_TIMEBASE_HZ, &imu_quality,
true); /* Current prototype is the PX4-equivalent unarmed bench mode. */
}
static unsigned int feed_pending_imu_to_eskf(
gps_system_state_t* system,
unsigned int max_samples)
{
if(!system->fusion_initialized || (max_samples == 0u))
{
return 0u;
}
unsigned int processed = 0u;
imu_sensor_service();
while((processed < max_samples) &&
imu_sensor_take_measurement(&system->imu_sample))
{
bool accepted = navigation_fusion_push_imu(&system->imu_sample);
bool output_available = accepted &&
navigation_fusion_get_output(&system->fused_solution);
if(output_available)
{
update_eskf_doppler_bandwidth(&system->fused_solution);
}
if(output_available && system->fused_solution.global_valid &&
(system->fused_solution.timestamp_ticks !=
system->last_fused_location_tick))
{
system->fused_publish_sample_count++;
bool first_location =
(system->last_fused_location_tick == 0u);
bool publish_due =
(system->fused_publish_sample_count >=
IMU_CORRECTED_PUBLISH_EVERY_N_SAMPLES);
if(first_location || publish_due)
{
gps_system_uart_publish_fused_location(
&system->fused_solution);
system->last_fused_location_tick =
system->fused_solution.timestamp_ticks;
system->fused_publish_sample_count = 0u;
}
}
processed++;
}
return processed;
}
int main(void)
{
gps_system_state_t system = {0};
/* GPS and IMU are initialized independently. The application owns their
* interrupt connection and decides whether the ESKF path is enabled. */
initialize_sensor_fusion(&system);
gps_receiver_initialize(GPS_NAV_SOLVE_TIMER_TICKS);
bool imu_irq_attached = system.fusion_initialized &&
gps_receiver_attach_external_irq(BSP_PMOD_IRQ,
imu_sensor_irq_handler);
system.imu_running = imu_irq_attached && system.fusion_initialized &&
imu_sensor_start();
gps_receiver_start_acquisition();
gps_system_uart_publish_startup(
system.fusion_enabled, system.fusion_initialized,
system.imu_initialized, system.imu_running,
IMU_CORRECTED_PUBLISH_EVERY_N_SAMPLES);
bool imu_backlog_may_remain = false;
for(;;)
{
/* Skip WFI only while draining a bounded IMU backlog. Every batch
* still services GPS before more prediction work, so a continuously
* non-empty IMU queue cannot starve navigation epochs. */
if(!imu_backlog_may_remain)
{
/* GPS, IMU data-ready and the maintenance timer wake this loop. */
asm volatile("wfi");
}
/* Limit pre-GPS work to one sample for low event latency. */
(void)feed_pending_imu_to_eskf(
&system, IMU_SAMPLES_BEFORE_GPS_SERVICE);
/* GPS remains an independent receiver: events first, then coherent WLS. */
gps_receiver_process_pending_events();
system.gps_epoch_ready = gps_receiver_solve_ready_epoch(
&system.gps_solution, &system.gps_measurement_ticks);
/* WLS may take long enough for more IMU samples to arrive. Drain a
* small batch, then return to GPS service before continuing. */
unsigned int imu_after_gps = feed_pending_imu_to_eskf(
&system, IMU_SAMPLES_AFTER_GPS_SERVICE);
imu_backlog_may_remain =
(imu_after_gps == IMU_SAMPLES_AFTER_GPS_SERVICE);
if(system.gps_epoch_ready && system.gps_solution.solution_valid)
{
/* Raw WLS latitude/longitude remains visible even with ESKF disabled. */
gps_system_uart_publish_location("gps",
system.gps_solution.latitude_deg,
system.gps_solution.longitude_deg,
system.gps_solution.height_m,
&system.gps_solution);
/* This is the only GPS -> sensor-fusion hand-off in the application. */
bool gps_measurement_ready = system.fusion_initialized &&
gps_fusion_build_measurement(
&system.gps_solution,
system.gps_measurement_ticks,
&system.gps_for_fusion);
if(gps_measurement_ready)
{
/* Delayed GPS update is applied at its capture timestamp; newer
* IMU history is replayed to produce the current fused state. */
(void)navigation_fusion_push_gps(&system.gps_for_fusion);
}
}
if(system.fusion_initialized)
{
gps_system_uart_publish_periodic_fusion_status(
&system.fused_solution, &system.last_status_tick);
}
}
}