/// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- /* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * NavEKF based AHRS (Attitude Heading Reference System) interface for * ArduPilot * */ #include #include "AP_AHRS.h" #include #include #if AP_AHRS_NAVEKF_AVAILABLE extern const AP_HAL::HAL& hal; // return the smoothed gyro vector corrected for drift const Vector3f &AP_AHRS_NavEKF::get_gyro(void) const { if (!active_EKF_type()) { return AP_AHRS_DCM::get_gyro(); } return _gyro_estimate; } const Matrix3f &AP_AHRS_NavEKF::get_dcm_matrix(void) const { if (!active_EKF_type()) { return AP_AHRS_DCM::get_dcm_matrix(); } return _dcm_matrix; } const Vector3f &AP_AHRS_NavEKF::get_gyro_drift(void) const { if (!active_EKF_type()) { return AP_AHRS_DCM::get_gyro_drift(); } return _gyro_bias; } // reset the current gyro drift estimate // should be called if gyro offsets are recalculated void AP_AHRS_NavEKF::reset_gyro_drift(void) { // update DCM AP_AHRS_DCM::reset_gyro_drift(); // reset the EKF gyro bias states EKF1.resetGyroBias(); EKF2.resetGyroBias(); } void AP_AHRS_NavEKF::update(void) { update_DCM(); update_EKF1(); update_EKF2(); } void AP_AHRS_NavEKF::update_DCM(void) { // we need to restore the old DCM attitude values as these are // used internally in DCM to calculate error values for gyro drift // correction roll = _dcm_attitude.x; pitch = _dcm_attitude.y; yaw = _dcm_attitude.z; update_cd_values(); AP_AHRS_DCM::update(); // keep DCM attitude available for get_secondary_attitude() _dcm_attitude(roll, pitch, yaw); } void AP_AHRS_NavEKF::update_EKF1(void) { if (!ekf1_started) { // wait 1 second for DCM to output a valid tilt error estimate if (start_time_ms == 0) { start_time_ms = hal.scheduler->millis(); } if (hal.scheduler->millis() - start_time_ms > startup_delay_ms) { ekf1_started = EKF1.InitialiseFilterDynamic(); } } if (ekf1_started) { EKF1.UpdateFilter(); EKF1.getRotationBodyToNED(_dcm_matrix); if (active_EKF_type() == EKF_TYPE1) { Vector3f eulers; EKF1.getEulerAngles(eulers); roll = eulers.x; pitch = eulers.y; yaw = eulers.z; update_cd_values(); update_trig(); // keep _gyro_bias for get_gyro_drift() EKF1.getGyroBias(_gyro_bias); _gyro_bias = -_gyro_bias; // calculate corrected gryo estimate for get_gyro() _gyro_estimate.zero(); uint8_t healthy_count = 0; for (uint8_t i=0; i<_ins.get_gyro_count(); i++) { if (_ins.get_gyro_health(i) && healthy_count < 2) { _gyro_estimate += _ins.get_gyro(i); healthy_count++; } } if (healthy_count > 1) { _gyro_estimate /= healthy_count; } _gyro_estimate += _gyro_bias; float abias1, abias2; EKF1.getAccelZBias(abias1, abias2); // update _accel_ef_ekf for (uint8_t i=0; i<_ins.get_accel_count(); i++) { Vector3f accel = _ins.get_accel(i); if (i==0) { accel.z -= abias1; } else if (i==1) { accel.z -= abias2; } if (_ins.get_accel_health(i)) { _accel_ef_ekf[i] = _dcm_matrix * accel; } } if(_ins.use_accel(0) && _ins.use_accel(1)) { float IMU1_weighting; EKF1.getIMU1Weighting(IMU1_weighting); _accel_ef_ekf_blended = _accel_ef_ekf[0] * IMU1_weighting + _accel_ef_ekf[1] * (1.0f-IMU1_weighting); } else { _accel_ef_ekf_blended = _accel_ef_ekf[_ins.get_primary_accel()]; } } } } void AP_AHRS_NavEKF::update_EKF2(void) { if (!ekf2_started) { // wait 1 second for DCM to output a valid tilt error estimate if (start_time_ms == 0) { start_time_ms = hal.scheduler->millis(); } if (hal.scheduler->millis() - start_time_ms > startup_delay_ms) { ekf2_started = EKF2.InitialiseFilter(); } } if (ekf2_started) { EKF2.UpdateFilter(); EKF2.getRotationBodyToNED(_dcm_matrix); if (active_EKF_type() == EKF_TYPE2) { Vector3f eulers; EKF2.getEulerAngles(eulers); roll = eulers.x; pitch = eulers.y; yaw = eulers.z; update_cd_values(); update_trig(); // keep _gyro_bias for get_gyro_drift() EKF2.getGyroBias(_gyro_bias); _gyro_bias = -_gyro_bias; // calculate corrected gryo estimate for get_gyro() _gyro_estimate.zero(); uint8_t healthy_count = 0; for (uint8_t i=0; i<_ins.get_gyro_count(); i++) { if (_ins.get_gyro_health(i) && healthy_count < 2) { _gyro_estimate += _ins.get_gyro(i); healthy_count++; } } if (healthy_count > 1) { _gyro_estimate /= healthy_count; } _gyro_estimate += _gyro_bias; float abias; EKF2.getAccelZBias(abias); // This EKF uses the primary IMU // Eventually we will run a separate instance of the EKF for each IMU and do the selection and blending of EKF outputs upstream // update _accel_ef_ekf for (uint8_t i=0; i<_ins.get_accel_count(); i++) { Vector3f accel = _ins.get_accel(i); if (i==_ins.get_primary_accel()) { accel.z -= abias; } if (_ins.get_accel_health(i)) { _accel_ef_ekf[i] = _dcm_matrix * accel; } } _accel_ef_ekf_blended = _accel_ef_ekf[_ins.get_primary_accel()]; } } } // accelerometer values in the earth frame in m/s/s const Vector3f &AP_AHRS_NavEKF::get_accel_ef(uint8_t i) const { if (active_EKF_type() == EKF_TYPE_NONE) { return AP_AHRS_DCM::get_accel_ef(i); } return _accel_ef_ekf[i]; } // blended accelerometer values in the earth frame in m/s/s const Vector3f &AP_AHRS_NavEKF::get_accel_ef_blended(void) const { if (active_EKF_type() == EKF_TYPE_NONE) { return AP_AHRS_DCM::get_accel_ef_blended(); } return _accel_ef_ekf_blended; } void AP_AHRS_NavEKF::reset(bool recover_eulers) { AP_AHRS_DCM::reset(recover_eulers); if (ekf1_started) { ekf1_started = EKF1.InitialiseFilterBootstrap(); } if (ekf2_started) { ekf2_started = EKF2.InitialiseFilter(); } } // reset the current attitude, used on new IMU calibration void AP_AHRS_NavEKF::reset_attitude(const float &_roll, const float &_pitch, const float &_yaw) { AP_AHRS_DCM::reset_attitude(_roll, _pitch, _yaw); if (ekf1_started) { ekf1_started = EKF1.InitialiseFilterBootstrap(); } if (ekf2_started) { ekf2_started = EKF2.InitialiseFilter(); } } // dead-reckoning support bool AP_AHRS_NavEKF::get_position(struct Location &loc) const { Vector3f ned_pos; switch (active_EKF_type()) { case EKF_TYPE1: if (EKF1.getLLH(loc) && EKF1.getPosNED(ned_pos)) { // fixup altitude using relative position from AHRS home, not // EKF origin loc.alt = get_home().alt - ned_pos.z*100; return true; } break; case EKF_TYPE2: if (EKF2.getLLH(loc) && EKF2.getPosNED(ned_pos)) { // fixup altitude using relative position from AHRS home, not // EKF origin loc.alt = get_home().alt - ned_pos.z*100; return true; } break; default: break; } return AP_AHRS_DCM::get_position(loc); } // status reporting of estimated errors float AP_AHRS_NavEKF::get_error_rp(void) const { return AP_AHRS_DCM::get_error_rp(); } float AP_AHRS_NavEKF::get_error_yaw(void) const { return AP_AHRS_DCM::get_error_yaw(); } // return a wind estimation vector, in m/s Vector3f AP_AHRS_NavEKF::wind_estimate(void) { Vector3f wind; switch (active_EKF_type()) { case EKF_TYPE_NONE: wind = AP_AHRS_DCM::wind_estimate(); break; case EKF_TYPE1: EKF1.getWind(wind); break; case EKF_TYPE2: EKF2.getWind(wind); break; } return wind; } // return an airspeed estimate if available. return true // if we have an estimate bool AP_AHRS_NavEKF::airspeed_estimate(float *airspeed_ret) const { return AP_AHRS_DCM::airspeed_estimate(airspeed_ret); } // true if compass is being used bool AP_AHRS_NavEKF::use_compass(void) { switch (active_EKF_type()) { case EKF_TYPE_NONE: break; case EKF_TYPE1: return EKF1.use_compass(); case EKF_TYPE2: return EKF2.use_compass(); } return AP_AHRS_DCM::use_compass(); } // return secondary attitude solution if available, as eulers in radians bool AP_AHRS_NavEKF::get_secondary_attitude(Vector3f &eulers) { switch (active_EKF_type()) { case EKF_TYPE_NONE: // EKF is secondary EKF1.getEulerAngles(eulers); return ekf1_started; case EKF_TYPE1: case EKF_TYPE2: default: // DCM is secondary eulers = _dcm_attitude; return true; } } // return secondary position solution if available bool AP_AHRS_NavEKF::get_secondary_position(struct Location &loc) { switch (active_EKF_type()) { case EKF_TYPE_NONE: // EKF is secondary EKF1.getLLH(loc); return ekf1_started; case EKF_TYPE1: case EKF_TYPE2: default: // return DCM position AP_AHRS_DCM::get_position(loc); return true; } } // EKF has a better ground speed vector estimate Vector2f AP_AHRS_NavEKF::groundspeed_vector(void) { Vector3f vec; switch (active_EKF_type()) { case EKF_TYPE_NONE: return AP_AHRS_DCM::groundspeed_vector(); case EKF_TYPE1: default: EKF1.getVelNED(vec); return Vector2f(vec.x, vec.y); case EKF_TYPE2: EKF2.getVelNED(vec); return Vector2f(vec.x, vec.y); } } void AP_AHRS_NavEKF::set_home(const Location &loc) { AP_AHRS_DCM::set_home(loc); } // return true if inertial navigation is active bool AP_AHRS_NavEKF::have_inertial_nav(void) const { return active_EKF_type() != EKF_TYPE_NONE; } // return a ground velocity in meters/second, North/East/Down // order. Must only be called if have_inertial_nav() is true bool AP_AHRS_NavEKF::get_velocity_NED(Vector3f &vec) const { switch (active_EKF_type()) { case EKF_TYPE_NONE: return false; case EKF_TYPE1: default: EKF1.getVelNED(vec); return true; case EKF_TYPE2: EKF2.getVelNED(vec); return true; } } // return a relative ground position in meters/second, North/East/Down // order. Must only be called if have_inertial_nav() is true bool AP_AHRS_NavEKF::get_relative_position_NED(Vector3f &vec) const { switch (active_EKF_type()) { case EKF_TYPE_NONE: return false; case EKF_TYPE1: default: return EKF1.getPosNED(vec); case EKF_TYPE2: return EKF2.getPosNED(vec); } } /* canonicalise _ekf_type, forcing it to be 0, 1 or 2 */ uint8_t AP_AHRS_NavEKF::ekf_type(void) const { uint8_t type = _ekf_type; #if AHRS_EKF_USE_ALWAYS // on copters always use an EKF if (type == 0) { type = 1; } #endif // check for invalid type if (type > 2) { type = 1; } return type; } AP_AHRS_NavEKF::EKF_TYPE AP_AHRS_NavEKF::active_EKF_type(void) const { EKF_TYPE ret = EKF_TYPE_NONE; switch (ekf_type()) { case 0: return EKF_TYPE_NONE; case 1: { // do we have an EKF yet? if (!ekf1_started) { return EKF_TYPE_NONE; } #if AHRS_EKF_USE_ALWAYS uint8_t ekf_faults; EKF1.getFilterFaults(ekf_faults); if (ekf_faults == 0) { ret = EKF_TYPE1; } #else if (EKF1.healthy()) { ret = EKF_TYPE1; } #endif break; } case 2: { // do we have an EKF2 yet? if (!ekf2_started) { return EKF_TYPE_NONE; } #if AHRS_EKF_USE_ALWAYS uint8_t ekf2_faults; EKF2.getFilterFaults(ekf2_faults); if (ekf2_faults == 0) { ret = EKF_TYPE2; } #else if (EKF2.healthy()) { ret = EKF_TYPE2; } #endif break; } } if (ret != EKF_TYPE_NONE && (_vehicle_class == AHRS_VEHICLE_FIXED_WING || _vehicle_class == AHRS_VEHICLE_GROUND)) { nav_filter_status filt_state; if (ret == EKF_TYPE1) { EKF1.getFilterStatus(filt_state); } else { EKF2.getFilterStatus(filt_state); } if (hal.util->get_soft_armed() && !filt_state.flags.using_gps && _gps.status() >= AP_GPS::GPS_OK_FIX_3D) { // if the EKF is not fusing GPS and we have a 3D lock, then // plane and rover would prefer to use the GPS position from // DCM. This is a safety net while some issues with the EKF // get sorted out return EKF_TYPE_NONE; } if (hal.util->get_soft_armed() && filt_state.flags.const_pos_mode) { return EKF_TYPE_NONE; } if (!filt_state.flags.attitude || !filt_state.flags.horiz_vel || !filt_state.flags.vert_vel || !filt_state.flags.horiz_pos_abs || !filt_state.flags.vert_pos) { return EKF_TYPE_NONE; } } return ret; } /* check if the AHRS subsystem is healthy */ bool AP_AHRS_NavEKF::healthy(void) const { // If EKF is started we switch away if it reports unhealthy. This could be due to bad // sensor data. If EKF reversion is inhibited, we only switch across if the EKF encounters // an internal processing error, but not for bad sensor data. switch (ekf_type()) { case 0: return AP_AHRS_DCM::healthy(); case 1: { bool ret = ekf1_started && EKF1.healthy(); if (!ret) { return false; } if ((_vehicle_class == AHRS_VEHICLE_FIXED_WING || _vehicle_class == AHRS_VEHICLE_GROUND) && active_EKF_type() != EKF_TYPE1) { // on fixed wing we want to be using EKF to be considered // healthy if EKF is enabled return false; } return true; } case 2: { bool ret = ekf2_started && EKF2.healthy(); if (!ret) { return false; } if ((_vehicle_class == AHRS_VEHICLE_FIXED_WING || _vehicle_class == AHRS_VEHICLE_GROUND) && active_EKF_type() != EKF_TYPE2) { // on fixed wing we want to be using EKF to be considered // healthy if EKF is enabled return false; } return true; } } return AP_AHRS_DCM::healthy(); } void AP_AHRS_NavEKF::set_ekf_use(bool setting) { _ekf_type.set(setting?1:0); } // true if the AHRS has completed initialisation bool AP_AHRS_NavEKF::initialised(void) const { switch (ekf_type()) { case 0: return true; case 1: default: // initialisation complete 10sec after ekf has started return (ekf1_started && (hal.scheduler->millis() - start_time_ms > AP_AHRS_NAVEKF_SETTLE_TIME_MS)); case 2: // initialisation complete 10sec after ekf has started return (ekf2_started && (hal.scheduler->millis() - start_time_ms > AP_AHRS_NAVEKF_SETTLE_TIME_MS)); } }; // write optical flow data to EKF void AP_AHRS_NavEKF::writeOptFlowMeas(uint8_t &rawFlowQuality, Vector2f &rawFlowRates, Vector2f &rawGyroRates, uint32_t &msecFlowMeas) { EKF1.writeOptFlowMeas(rawFlowQuality, rawFlowRates, rawGyroRates, msecFlowMeas); EKF2.writeOptFlowMeas(rawFlowQuality, rawFlowRates, rawGyroRates, msecFlowMeas); } // inhibit GPS useage uint8_t AP_AHRS_NavEKF::setInhibitGPS(void) { switch (ekf_type()) { case 0: case 1: default: return EKF1.setInhibitGPS(); case 2: return EKF2.setInhibitGPS(); } } // get speed limit void AP_AHRS_NavEKF::getEkfControlLimits(float &ekfGndSpdLimit, float &ekfNavVelGainScaler) { switch (ekf_type()) { case 0: case 1: default: EKF1.getEkfControlLimits(ekfGndSpdLimit,ekfNavVelGainScaler); break; case 2: EKF2.getEkfControlLimits(ekfGndSpdLimit,ekfNavVelGainScaler); break; } } // get compass offset estimates // true if offsets are valid bool AP_AHRS_NavEKF::getMagOffsets(Vector3f &magOffsets) { switch (ekf_type()) { case 0: case 1: default: return EKF1.getMagOffsets(magOffsets); case 2: return EKF2.getMagOffsets(magOffsets); } } // report any reason for why the backend is refusing to initialise const char *AP_AHRS_NavEKF::prearm_failure_reason(void) const { switch (ekf_type()) { case 0: return nullptr; case 1: return EKF1.prearm_failure_reason(); case 2: // not implemented yet return nullptr; } return nullptr; } // return the amount of yaw angle change due to the last yaw angle reset in radians // returns true if a reset yaw angle has been updated and not queried // this function should not have more than one client bool AP_AHRS_NavEKF::getLastYawResetAngle(float &yawAng) { switch (ekf_type()) { case 1: return EKF1.getLastYawResetAngle(yawAng); case 2: return EKF2.getLastYawResetAngle(yawAng); } return false; } // Resets the baro so that it reads zero at the current height // Resets the EKF height to zero // Adjusts the EKf origin height so that the EKF height + origin height is the same as before // Returns true if the height datum reset has been performed // If using a range finder for height no reset is performed and it returns false bool AP_AHRS_NavEKF::resetHeightDatum(void) { switch (ekf_type()) { case 1: EKF2.resetHeightDatum(); return EKF1.resetHeightDatum(); case 2: EKF1.resetHeightDatum(); return EKF2.resetHeightDatum(); } return false; } #endif // AP_AHRS_NAVEKF_AVAILABLE