#include "ins.h" #include "pios.h" #include "ahrs_spi_comm.h" #include "insgps.h" #include "CoordinateConversions.h" #define DEG_TO_RAD (M_PI / 180.0) #define RAD_TO_DEG (180.0 / M_PI) #define INSGPS_GPS_TIMEOUT 2 /* 2 seconds triggers reinit of position */ #define INSGPS_GPS_MINSAT 6 /* 2 seconds triggers reinit of position */ #define INSGPS_GPS_MINPDOP 3.5 /* minimum PDOP for postition updates */ /* If GPS is more than this distance on any dimension then wait a few updates */ /* and reinitialize there */ #define INSGPS_GPS_FAR 10 //! Contains the data from the mag sensor chip extern struct mag_sensor mag_data; //! Contains the data from the accelerometer extern struct accel_sensor accel_data; //! Contains the data from the gyro extern struct gyro_sensor gyro_data; //! Conains the current estimate of the attitude extern struct attitude_solution attitude_data; //! Contains data from the altitude sensor extern struct altitude_sensor altitude_data; //! Contains data from the GPS (via the SPI link) extern struct gps_sensor gps_data; //! Offset correction of barometric alt, to match gps data static float baro_offset = 0; extern void send_calibration(void); extern void send_attitude(void); extern void send_velocity(void); extern void send_position(void); extern volatile int8_t ahrs_algorithm; extern void get_accel_gyro_data(); extern void get_mag_data(); /* INS functions */ /** * @brief Update the EKF when in outdoor mode. The primary difference is using the GPS values. */ uint32_t total_far_count = 0; uint32_t relocated = 0; void ins_outdoor_update() { static uint32_t ins_last_time; float gyro[3], accel[3], vel[3]; float dT; uint16_t sensors; static uint32_t gps_far_count = 0; dT = PIOS_DELAY_DiffuS(ins_last_time) / 1e6; ins_last_time = PIOS_DELAY_GetRaw(); // This should only happen at start up or at mode switches if(dT > 0.01) dT = 0.01; // format data for INS algo gyro[0] = gyro_data.filtered.x; gyro[1] = gyro_data.filtered.y; gyro[2] = gyro_data.filtered.z; accel[0] = accel_data.filtered.x, accel[1] = accel_data.filtered.y, accel[2] = accel_data.filtered.z, INSStatePrediction(gyro, accel, dT); attitude_data.quaternion.q1 = Nav.q[0]; attitude_data.quaternion.q2 = Nav.q[1]; attitude_data.quaternion.q3 = Nav.q[2]; attitude_data.quaternion.q4 = Nav.q[3]; send_attitude(); // get message out quickly INSCovariancePrediction(dT); PositionActualData positionActual; PositionActualGet(&positionActual); positionActual.North = Nav.Pos[0]; positionActual.East = Nav.Pos[1]; positionActual.Down = Nav.Pos[2]; PositionActualSet(&positionActual); VelocityActualData velocityActual; VelocityActualGet(&velocityActual); velocityActual.North = Nav.Vel[0]; velocityActual.East = Nav.Vel[1]; velocityActual.Down = Nav.Vel[2]; VelocityActualSet(&velocityActual); sensors = 0; if (gps_data.updated) { vel[0] = gps_data.groundspeed * cos(gps_data.heading * DEG_TO_RAD); vel[1] = gps_data.groundspeed * sin(gps_data.heading * DEG_TO_RAD); vel[2] = 0; if (abs(gps_data.NED[0] - Nav.Pos[0]) > INSGPS_GPS_FAR || abs(gps_data.NED[1] - Nav.Pos[1]) > INSGPS_GPS_FAR || abs(gps_data.NED[2] - Nav.Pos[2]) > INSGPS_GPS_FAR || abs(vel[0] - Nav.Vel[0]) > INSGPS_GPS_FAR || abs(vel[1] - Nav.Vel[1]) > INSGPS_GPS_FAR) { gps_far_count++; total_far_count++; gps_data.updated = false; if(gps_far_count > 30) { INSPosVelReset(gps_data.NED,vel); relocated++; } } else { sensors |= HORIZ_SENSORS | POS_SENSORS; /* * When using gps need to make sure that barometer is brought into NED frame * we should try and see if the altitude from the home location is good enough * to use for the offset but for now starting with this conservative filter */ if(fabs(gps_data.NED[2] + (altitude_data.altitude - baro_offset)) > 10) { baro_offset = gps_data.NED[2] + altitude_data.altitude; } else { /* IIR filter with 100 second or so tau to keep them crudely in the same frame */ baro_offset = baro_offset * 0.999 + (gps_data.NED[2] + altitude_data.altitude) * 0.001; } gps_data.updated = false; } } if(mag_data.updated) { sensors |= MAG_SENSORS; mag_data.updated = false; } if(altitude_data.updated) { sensors |= BARO_SENSOR; altitude_data.updated = false; } /* * TODO: Need to add a general sanity check for all the inputs to make sure their kosher * although probably should occur within INS itself */ INSCorrection(mag_data.scaled.axis, gps_data.NED, vel, altitude_data.altitude - baro_offset, sensors); if(fabs(Nav.gyro_bias[0]) > 0.1 || fabs(Nav.gyro_bias[1]) > 0.1 || fabs(Nav.gyro_bias[2]) > 0.1) { float zeros[3] = {0,0,0}; INSSetGyroBias(zeros); } } /** * @brief Update the EKF when in indoor mode */ void ins_indoor_update() { static uint32_t updated_without_gps = 0; float gyro[3], accel[3]; float zeros[3] = {0, 0, 0}; static uint32_t ins_last_time = 0; uint16_t sensors = 0; float dT; dT = PIOS_DELAY_DiffuS(ins_last_time) / 1e6; ins_last_time = PIOS_DELAY_GetRaw(); // This should only happen at start up or at mode switches if(dT > 0.01) dT = 0.01; // format data for INS algo gyro[0] = gyro_data.filtered.x; gyro[1] = gyro_data.filtered.y; gyro[2] = gyro_data.filtered.z; accel[0] = accel_data.filtered.x, accel[1] = accel_data.filtered.y, accel[2] = accel_data.filtered.z, INSStatePrediction(gyro, accel, dT); attitude_data.quaternion.q1 = Nav.q[0]; attitude_data.quaternion.q2 = Nav.q[1]; attitude_data.quaternion.q3 = Nav.q[2]; attitude_data.quaternion.q4 = Nav.q[3]; send_attitude(); // get message out quickly INSCovariancePrediction(dT); /* Indoors, update with zero position and velocity and high covariance */ sensors = HORIZ_SENSORS | VERT_SENSORS; if(mag_data.updated && (ahrs_algorithm == INSSETTINGS_ALGORITHM_INSGPS_INDOOR)) { sensors |= MAG_SENSORS; mag_data.updated = false; } if(altitude_data.updated) { sensors |= BARO_SENSOR; altitude_data.updated = false; } if(gps_data.updated) { PositionActualData positionActual; PositionActualGet(&positionActual); positionActual.North = gps_data.NED[0]; positionActual.East = gps_data.NED[1]; positionActual.Down = Nav.Pos[2]; PositionActualSet(&positionActual); VelocityActualData velocityActual; VelocityActualGet(&velocityActual); velocityActual.North = gps_data.groundspeed * cos(gps_data.heading * DEG_TO_RAD); velocityActual.East = gps_data.groundspeed * sin(gps_data.heading * DEG_TO_RAD); velocityActual.Down = Nav.Vel[2]; VelocityActualSet(&velocityActual); updated_without_gps = 0; gps_data.updated = false; } else { PositionActualData positionActual; PositionActualGet(&positionActual); VelocityActualData velocityActual; VelocityActualGet(&velocityActual); positionActual.Down = Nav.Pos[2]; velocityActual.Down = Nav.Vel[2]; if(updated_without_gps > 500) { // After 2-3 seconds without a GPS update set velocity estimate to NAN positionActual.North = NAN; positionActual.East = NAN; velocityActual.North = NAN; velocityActual.East = NAN; } else updated_without_gps++; PositionActualSet(&positionActual); VelocityActualSet(&velocityActual); } /* * TODO: Need to add a general sanity check for all the inputs to make sure their kosher * although probably should occur within INS itself */ INSCorrection(mag_data.scaled.axis, zeros, zeros, altitude_data.altitude, sensors); if(fabs(Nav.gyro_bias[0]) > 0.1 || fabs(Nav.gyro_bias[1]) > 0.1 || fabs(Nav.gyro_bias[2]) > 0.1) { float zeros[3] = {0,0,0}; INSSetGyroBias(zeros); } } /** * @brief Initialize the EKF assuming stationary */ bool inited = false; float init_q[4]; void ins_init_algorithm() { inited = true; float Rbe[3][3], q[4], accels[3], rpy[3], mag; float ge[3]={0,0,-9.81}, zeros[3]={0,0,0}, Pdiag[16]={25,25,25,5,5,5,1e-5,1e-5,1e-5,1e-5,1e-5,1e-5,1e-5,1e-4,1e-4,1e-4}; bool using_mags, using_gps; INSGPSInit(); HomeLocationData home; HomeLocationGet(&home); accels[0]=accel_data.filtered.x; accels[1]=accel_data.filtered.y; accels[2]=accel_data.filtered.z; using_mags = (ahrs_algorithm == INSSETTINGS_ALGORITHM_INSGPS_OUTDOOR) || (ahrs_algorithm == INSSETTINGS_ALGORITHM_INSGPS_INDOOR); using_mags &= (home.Be[0] != 0) || (home.Be[1] != 0) || (home.Be[2] != 0); /* only use mags when valid home location */ using_gps = (ahrs_algorithm == INSSETTINGS_ALGORITHM_INSGPS_OUTDOOR) && (gps_data.quality >= INSGPS_GPS_MINSAT); /* Block till a data update */ get_accel_gyro_data(); /* Ensure we get mag data in a timely manner */ uint16_t fail_count = 50; // 50 at 200 Hz is up to 0.25 sec while(using_mags && !mag_data.updated && fail_count--) { get_mag_data(); get_accel_gyro_data(); AhrsPoll(); PIOS_DELAY_WaituS(2000); } using_mags &= mag_data.updated; if (using_mags) { RotFrom2Vectors(accels, ge, mag_data.scaled.axis, home.Be, Rbe); R2Quaternion(Rbe,q); if (using_gps) INSSetState(gps_data.NED, zeros, q, zeros, zeros); else INSSetState(zeros, zeros, q, zeros, zeros); } else { // assume yaw = 0 mag = VectorMagnitude(accels); rpy[1] = asinf(-accels[0]/mag); rpy[0] = atan2(accels[1]/mag,accels[2]/mag); rpy[2] = 0; RPY2Quaternion(rpy,init_q); if (using_gps) INSSetState(gps_data.NED, zeros, init_q, zeros, zeros); else { for (uint32_t i = 0; i < 5; i++) { INSSetState(zeros, zeros, init_q, zeros, zeros); ins_indoor_update(); } } } INSResetP(Pdiag); // TODO: include initial estimate of gyro bias? }