Path: blob/master/libraries/AP_AHRS/AP_AHRS_DCM.cpp
9314 views
/*1* APM_AHRS_DCM.cpp2*3* AHRS system using DCM matrices4*5* Based on DCM code by Doug Weibel, Jordi Munoz and Jose Julio. DIYDrones.com6*7* Adapted for the general ArduPilot AHRS interface by Andrew Tridgell89This program is free software: you can redistribute it and/or modify10it under the terms of the GNU General Public License as published by11the Free Software Foundation, either version 3 of the License, or12(at your option) any later version.1314This program is distributed in the hope that it will be useful,15but WITHOUT ANY WARRANTY; without even the implied warranty of16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the17GNU General Public License for more details.1819You should have received a copy of the GNU General Public License20along with this program. If not, see <http://www.gnu.org/licenses/>.21*/2223#include "AP_AHRS_config.h"2425#if AP_AHRS_DCM_ENABLED2627#include "AP_AHRS.h"28#include <AP_HAL/AP_HAL.h>29#include <GCS_MAVLink/GCS.h>30#include <AP_GPS/AP_GPS.h>31#include <AP_Baro/AP_Baro.h>32#include <AP_Compass/AP_Compass.h>33#include <AP_Logger/AP_Logger.h>34#include <AP_Vehicle/AP_Vehicle_Type.h>3536extern const AP_HAL::HAL& hal;3738// this is the speed in m/s above which we first get a yaw lock with39// the GPS40#define GPS_SPEED_MIN 34142// the limit (in degrees/second) beyond which we stop integrating43// omega_I. At larger spin rates the DCM PI controller can get 'dizzy'44// which results in false gyro drift. See45// http://gentlenav.googlecode.com/files/fastRotations.pdf46#define SPIN_RATE_LIMIT 204748// reset the current gyro drift estimate49// should be called if gyro offsets are recalculated50void51AP_AHRS_DCM::reset_gyro_drift(void)52{53_omega_I.zero();54_omega_I_sum.zero();55_omega_I_sum_time = 0;56}5758// run a full DCM update round59void60AP_AHRS_DCM::update()61{62AP_InertialSensor &_ins = AP::ins();6364// ask the IMU how much time this sensor reading represents65const float delta_t = _ins.get_delta_time();6667// if the update call took more than 0.2 seconds then discard it,68// otherwise we may move too far. This happens when arming motors69// in ArduCopter70if (delta_t > 0.2f) {71memset((void *)&_ra_sum[0], 0, sizeof(_ra_sum));72_ra_deltat = 0;73return;74}7576// Integrate the DCM matrix using gyro inputs77matrix_update();7879// Normalize the DCM matrix80normalize();8182// Perform drift correction83drift_correction(delta_t);8485// paranoid check for bad values in the DCM matrix86check_matrix();8788// calculate the euler angles and DCM matrix which will be used89// for high level navigation control. Apply trim such that a90// positive trim value results in a positive vehicle rotation91// about that axis (ie a negative offset)92_body_dcm_matrix = _dcm_matrix * AP::ahrs().get_rotation_vehicle_body_to_autopilot_body();93_body_dcm_matrix.to_euler(&roll, &pitch, &yaw);9495// pre-calculate some trig for CPU purposes:96_cos_yaw = cosf(yaw);97_sin_yaw = sinf(yaw);9899backup_attitude();100101// remember the last origin for fallback support102IGNORE_RETURN(AP::ahrs().get_origin(last_origin));103104#if HAL_LOGGING_ENABLED105const uint32_t now_ms = AP_HAL::millis();106if (now_ms - last_log_ms >= 100) {107// log DCM at 10Hz108last_log_ms = now_ms;109110// @LoggerMessage: DCM111// @Description: DCM Estimator Data112// @Field: TimeUS: Time since system startup113// @Field: Roll: estimated roll114// @Field: Pitch: estimated pitch115// @Field: Yaw: estimated yaw116// @Field: ErrRP: lowest estimated gyro drift error117// @Field: ErrYaw: difference between measured yaw and DCM yaw estimate118// @Field: VWN: wind velocity, to-the-North component119// @Field: VWE: wind velocity, to-the-East component120// @Field: VWD: wind velocity, Up-to-Down component121// @Field: SAs: synthetic (equivalent) airspeed122AP::logger().WriteStreaming(123"DCM",124"TimeUS," "Roll," "Pitch," "Yaw," "ErrRP," "ErrYaw," "VWN," "VWE," "VWD," "SAs",125"s" "d" "d" "d" "d" "h" "n" "n" "n" "n",126"F" "0" "0" "0" "0" "0" "0" "0" "0" "0",127"Q" "f" "f" "f" "f" "f" "f" "f" "f" "f",128AP_HAL::micros64(),129degrees(roll),130degrees(pitch),131wrap_360(degrees(yaw)),132get_error_rp(),133get_error_yaw(),134_wind.x,135_wind.y,136_wind.z,137_last_airspeed_TAS / get_EAS2TAS()138);139}140#endif // HAL_LOGGING_ENABLED141}142143void AP_AHRS_DCM::get_results(AP_AHRS_Backend::Estimates &results)144{145results.roll_rad = roll;146results.pitch_rad = pitch;147results.yaw_rad = yaw;148149results.dcm_matrix = _body_dcm_matrix;150151// quaternion is derived from transformation matrix:152results.quaternion.from_rotation_matrix(_dcm_matrix);153154results.attitude_valid = true;155156results.gyro_estimate = _omega;157results.gyro_drift = _omega_I;158results.accel_ef = _accel_ef;159160results.velocity_NED_valid = get_velocity_NED(results.velocity_NED);161results.vert_pos_rate_D_valid = get_vert_pos_rate_D(results.vert_pos_rate_D);162163results.location_valid = get_location(results.location);164}165166/*167backup attitude to persistent_data for use in watchdog reset168*/169void AP_AHRS_DCM::backup_attitude(void)170{171AP_HAL::Util::PersistentData &pd = hal.util->persistent_data;172pd.roll_rad = roll;173pd.pitch_rad = pitch;174pd.yaw_rad = yaw;175}176177// update the DCM matrix using only the gyros178void AP_AHRS_DCM::matrix_update(void)179{180// use only the primary gyro so our bias estimate is valid, allowing us to return the right filtered gyro181// for rate controllers182const auto &_ins = AP::ins();183Vector3f delta_angle;184float dangle_dt;185if (_ins.get_delta_angle(delta_angle, dangle_dt) && dangle_dt > 0) {186_omega = delta_angle / dangle_dt;187_omega += _omega_I;188_dcm_matrix.rotate((_omega + _omega_P + _omega_yaw_P) * dangle_dt);189}190191// now update _omega from the filtered value from the primary IMU. We need to use192// the primary IMU as the notch filters will only be running on one IMU193194// note that we do not include the P terms in _omega. This is195// because the spin_rate is calculated from _omega.length(),196// and including the P terms would give positive feedback into197// the _P_gain() calculation, which can lead to a very large P198// value199_omega = _ins.get_gyro() + _omega_I;200}201202203/*204* reset the DCM matrix and omega. Used on ground start, and on205* extreme errors in the matrix206*/207void208AP_AHRS_DCM::reset(bool recover_eulers)209{210// reset the integration terms211_omega_I.zero();212_omega_P.zero();213_omega_yaw_P.zero();214_omega.zero();215216// if the caller wants us to try to recover to the current217// attitude then calculate the dcm matrix from the current218// roll/pitch/yaw values219if (hal.util->was_watchdog_reset() && AP_HAL::millis() < 10000) {220const AP_HAL::Util::PersistentData &pd = hal.util->persistent_data;221roll = pd.roll_rad;222pitch = pd.pitch_rad;223yaw = pd.yaw_rad;224_dcm_matrix.from_euler(roll, pitch, yaw);225GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Restored watchdog attitude %.0f %.0f %.0f",226degrees(roll), degrees(pitch), degrees(yaw));227} else if (recover_eulers && !isnan(roll) && !isnan(pitch) && !isnan(yaw)) {228_dcm_matrix.from_euler(roll, pitch, yaw);229} else {230231// Use the measured accel due to gravity to calculate an initial232// roll and pitch estimate233234AP_InertialSensor &_ins = AP::ins();235236// Get body frame accel vector237Vector3f initAccVec = _ins.get_accel();238uint8_t counter = 0;239240// the first vector may be invalid as the filter starts up241while ((initAccVec.length() < 9.0f || initAccVec.length() > 11) && counter++ < 20) {242_ins.wait_for_sample();243_ins.update();244initAccVec = _ins.get_accel();245}246247// normalise the acceleration vector248if (initAccVec.length() > 5.0f) {249// calculate initial pitch angle250pitch = atan2f(initAccVec.x, norm(initAccVec.y, initAccVec.z));251// calculate initial roll angle252roll = atan2f(-initAccVec.y, -initAccVec.z);253} else {254// If we can't use the accel vector, then align flat255roll = 0.0f;256pitch = 0.0f;257}258_dcm_matrix.from_euler(roll, pitch, 0);259260}261262// pre-calculate some trig for CPU purposes:263_cos_yaw = cosf(yaw);264_sin_yaw = sinf(yaw);265266_last_startup_ms = AP_HAL::millis();267}268269/*270* check the DCM matrix for pathological values271*/272void273AP_AHRS_DCM::check_matrix(void)274{275if (_dcm_matrix.is_nan()) {276//Serial.printf("ERROR: DCM matrix NAN\n");277AP_AHRS_DCM::reset(true);278return;279}280// some DCM matrix values can lead to an out of range error in281// the pitch calculation via asin(). These NaN values can282// feed back into the rest of the DCM matrix via the283// error_course value.284if (!(_dcm_matrix.c.x < 1.0f &&285_dcm_matrix.c.x > -1.0f)) {286// We have an invalid matrix. Force a normalisation.287normalize();288289if (_dcm_matrix.is_nan() ||290fabsf(_dcm_matrix.c.x) > 10.0) {291// See Issue #20284: regarding the selection of 10.0 for DCM reset292// This won't be lowered without evidence of an issue or mathematical proof & testing of a lower bound293294// normalisation didn't fix the problem! We're295// in real trouble. All we can do is reset296//Serial.printf("ERROR: DCM matrix error. _dcm_matrix.c.x=%f\n",297// _dcm_matrix.c.x);298AP_AHRS_DCM::reset(true);299}300}301}302303// renormalise one vector component of the DCM matrix304// this will return false if renormalization fails305bool306AP_AHRS_DCM::renorm(Vector3f const &a, Vector3f &result)307{308// numerical errors will slowly build up over time in DCM,309// causing inaccuracies. We can keep ahead of those errors310// using the renormalization technique from the DCM IMU paper311// (see equations 18 to 21).312313// For APM we don't bother with the taylor expansion314// optimisation from the paper as on our 2560 CPU the cost of315// the sqrt() is 44 microseconds, and the small time saving of316// the taylor expansion is not worth the potential of317// additional error buildup.318319// Note that we can get significant renormalisation values320// when we have a larger delta_t due to a glitch elsewhere in321// APM, such as a I2c timeout or a set of EEPROM writes. While322// we would like to avoid these if possible, if it does happen323// we don't want to compound the error by making DCM less324// accurate.325326const float renorm_val = 1.0f / a.length();327328// keep the average for reporting329_renorm_val_sum += renorm_val;330_renorm_val_count++;331332if (!(renorm_val < 2.0f && renorm_val > 0.5f)) {333// this is larger than it should get - log it as a warning334if (!(renorm_val < 1.0e6f && renorm_val > 1.0e-6f)) {335// we are getting values which are way out of336// range, we will reset the matrix and hope we337// can recover our attitude using drift338// correction before we hit the ground!339//Serial.printf("ERROR: DCM renormalisation error. renorm_val=%f\n",340// renorm_val);341return false;342}343}344345result = a * renorm_val;346return true;347}348349/*************************************************350* Direction Cosine Matrix IMU: Theory351* William Premerlani and Paul Bizard352*353* Numerical errors will gradually reduce the orthogonality conditions expressed by equation 5354* to approximations rather than identities. In effect, the axes in the two frames of reference no355* longer describe a rigid body. Fortunately, numerical error accumulates very slowly, so it is a356* simple matter to stay ahead of it.357* We call the process of enforcing the orthogonality conditions: renormalization.358*/359void360AP_AHRS_DCM::normalize(void)361{362const float error = _dcm_matrix.a * _dcm_matrix.b; // eq.18363364const Vector3f t0 = _dcm_matrix.a - (_dcm_matrix.b * (0.5f * error)); // eq.19365const Vector3f t1 = _dcm_matrix.b - (_dcm_matrix.a * (0.5f * error)); // eq.19366const Vector3f t2 = t0 % t1; // c= a x b // eq.20367368if (!renorm(t0, _dcm_matrix.a) ||369!renorm(t1, _dcm_matrix.b) ||370!renorm(t2, _dcm_matrix.c)) {371// Our solution is blowing up and we will force back372// to last euler angles373_last_failure_ms = AP_HAL::millis();374AP_AHRS_DCM::reset(true);375}376}377378379// produce a yaw error value. The returned value is proportional380// to sin() of the current heading error in earth frame381float382AP_AHRS_DCM::yaw_error_compass(Compass &compass)383{384const Vector3f &mag = compass.get_field();385// get the mag vector in the earth frame386Vector2f rb = _dcm_matrix.mulXY(mag);387388if (rb.length() < FLT_EPSILON) {389return 0.0f;390}391392rb.normalize();393if (rb.is_inf()) {394// not a valid vector395return 0.0f;396}397398// update vector holding earths magnetic field (if required)399if( !is_equal(_last_declination, compass.get_declination()) ) {400_last_declination = compass.get_declination();401_mag_earth.x = cosf(_last_declination);402_mag_earth.y = sinf(_last_declination);403}404405// calculate the error term in earth frame406// calculate the Z component of the cross product of rb and _mag_earth407return rb % _mag_earth;408}409410// the _P_gain raises the gain of the PI controller411// when we are spinning fast. See the fastRotations412// paper from Bill.413float414AP_AHRS_DCM::_P_gain(float spin_rate)415{416if (spin_rate < radians(50)) {417return 1.0f;418}419if (spin_rate > radians(500)) {420return 10.0f;421}422return spin_rate/radians(50);423}424425// _yaw_gain reduces the gain of the PI controller applied to heading errors426// when observability from change of velocity is good (eg changing speed or turning)427// This reduces unwanted roll and pitch coupling due to compass errors for planes.428// High levels of noise on _accel_ef will cause the gain to drop and could lead to429// increased heading drift during straight and level flight, however some gain is430// always available. TODO check the necessity of adding adjustable acc threshold431// and/or filtering accelerations before getting magnitude432float433AP_AHRS_DCM::_yaw_gain(void) const434{435const float VdotEFmag = _accel_ef.xy().length();436437if (VdotEFmag <= 4.0f) {438return 0.2f*(4.5f - VdotEFmag);439}440return 0.1f;441}442443444// return true if we have and should use GPS445bool AP_AHRS_DCM::have_gps(void) const446{447if (_gps_use == GPSUse::Disable || AP::gps().status() <= AP_GPS::NO_FIX) {448return false;449}450return true;451}452453/*454when we are getting the initial attitude we want faster gains so455that if the board starts upside down we quickly approach the right456attitude.457We don't want to keep those high gains for too long though as high P458gains cause slow gyro offset learning. So we keep the high gains for459a maximum of 20 seconds460*/461bool AP_AHRS_DCM::use_fast_gains(void) const462{463return !hal.util->get_soft_armed() && (AP_HAL::millis() - _last_startup_ms) < 20000U;464}465466467// return true if we should use the compass for yaw correction468bool AP_AHRS_DCM::use_compass(void)469{470const Compass &compass = AP::compass();471472if (!compass.use_for_yaw()) {473// no compass available474return false;475}476if (!AP::ahrs().get_fly_forward() || !have_gps()) {477// we don't have any alternative to the compass478return true;479}480if (AP::gps().ground_speed() < GPS_SPEED_MIN) {481// we are not going fast enough to use the GPS482return true;483}484485// if the current yaw differs from the GPS yaw by more than 45486// degrees and the estimated wind speed is less than 80% of the487// ground speed, then switch to GPS navigation. This will help488// prevent flyaways with very bad compass offsets489const float error = fabsf(wrap_180(degrees(yaw) - AP::gps().ground_course()));490if (error > 45 && _wind.length() < AP::gps().ground_speed()*0.8f) {491if (AP_HAL::millis() - _last_consistent_heading > 2000) {492// start using the GPS for heading if the compass has been493// inconsistent with the GPS for 2 seconds494return false;495}496} else {497_last_consistent_heading = AP_HAL::millis();498}499500// use the compass501return true;502}503504// yaw drift correction using the compass or GPS505// this function produces the _omega_yaw_P vector, and also506// contributes to the _omega_I.z long term yaw drift estimate507void508AP_AHRS_DCM::drift_correction_yaw(void)509{510bool new_value = false;511float yaw_error;512float yaw_deltat;513514const AP_GPS &_gps = AP::gps();515516Compass &compass = AP::compass();517518#if COMPASS_CAL_ENABLED519if (compass.is_calibrating()) {520// don't do any yaw correction while calibrating521return;522}523#endif524525if (AP_AHRS_DCM::use_compass()) {526/*527we are using compass for yaw528*/529if (compass.last_update_usec() != _compass_last_update) {530yaw_deltat = (compass.last_update_usec() - _compass_last_update) * 1.0e-6f;531_compass_last_update = compass.last_update_usec();532// we force an additional compass read()533// here. This has the effect of throwing away534// the first compass value, which can be bad535if (!have_initial_yaw && compass.read()) {536const float heading = compass.calculate_heading(_dcm_matrix);537_dcm_matrix.from_euler(roll, pitch, heading);538_omega_yaw_P.zero();539have_initial_yaw = true;540}541new_value = true;542yaw_error = yaw_error_compass(compass);543544// also update the _gps_last_update, so if we later545// disable the compass due to significant yaw error we546// don't suddenly change yaw with a reset547_gps_last_update = _gps.last_fix_time_ms();548}549} else if (AP::ahrs().get_fly_forward() && have_gps()) {550/*551we are using GPS for yaw552*/553if (_gps.last_fix_time_ms() != _gps_last_update &&554_gps.ground_speed() >= GPS_SPEED_MIN) {555yaw_deltat = (_gps.last_fix_time_ms() - _gps_last_update) * 1.0e-3f;556_gps_last_update = _gps.last_fix_time_ms();557new_value = true;558const float gps_course_rad = radians(_gps.ground_course());559const float yaw_error_rad = wrap_PI(gps_course_rad - yaw);560yaw_error = sinf(yaw_error_rad);561562/* reset yaw to match GPS heading under any of the563following 3 conditions:5645651) if we have reached GPS_SPEED_MIN and have never had566yaw information before5675682) if the last time we got yaw information from the GPS569is more than 20 seconds ago, which means we may have570suffered from considerable gyro drift5715723) if we are over 3*GPS_SPEED_MIN (which means 9m/s)573and our yaw error is over 60 degrees, which means very574poor yaw. This can happen on bungee launch when the575operator pulls back the plane rapidly enough then on576release the GPS heading changes very rapidly577*/578if (!have_initial_yaw ||579yaw_deltat > 20 ||580(_gps.ground_speed() >= 3*GPS_SPEED_MIN && fabsf(yaw_error_rad) >= 1.047f)) {581// reset DCM matrix based on current yaw582_dcm_matrix.from_euler(roll, pitch, gps_course_rad);583_omega_yaw_P.zero();584have_initial_yaw = true;585yaw_error = 0;586}587}588}589590if (!new_value) {591// we don't have any new yaw information592// slowly decay _omega_yaw_P to cope with loss593// of our yaw source594_omega_yaw_P *= 0.97f;595return;596}597598// convert the error vector to body frame599const float error_z = _dcm_matrix.c.z * yaw_error;600601// the spin rate changes the P gain, and disables the602// integration at higher rates603const float spin_rate = _omega.length();604605// sanity check _kp_yaw606if (_kp_yaw < AP_AHRS_YAW_P_MIN) {607_kp_yaw.set(AP_AHRS_YAW_P_MIN);608}609610// update the proportional control to drag the611// yaw back to the right value. We use a gain612// that depends on the spin rate. See the fastRotations.pdf613// paper from Bill Premerlani614// We also adjust the gain depending on the rate of change of horizontal velocity which615// is proportional to how observable the heading is from the accelerations and GPS velocity616// The acceleration derived heading will be more reliable in turns than compass or GPS617618_omega_yaw_P.z = error_z * _P_gain(spin_rate) * _kp_yaw * _yaw_gain();619if (use_fast_gains()) {620_omega_yaw_P.z *= 8;621}622623// don't update the drift term if we lost the yaw reference624// for more than 2 seconds625if (yaw_deltat < 2.0f && spin_rate < radians(SPIN_RATE_LIMIT)) {626// also add to the I term627_omega_I_sum.z += error_z * _ki_yaw * yaw_deltat;628}629630_error_yaw = 0.8f * _error_yaw + 0.2f * fabsf(yaw_error);631}632633634/**635return an accel vector delayed by AHRS_ACCEL_DELAY samples for a636specific accelerometer instance637*/638Vector3f AP_AHRS_DCM::ra_delayed(uint8_t instance, const Vector3f &ra)639{640// get the old element, and then fill it with the new element641const Vector3f ret = _ra_delay_buffer[instance];642_ra_delay_buffer[instance] = ra;643if (ret.is_zero()) {644// use the current vector if the previous vector is exactly645// zero. This prevents an error on initialisation646return ra;647}648return ret;649}650651/* returns true if attitude should be corrected from GPS-derived652* velocity-deltas. We turn this off for Copter and other similar653* vehicles while the vehicle is disarmed to avoid the HUD bobbing654* around while the vehicle is disarmed.655*/656bool AP_AHRS_DCM::should_correct_centrifugal() const657{658#if APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduSub) || APM_BUILD_TYPE(APM_BUILD_Blimp)659return hal.util->get_soft_armed();660#endif661662return true;663}664665// perform drift correction. This function aims to update _omega_P and666// _omega_I with our best estimate of the short term and long term667// gyro error. The _omega_P value is what pulls our attitude solution668// back towards the reference vector quickly. The _omega_I term is an669// attempt to learn the long term drift rate of the gyros.670//671// This drift correction implementation is based on a paper672// by Bill Premerlani from here:673// http://gentlenav.googlecode.com/files/RollPitchDriftCompensation.pdf674void675AP_AHRS_DCM::drift_correction(float deltat)676{677Vector3f velocity;678uint32_t last_correction_time;679680// perform yaw drift correction if we have a new yaw reference681// vector682drift_correction_yaw();683684const AP_InertialSensor &_ins = AP::ins();685686// rotate accelerometer values into the earth frame687for (uint8_t i=0; i<_ins.get_accel_count(); i++) {688if (_ins.use_accel(i)) {689/*690by using get_delta_velocity() instead of get_accel() the691accel value is sampled over the right time delta for692each sensor, which prevents an aliasing effect693*/694Vector3f delta_velocity;695float delta_velocity_dt;696_ins.get_delta_velocity(i, delta_velocity, delta_velocity_dt);697if (delta_velocity_dt > 0) {698Vector3f accel_ef = _dcm_matrix * (delta_velocity / delta_velocity_dt);699// integrate the accel vector in the earth frame between GPS readings700_ra_sum[i] += accel_ef * deltat;701}702}703}704705// set _accel_ef_blended based on filtered accel706_accel_ef = _dcm_matrix * _ins.get_accel();707708// keep a sum of the deltat values, so we know how much time709// we have integrated over710_ra_deltat += deltat;711712const AP_GPS &_gps = AP::gps();713const bool fly_forward = AP::ahrs().get_fly_forward();714715if (!have_gps() ||716_gps.status() < AP_GPS::GPS_OK_FIX_3D ||717_gps.num_sats() < _gps_minsats) {718// no GPS, or not a good lock. From experience we need at719// least 6 satellites to get a really reliable velocity number720// from the GPS.721//722// As a fallback we use the fixed wing acceleration correction723// if we have an airspeed estimate (which we only have if724// _fly_forward is set), otherwise no correction725if (_ra_deltat < 0.2f) {726// not enough time has accumulated727return;728}729730float airspeed_TAS = _last_airspeed_TAS;731#if AP_AIRSPEED_ENABLED732if (airspeed_sensor_enabled()) {733airspeed_TAS = AP::airspeed()->get_airspeed() * get_EAS2TAS();734}735#endif736737// use airspeed to estimate our ground velocity in738// earth frame by subtracting the wind739velocity = _dcm_matrix.colx() * airspeed_TAS;740741// add in wind estimate742velocity += _wind;743744last_correction_time = AP_HAL::millis();745_have_gps_lock = false;746} else {747if (_gps.last_fix_time_ms() == _ra_sum_start) {748// we don't have a new GPS fix - nothing more to do749return;750}751velocity = _gps.velocity();752last_correction_time = _gps.last_fix_time_ms();753if (_have_gps_lock == false) {754// if we didn't have GPS lock in the last drift755// correction interval then set the velocities equal756_last_velocity = velocity;757}758_have_gps_lock = true;759760// keep last airspeed estimate for dead-reckoning purposes761Vector3f airspeed = velocity - _wind;762763// rotate vector to body frame764airspeed = _body_dcm_matrix.mul_transpose(airspeed);765766// take positive component in X direction. This mimics a pitot767// tube768_last_airspeed_TAS = MAX(airspeed.x, 0);769}770771if (have_gps()) {772// use GPS for positioning with any fix, even a 2D fix773_last_lat = _gps.location().lat;774_last_lng = _gps.location().lng;775_last_pos_ms = AP_HAL::millis();776_position_offset_north = 0;777_position_offset_east = 0;778779// once we have a single GPS lock, we can update using780// dead-reckoning from then on781_have_position = true;782} else {783// update dead-reckoning position estimate784_position_offset_north += velocity.x * _ra_deltat;785_position_offset_east += velocity.y * _ra_deltat;786}787788// see if this is our first time through - in which case we789// just setup the start times and return790if (_ra_sum_start == 0) {791_ra_sum_start = last_correction_time;792_last_velocity = velocity;793return;794}795796// equation 9: get the corrected acceleration vector in earth frame. Units797// are m/s/s798Vector3f GA_e(0.0f, 0.0f, -1.0f);799800if (_ra_deltat <= 0) {801// waiting for more data802return;803}804805bool using_gps_corrections = false;806float ra_scale = 1.0f/(_ra_deltat*GRAVITY_MSS);807808if (should_correct_centrifugal() && (_have_gps_lock || fly_forward)) {809const float v_scale = gps_gain.get() * ra_scale;810const Vector3f vdelta = (velocity - _last_velocity) * v_scale;811GA_e += vdelta;812GA_e.normalize();813if (GA_e.is_inf()) {814// wait for some non-zero acceleration information815_last_failure_ms = AP_HAL::millis();816return;817}818using_gps_corrections = true;819}820821// calculate the error term in earth frame.822// we do this for each available accelerometer then pick the823// accelerometer that leads to the smallest error term. This takes824// advantage of the different sample rates on different825// accelerometers to dramatically reduce the impact of aliasing826// due to harmonics of vibrations that match closely the sampling827// rate of our accelerometers. On the Pixhawk we have the LSM303D828// running at 800Hz and the MPU6000 running at 1kHz, by combining829// the two the effects of aliasing are greatly reduced.830Vector3f error[INS_MAX_INSTANCES];831float error_dirn[INS_MAX_INSTANCES];832Vector3f GA_b[INS_MAX_INSTANCES];833int8_t besti = -1;834float best_error = 0;835for (uint8_t i=0; i<_ins.get_accel_count(); i++) {836if (!_ins.get_accel_health(i)) {837// only use healthy sensors838continue;839}840_ra_sum[i] *= ra_scale;841842// get the delayed ra_sum to match the GPS lag843if (using_gps_corrections) {844GA_b[i] = ra_delayed(i, _ra_sum[i]);845} else {846GA_b[i] = _ra_sum[i];847}848if (GA_b[i].is_zero()) {849// wait for some non-zero acceleration information850continue;851}852GA_b[i].normalize();853if (GA_b[i].is_inf()) {854// wait for some non-zero acceleration information855continue;856}857error[i] = GA_b[i] % GA_e;858// Take dot product to catch case vectors are opposite sign and parallel859error_dirn[i] = GA_b[i] * GA_e;860const float error_length = error[i].length();861if (besti == -1 || error_length < best_error) {862besti = i;863best_error = error_length;864}865// Catch case where orientation is 180 degrees out866if (error_dirn[besti] < 0.0f) {867best_error = 1.0f;868}869870}871872if (besti == -1) {873// no healthy accelerometers!874_last_failure_ms = AP_HAL::millis();875return;876}877878_active_accel_instance = besti;879880#define YAW_INDEPENDENT_DRIFT_CORRECTION 0881#if YAW_INDEPENDENT_DRIFT_CORRECTION882// step 2 calculate earth_error_Z883const float earth_error_Z = error.z;884885// equation 10886const float tilt = GA_e.xy().length();887888// equation 11889const float theta = atan2f(GA_b[besti].y, GA_b[besti].x);890891// equation 12892const Vector3f GA_e2 = Vector3f(cosf(theta)*tilt, sinf(theta)*tilt, GA_e.z);893894// step 6895error = GA_b[besti] % GA_e2;896error.z = earth_error_Z;897#endif // YAW_INDEPENDENT_DRIFT_CORRECTION898899// to reduce the impact of two competing yaw controllers, we900// reduce the impact of the gps/accelerometers on yaw when we are901// flat, but still allow for yaw correction using the902// accelerometers at high roll angles as long as we have a GPS903if (AP_AHRS_DCM::use_compass()) {904if (have_gps() && is_equal(gps_gain.get(), 1.0f)) {905error[besti].z *= sinf(fabsf(roll));906} else {907error[besti].z = 0;908}909}910911// if ins is unhealthy then stop attitude drift correction and912// hope the gyros are OK for a while. Just slowly reduce _omega_P913// to prevent previous bad accels from throwing us off914if (!_ins.healthy()) {915error[besti].zero();916} else {917// convert the error term to body frame918error[besti] = _dcm_matrix.mul_transpose(error[besti]);919}920921if (error[besti].is_nan() || error[besti].is_inf()) {922// don't allow bad values923check_matrix();924_last_failure_ms = AP_HAL::millis();925return;926}927928_error_rp = 0.8f * _error_rp + 0.2f * best_error;929930// base the P gain on the spin rate931const float spin_rate = _omega.length();932933// sanity check _kp value934if (_kp < AP_AHRS_RP_P_MIN) {935_kp.set(AP_AHRS_RP_P_MIN);936}937938// we now want to calculate _omega_P and _omega_I. The939// _omega_P value is what drags us quickly to the940// accelerometer reading.941_omega_P = error[besti] * _P_gain(spin_rate) * _kp;942if (use_fast_gains()) {943_omega_P *= 8;944}945946if (fly_forward && _gps.status() >= AP_GPS::GPS_OK_FIX_2D &&947_gps.ground_speed() < GPS_SPEED_MIN &&948_ins.get_accel().x >= 7 &&949pitch > radians(-30) && pitch < radians(30)) {950// assume we are in a launch acceleration, and reduce the951// rp gain by 50% to reduce the impact of GPS lag on952// takeoff attitude when using a catapult953_omega_P *= 0.5f;954}955956// accumulate some integrator error957if (spin_rate < radians(SPIN_RATE_LIMIT)) {958_omega_I_sum += error[besti] * _ki * _ra_deltat;959_omega_I_sum_time += _ra_deltat;960}961962if (_omega_I_sum_time >= 5) {963// limit the rate of change of omega_I to the hardware964// reported maximum gyro drift rate. This ensures that965// short term errors don't cause a buildup of omega_I966// beyond the physical limits of the device967const float change_limit = AP::ins().get_gyro_drift_rate() * _omega_I_sum_time;968_omega_I_sum.x = constrain_float(_omega_I_sum.x, -change_limit, change_limit);969_omega_I_sum.y = constrain_float(_omega_I_sum.y, -change_limit, change_limit);970_omega_I_sum.z = constrain_float(_omega_I_sum.z, -change_limit, change_limit);971_omega_I += _omega_I_sum;972_omega_I_sum.zero();973_omega_I_sum_time = 0;974}975976// zero our accumulator ready for the next GPS step977memset((void *)&_ra_sum[0], 0, sizeof(_ra_sum));978_ra_deltat = 0;979_ra_sum_start = last_correction_time;980981// remember the velocity for next time982_last_velocity = velocity;983}984985986// update our wind speed estimate987void AP_AHRS_DCM::estimate_wind(void)988{989if (!AP::ahrs().get_wind_estimation_enabled()) {990return;991}992const Vector3f &velocity = _last_velocity;993994// this is based on the wind speed estimation code from MatrixPilot by995// Bill Premerlani. Adaption for ArduPilot by Jon Challinger996// See http://gentlenav.googlecode.com/files/WindEstimation.pdf997const Vector3f fuselageDirection = _dcm_matrix.colx();998const Vector3f fuselageDirectionDiff = fuselageDirection - _last_fuse;999const uint32_t now = AP_HAL::millis();10001001// scrap our data and start over if we're taking too long to get a direction change1002if (now - _last_wind_time > 10000) {1003_last_wind_time = now;1004_last_fuse = fuselageDirection;1005_last_vel = velocity;1006return;1007}10081009float diff_length = fuselageDirectionDiff.length();1010if (diff_length > 0.2f) {1011// when turning, use the attitude response to estimate1012// wind speed1013const Vector3f velocityDiff = velocity - _last_vel;10141015// estimate airspeed it using equation 61016const float V = velocityDiff.length() / diff_length;10171018const Vector3f fuselageDirectionSum = fuselageDirection + _last_fuse;1019const Vector3f velocitySum = velocity + _last_vel;10201021_last_fuse = fuselageDirection;1022_last_vel = velocity;10231024const float theta = atan2f(velocityDiff.y, velocityDiff.x) - atan2f(fuselageDirectionDiff.y, fuselageDirectionDiff.x);1025const float sintheta = sinf(theta);1026const float costheta = cosf(theta);10271028Vector3f wind{1029velocitySum.x - V * (costheta * fuselageDirectionSum.x - sintheta * fuselageDirectionSum.y),1030velocitySum.y - V * (sintheta * fuselageDirectionSum.x + costheta * fuselageDirectionSum.y),1031velocitySum.z - V * fuselageDirectionSum.z1032};1033wind *= 0.5f;10341035if (wind.length() < _wind.length() + 20) {1036_wind = _wind * 0.95f + wind * 0.05f;1037}10381039_last_wind_time = now;10401041return;1042}10431044#if AP_AIRSPEED_ENABLED1045if (now - _last_wind_time > 2000 && airspeed_sensor_enabled()) {1046// when flying straight use airspeed to get wind estimate if available1047const Vector3f airspeed = _dcm_matrix.colx() * AP::airspeed()->get_airspeed();1048const Vector3f wind = velocity - (airspeed * get_EAS2TAS());1049_wind = _wind * 0.92f + wind * 0.08f;1050}1051#endif1052}10531054#ifdef AP_AHRS_EXTERNAL_WIND_ESTIMATE_ENABLED1055void AP_AHRS_DCM::set_external_wind_estimate(float speed, float direction) {1056_wind.x = -cosf(radians(direction)) * speed;1057_wind.y = -sinf(radians(direction)) * speed;1058_wind.z = 0;1059}1060#endif10611062// return our current position estimate using1063// dead-reckoning or GPS1064bool AP_AHRS_DCM::get_location(Location &loc) const1065{1066loc.lat = _last_lat;1067loc.lng = _last_lng;1068const auto &baro = AP::baro();1069const auto &gps = AP::gps();1070int32_t alt_cm;1071if (_gps_use == GPSUse::EnableWithHeight &&1072gps.status() >= AP_GPS::GPS_OK_FIX_3D) {1073alt_cm = gps.location().alt;1074} else {1075alt_cm = baro.get_altitude() * 100 + AP::ahrs().get_home().alt;1076}1077loc.set_alt_cm(alt_cm, Location::AltFrame::ABSOLUTE);1078loc.offset(_position_offset_north, _position_offset_east);1079if (_have_position) {1080const uint32_t now = AP_HAL::millis();1081float dt = 0;1082gps.get_lag(dt);1083dt += constrain_float((now - _last_pos_ms) * 0.001, 0, 0.5);1084Vector2f dpos = _last_velocity.xy() * dt;1085loc.offset(dpos.x, dpos.y);1086}1087return _have_position;1088}10891090bool AP_AHRS_DCM::airspeed_EAS(float &airspeed_ret) const1091{1092#if AP_AIRSPEED_ENABLED1093const auto *airspeed = AP::airspeed();1094if (airspeed != nullptr) {1095return airspeed_EAS(airspeed->get_primary(), airspeed_ret);1096}1097#endif1098// airspeed_estimate will also make this nullptr check and act1099// appropriately when we call it with a dummy sensor ID.1100return airspeed_EAS(0, airspeed_ret);1101}11021103// return an (equivalent) airspeed estimate:1104// - from a real sensor if available1105// - otherwise from a GPS-derived wind-triangle estimate (if GPS available)1106// - otherwise from a cached wind-triangle estimate value (but returning false)1107bool AP_AHRS_DCM::airspeed_EAS(uint8_t airspeed_index, float &airspeed_ret) const1108{1109// airspeed_ret: will always be filled-in by get_unconstrained_airspeed_EAS which fills in airspeed_ret in this order:1110// airspeed as filled-in by an enabled airspeed sensor1111// if no airspeed sensor: airspeed estimated using the GPS speed & wind_speed_estimation1112// Or if none of the above, fills-in using the previous airspeed estimate1113// Return false: if we are using the previous airspeed estimate1114if (!get_unconstrained_airspeed_EAS(airspeed_index, airspeed_ret)) {1115return false;1116}11171118const float _wind_max = AP::ahrs().get_max_wind();1119if (_wind_max > 0 && AP::gps().status() >= AP_GPS::GPS_OK_FIX_2D) {1120// constrain the airspeed by the ground speed1121// and AHRS_WIND_MAX1122const float gnd_speed = AP::gps().ground_speed();1123float true_airspeed = airspeed_ret * get_EAS2TAS();1124true_airspeed = constrain_float(true_airspeed,1125gnd_speed - _wind_max,1126gnd_speed + _wind_max);1127airspeed_ret = true_airspeed / get_EAS2TAS();1128}11291130return true;1131}11321133// airspeed_ret: will always be filled-in by get_unconstrained_airspeed_EAS which fills in airspeed_ret in this order:1134// airspeed as filled-in by an enabled airspeed sensor1135// if no airspeed sensor: airspeed estimated using the GPS speed & wind_speed_estimation1136// Or if none of the above, fills-in using the previous airspeed estimate1137// Return false: if we are using the previous airspeed estimate1138bool AP_AHRS_DCM::get_unconstrained_airspeed_EAS(uint8_t airspeed_index, float &airspeed_ret) const1139{1140#if AP_AIRSPEED_ENABLED1141if (airspeed_sensor_enabled(airspeed_index)) {1142airspeed_ret = AP::airspeed()->get_airspeed(airspeed_index);1143return true;1144}1145#endif11461147if (AP::ahrs().get_wind_estimation_enabled() && have_gps()) {1148// estimated via GPS speed and wind1149airspeed_ret = _last_airspeed_TAS * get_TAS2EAS();1150return true;1151}11521153// Else give the last estimate, but return false.1154// This is used by the dead-reckoning code1155airspeed_ret = _last_airspeed_TAS * get_TAS2EAS();1156return false;1157}11581159/*1160check if the AHRS subsystem is healthy1161*/1162bool AP_AHRS_DCM::healthy(void) const1163{1164// consider ourselves healthy if there have been no failures for 5 seconds1165return (_last_failure_ms == 0 || AP_HAL::millis() - _last_failure_ms > 5000);1166}11671168/*1169return NED velocity if we have GPS lock1170*/1171bool AP_AHRS_DCM::get_velocity_NED(Vector3f &vec) const1172{1173const AP_GPS &_gps = AP::gps();1174if (_gps.status() < AP_GPS::GPS_OK_FIX_3D) {1175return false;1176}1177vec = _gps.velocity();1178return true;1179}11801181// return a ground speed estimate in m/s1182Vector2f AP_AHRS_DCM::groundspeed_vector(void)1183{1184// Generate estimate of ground speed vector using air data system1185Vector2f gndVelADS;1186Vector2f gndVelGPS;1187float airspeed = 0;1188const bool gotAirspeed = airspeed_TAS(airspeed);1189const bool gotGPS = (AP::gps().status() >= AP_GPS::GPS_OK_FIX_2D);1190if (gotAirspeed) {1191const Vector2f airspeed_vector{_cos_yaw * airspeed, _sin_yaw * airspeed};1192Vector3f wind;1193UNUSED_RESULT(wind_estimate(wind));1194gndVelADS = airspeed_vector + wind.xy();1195}11961197// Generate estimate of ground speed vector using GPS1198if (gotGPS) {1199const float cog = radians(AP::gps().ground_course());1200gndVelGPS = Vector2f(cosf(cog), sinf(cog)) * AP::gps().ground_speed();1201}1202// If both ADS and GPS data is available, apply a complementary filter1203if (gotAirspeed && gotGPS) {1204// The LPF is applied to the GPS and the HPF is applied to the air data estimate1205// before the two are summed1206//Define filter coefficients1207// alpha and beta must sum to one1208// beta = dt/Tau, where1209// dt = filter time step (0.1 sec if called by nav loop)1210// Tau = cross-over time constant (nominal 2 seconds)1211// More lag on GPS requires Tau to be bigger, less lag allows it to be smaller1212// To-Do - set Tau as a function of GPS lag.1213const float alpha = 1.0f - beta;1214// Run LP filters1215_lp = gndVelGPS * beta + _lp * alpha;1216// Run HP filters1217_hp = (gndVelADS - _lastGndVelADS) + _hp * alpha;1218// Save the current ADS ground vector for the next time step1219_lastGndVelADS = gndVelADS;1220// Sum the HP and LP filter outputs1221return _hp + _lp;1222}1223// Only ADS data is available return ADS estimate1224if (gotAirspeed && !gotGPS) {1225return gndVelADS;1226}1227// Only GPS data is available so return GPS estimate1228if (!gotAirspeed && gotGPS) {1229return gndVelGPS;1230}12311232if (airspeed > 0) {1233// we have a rough airspeed, and we have a yaw. For1234// dead-reckoning purposes we can create a estimated1235// groundspeed vector1236Vector2f ret{_cos_yaw, _sin_yaw};1237ret *= airspeed;1238// adjust for estimated wind1239Vector3f wind;1240UNUSED_RESULT(wind_estimate(wind));1241ret.x += wind.x;1242ret.y += wind.y;1243return ret;1244}12451246return Vector2f(0.0f, 0.0f);1247}12481249// Get a derivative of the vertical position in m/s which is kinematically consistent with the vertical position is required by some control loops.1250// This is different to the vertical velocity from the EKF which is not always consistent with the vertical position due to the various errors that are being corrected for.1251bool AP_AHRS_DCM::get_vert_pos_rate_D(float &velocity) const1252{1253Vector3f velned;1254if (get_velocity_NED(velned)) {1255velocity = velned.z;1256return true;1257} else if (AP::baro().healthy()) {1258velocity = -AP::baro().get_climb_rate();1259return true;1260}1261return false;1262}12631264// returns false if we fail arming checks, in which case the buffer will be populated with a failure message1265// requires_position should be true if horizontal position configuration should be checked (not used)1266bool AP_AHRS_DCM::pre_arm_check(bool requires_position, char *failure_msg, uint8_t failure_msg_len) const1267{1268if (!healthy()) {1269hal.util->snprintf(failure_msg, failure_msg_len, "Not healthy");1270return false;1271}1272return true;1273}12741275/*1276relative-origin functions for fallback in AP_InertialNav1277*/1278bool AP_AHRS_DCM::get_origin(Location &ret) const1279{1280ret = last_origin;1281if (ret.is_zero()) {1282// use home if we never have had an origin1283ret = AP::ahrs().get_home();1284}1285return !ret.is_zero();1286}12871288bool AP_AHRS_DCM::get_relative_position_NED_origin(Vector3p &posNED) const1289{1290Location origin;1291if (!AP_AHRS_DCM::get_origin(origin)) {1292return false;1293}1294Location loc;1295if (!AP_AHRS_DCM::get_location(loc)) {1296return false;1297}1298posNED = origin.get_distance_NED_postype(loc);1299return true;1300}13011302bool AP_AHRS_DCM::get_relative_position_NE_origin(Vector2p &posNE) const1303{1304Vector3p posNED;1305if (!AP_AHRS_DCM::get_relative_position_NED_origin(posNED)) {1306return false;1307}1308posNE = posNED.xy();1309return true;1310}13111312bool AP_AHRS_DCM::get_relative_position_D_origin(postype_t &posD) const1313{1314Vector3p posNED;1315if (!AP_AHRS_DCM::get_relative_position_NED_origin(posNED)) {1316return false;1317}1318posD = posNED.z;1319return true;1320}13211322void AP_AHRS_DCM::send_ekf_status_report(GCS_MAVLINK &link) const1323{1324}13251326// return true if DCM has a yaw source available1327bool AP_AHRS_DCM::yaw_source_available(void) const1328{1329return AP::compass().use_for_yaw();1330}13311332void AP_AHRS_DCM::get_control_limits(float &ekfGndSpdLimit, float &ekfNavVelGainScaler) const1333{1334// lower gains in VTOL controllers when flying on DCM1335ekfGndSpdLimit = 50.0;1336ekfNavVelGainScaler = 0.5;1337}13381339#endif // AP_AHRS_DCM_ENABLED134013411342