LCOV - code coverage report
Current view: top level - AP_NavEKF3 - AP_NavEKF3_MagFusion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 646 686 94.2 %
Date: 2026-09-09 11:02:08 Functions: 15 15 100.0 %

          Line data    Source code
       1             : #include <AP_HAL/AP_HAL.h>
       2             : 
       3             : #include "AP_NavEKF3.h"
       4             : #include "AP_NavEKF3_core.h"
       5             : 
       6             : #include <GCS_MAVLink/GCS.h>
       7             : #include <AP_DAL/AP_DAL.h>
       8             : #include <AP_GPS/AP_GPS_config.h>
       9             : 
      10             : // minimum GPS horizontal speed required to use GPS ground course for yaw alignment (m/s)
      11             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
      12             :   #define GPS_VEL_YAW_ALIGN_MIN_SPD 5.0F
      13             : #else
      14             :   #define GPS_VEL_YAW_ALIGN_MIN_SPD 1.0F
      15             : #endif
      16             : 
      17             : /********************************************************
      18             : *                   RESET FUNCTIONS                     *
      19             : ********************************************************/
      20             : 
      21             : // Control reset of yaw and magnetic field states
      22     2787354 : void NavEKF3_core::controlMagYawReset()
      23             : {
      24             : 
      25             :     // Vehicles that can use a zero sideslip assumption (Planes) are a special case
      26             :     // They can use the GPS velocity to recover from bad initial compass data
      27             :     // This allows recovery for heading alignment errors due to compass faults
      28     2787354 :     if (assume_zero_sideslip() && (!finalInflightYawInit || !yawAlignComplete) && inFlight) {
      29         738 :         gpsYawResetRequest = true;
      30         738 :         return;
      31             :     } else {
      32     2786616 :         gpsYawResetRequest = false;
      33             :     }
      34             : 
      35             :     // Quaternion and delta rotation vector that are re-used for different calculations
      36     2786616 :     Vector3F deltaRotVecTemp;
      37     2786616 :     QuaternionF deltaQuatTemp;
      38             : 
      39             :     bool flightResetAllowed = false;
      40             :     bool initialResetAllowed = false;
      41     2786616 :     if (!finalInflightYawInit) {
      42             :         // Use a quaternion division to calculate the delta quaternion between the rotation at the current and last time
      43     1116719 :         deltaQuatTemp = stateStruct.quat / prevQuatMagReset;
      44     1116719 :         prevQuatMagReset = stateStruct.quat;
      45             : 
      46             :         // convert the quaternion to a rotation vector and find its length
      47     1116719 :         deltaQuatTemp.to_axis_angle(deltaRotVecTemp);
      48             : 
      49             :         // check if the spin rate is OK - high spin rates can cause angular alignment errors
      50     1116719 :         bool angRateOK = deltaRotVecTemp.length() < 0.1745f;
      51             : 
      52     1116719 :         initialResetAllowed = angRateOK && tiltAlignComplete;
      53     1116719 :         flightResetAllowed = angRateOK && !onGround;
      54             : 
      55             :     }
      56             : 
      57             :     // reset the limit on the number of magnetic anomaly resets for each takeoff
      58     2786616 :     if (onGround) {
      59      855769 :         magYawAnomallyCount = 0;
      60             :     }
      61             : 
      62             :     // Check if conditions for a interim or final yaw/mag reset are met
      63             :     bool finalResetRequest = false;
      64             :     bool interimResetRequest = false;
      65     2786616 :     if (flightResetAllowed && !assume_zero_sideslip()) {
      66             : #if APM_BUILD_TYPE(APM_BUILD_ArduSub)
      67             :         // for sub, we'd like to be far enough away from metal structures like docks and vessels
      68             :         // diving 0.5m is reasonable for both open water and pools
      69       15222 :         finalResetRequest = (stateStruct.position.z  - posDownAtTakeoff) > EKF3_MAG_FINAL_RESET_ALT_SUB;
      70             : #else
      71             :         // check that we have reached a height where ground magnetic interference effects are insignificant
      72             :         // and can perform a final reset of the yaw and field states
      73      220177 :         finalResetRequest = (stateStruct.position.z  - posDownAtTakeoff) < -EKF3_MAG_FINAL_RESET_ALT;
      74             : #endif
      75             : 
      76             :         // check for increasing height
      77      235399 :         bool hgtIncreasing = (posDownAtLastMagReset-stateStruct.position.z) > 0.5f;
      78      235399 :         ftype yawInnovIncrease = fabsF(innovYaw) - fabsF(yawInnovAtLastMagReset);
      79             : 
      80             :         // check for increasing yaw innovations
      81      235399 :         bool yawInnovIncreasing = yawInnovIncrease > 0.25f;
      82             : 
      83             :         // check that the yaw innovations haven't been caused by a large change in attitude
      84      235399 :         deltaQuatTemp = quatAtLastMagReset / stateStruct.quat;
      85      235399 :         deltaQuatTemp.to_axis_angle(deltaRotVecTemp);
      86      235399 :         bool largeAngleChange = deltaRotVecTemp.length() > yawInnovIncrease;
      87             : 
      88             :         // if yaw innovations and height have increased and we haven't rotated much
      89             :         // then we are climbing away from a ground based magnetic anomaly and need to reset
      90      235399 :         interimResetRequest = !finalInflightYawInit
      91      235399 :                                 && !finalResetRequest
      92      234136 :                                 && (magYawAnomallyCount < MAG_ANOMALY_RESET_MAX)
      93             :                                 && hgtIncreasing
      94      234136 :                                 && yawInnovIncreasing
      95      235400 :                                 && !largeAngleChange;
      96             :     }
      97             : 
      98             :     // an initial reset is required if we have not yet aligned the yaw angle
      99     2786616 :     bool initialResetRequest = initialResetAllowed && !yawAlignComplete;
     100             : 
     101             :     // a combined yaw angle and magnetic field reset can be initiated by:
     102     5573232 :     magYawResetRequest = magYawResetRequest || // an external request
     103     2783697 :             initialResetRequest || // an initial alignment performed by all vehicle types using magnetometer
     104     5570313 :             interimResetRequest || // an interim alignment required to recover from ground based magnetic anomaly
     105             :             finalResetRequest; // the final reset when we have achieved enough height to be in stable magnetic field environment
     106             : 
     107             :     // Perform a reset of magnetic field states and reset yaw to corrected magnetic heading
     108     2786616 :     if (magYawResetRequest && use_compass()) {
     109             :         // send initial alignment status to console
     110        4182 :         if (!yawAlignComplete) {
     111        2703 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u MAG%u initial yaw alignment complete",(unsigned)imu_index, (unsigned)magSelectIndex);
     112             :         }
     113             : 
     114             :         // set yaw from a single mag sample
     115        4182 :         setYawFromMag();
     116             : 
     117             :         // send in-flight yaw alignment status to console
     118        4182 :         if (finalResetRequest) {
     119        1263 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u MAG%u in-flight yaw alignment complete",(unsigned)imu_index, (unsigned)magSelectIndex);
     120        2919 :         } else if (interimResetRequest) {
     121           0 :             magYawAnomallyCount++;
     122           0 :             GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "EKF3 IMU%u MAG%u ground mag anomaly, yaw re-aligned",(unsigned)imu_index, (unsigned)magSelectIndex);
     123             :         }
     124             : 
     125             :         // clear the complete flags if an interim reset has been performed to allow subsequent
     126             :         // and final reset to occur
     127        4182 :         if (interimResetRequest) {
     128           0 :             finalInflightYawInit = false;
     129           0 :             finalInflightMagInit = false;
     130             :         }
     131             : 
     132             :         // mag states
     133        4182 :         if (!magFieldLearned) {
     134        4182 :             resetMagFieldStates();
     135             :         }
     136             :     }
     137             : 
     138     2786616 :     if (magStateResetRequest) {
     139          23 :         resetMagFieldStates();
     140             :     }
     141             : }
     142             : 
     143             : // this function is used to do a forced re-alignment of the yaw angle to align with the horizontal velocity
     144             : // vector from GPS. It is used to align the yaw angle after launch or takeoff.
     145       24356 : void NavEKF3_core::realignYawGPS(bool emergency_reset)
     146             : {
     147             :     // get quaternion from existing filter states and calculate roll, pitch and yaw angles
     148       24356 :     Vector3F eulerAngles;
     149       24356 :     stateStruct.quat.to_euler(eulerAngles.x, eulerAngles.y, eulerAngles.z);
     150             : 
     151       24356 :     if (gpsDataDelayed.vel.xy().length_squared() > sq(GPS_VEL_YAW_ALIGN_MIN_SPD)) {
     152             :         // calculate course yaw angle
     153        1576 :         ftype velYaw = atan2F(stateStruct.velocity.y,stateStruct.velocity.x);
     154             : 
     155             :         // calculate course yaw angle from GPS velocity
     156        1576 :         ftype gpsYaw = atan2F(gpsDataDelayed.vel.y,gpsDataDelayed.vel.x);
     157             : 
     158             :         // Check the yaw angles for consistency
     159        1576 :         ftype yawErr = MAX(fabsF(wrap_PI(gpsYaw - velYaw)),fabsF(wrap_PI(gpsYaw - eulerAngles.z)));
     160             : 
     161             :         // If the angles disagree by more than 45 degrees and GPS innovations are large or no previous yaw alignment, we declare the magnetic yaw as bad
     162        1576 :         bool badMagYaw = ((yawErr > 0.7854f) && (velTestRatio > 1.0f) && (PV_AidingMode == AID_ABSOLUTE)) || !yawAlignComplete;
     163             : 
     164             :         // get yaw variance from GPS speed uncertainty
     165        1576 :         const ftype gpsVelAcc = fmaxF(gpsSpdAccuracy, ftype(frontend->_gpsHorizVelNoise));
     166        1576 :         const ftype gps_yaw_variance = sq(asinF(constrain_float(gpsVelAcc/gpsDataDelayed.vel.xy().length(), -1.0F, 1.0F)));
     167        1576 :         if (gps_yaw_variance < sq(radians(GPS_VEL_YAW_ALIGN_MAX_ANG_ERR))) {
     168        1544 :             yawAlignGpsValidCount++;
     169             :         } else {
     170          32 :             yawAlignGpsValidCount = 0;
     171             :         }
     172             : 
     173             :         // correct yaw angle using GPS ground course if compass yaw bad
     174        1576 :         if (badMagYaw) {
     175             :             // attempt to use EKF-GSF estimate if available as it is more robust to GPS glitches
     176             :             // by default fly forward vehicles use ground course for initial yaw unless the GSF is explicitly selected as the yaw source
     177         154 :             const bool useGSF = !assume_zero_sideslip() || (yaw_source_last == AP_NavEKF_Source::SourceYaw::GSF);
     178          54 :             if (useGSF && EKFGSF_resetMainFilterYaw(emergency_reset)) {
     179           2 :                 return;
     180             :             }
     181             : 
     182         152 :             if (yawAlignGpsValidCount >= GPS_VEL_YAW_ALIGN_COUNT_THRESHOLD) {
     183          24 :                 yawAlignGpsValidCount = 0;
     184             :                 // keep roll and pitch and reset yaw
     185             :                 rotationOrder order;
     186          24 :                 bestRotationOrder(order);
     187          24 :                 resetQuatStateYawOnly(gpsYaw, gps_yaw_variance, order);
     188             : 
     189             :                 // reset the velocity and position states as they will be inaccurate due to bad yaw
     190          24 :                 ResetVelocity(resetDataSource::GPS);
     191          24 :                 ResetPosition(resetDataSource::GPS);
     192             : 
     193             :                 // send yaw alignment information to console
     194          24 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u yaw aligned to GPS velocity",(unsigned)imu_index);
     195             : 
     196          24 :                 if (use_compass()) {
     197             :                     // request a mag field reset which may enable us to use the magnetometer if the previous fault was due to bad initialisation
     198           0 :                     magStateResetRequest = true;
     199             :                     // clear the all sensors failed status so that the magnetometers sensors get a second chance now that we are flying
     200           0 :                     allMagSensorsFailed = false;
     201             :                 }
     202             :             }
     203        1422 :         } else if (yawAlignGpsValidCount >= GPS_VEL_YAW_ALIGN_COUNT_THRESHOLD) {
     204             :                 // There is no need to do a yaw reset
     205         284 :                 yawAlignGpsValidCount = 0;
     206         284 :                 recordYawResetsCompleted();
     207             :         }
     208             :     } else {
     209       22780 :         yawAlignGpsValidCount = 0;
     210             :     }
     211             : }
     212             : 
     213             : /*
     214             :   build the body-to-earth rotation matrix at the current state attitude with
     215             :   yaw set to zero, using the given Euler rotation order. Optionally returns
     216             :   the yaw angle removed. Returns false if the rotation order is not supported
     217             : */
     218     1088306 : bool NavEKF3_core::buildTbnZeroYaw(rotationOrder order, Matrix3F &Tbn, ftype *yawAng) const
     219             : {
     220     1088306 :     switch (order) {
     221      867049 :     case rotationOrder::TAIT_BRYAN_321: {
     222      867049 :         Vector3F euler321;
     223      867049 :         stateStruct.quat.to_euler(euler321.x, euler321.y, euler321.z);
     224      867049 :         Tbn.from_euler(euler321.x, euler321.y, 0.0f);
     225      867049 :         if (yawAng != nullptr) {
     226      862625 :             *yawAng = euler321.z;
     227             :         }
     228             :         return true;
     229             :     }
     230      221257 :     case rotationOrder::TAIT_BRYAN_312: {
     231      221257 :         const Vector3F euler312 = stateStruct.quat.to_vector312();
     232      221257 :         Tbn.from_euler312(euler312.x, euler312.y, 0.0f);
     233      221257 :         if (yawAng != nullptr) {
     234      221257 :             *yawAng = euler312.z;
     235             :         }
     236             :         return true;
     237             :     }
     238             :     }
     239             :     // rotation order not supported
     240             :     return false;
     241             : }
     242             : 
     243             : #if EK3_FEATURE_MOVING_BASELINE
     244             : /*
     245             :   Correct a yaw measurement calculated from a moving baseline antenna offset
     246             :   for vehicle attitude.
     247             : 
     248             :   The GPS driver recovers the vehicle yaw from the reported baseline heading
     249             :   by subtracting the bearing of the body-frame antenna offset, which is only
     250             :   exact when the vehicle is level. The residual attitude correction is applied
     251             :   here using this core's own roll and pitch estimate at the fusion time
     252             :   horizon, which is time-aligned with the measurement. Using the core's own
     253             :   attitude estimate rather than the published AHRS attitude keeps each lane
     254             :   independent of the others and allows Replay to reproduce the fusion exactly.
     255             : 
     256             :   The correction rotates attitude uncertainty into the yaw measurement in
     257             :   proportion to the vertical component of the baseline, so yawAngErr is
     258             :   inflated to match.
     259             : 
     260             :   Returns false when the antenna baseline is too close to vertical at the
     261             :   estimated attitude for the reported heading to contain usable yaw
     262             :   information, or when the rotation order is not supported, in which case the
     263             :   measurement must not be fused.
     264             : */
     265        4954 : bool NavEKF3_core::correctGPSYawForAntennaOffset(yaw_elements &yawAngData) const
     266             : {
     267        4954 :     const Vector3F &antOffsetBody = yawAngData.antOffset;
     268        4954 :     if (antOffsetBody.is_zero()) {
     269             :         // no antenna offset supplied so the measurement is used as reported
     270             :         return true;
     271             :     }
     272             : 
     273             :     // calculate the rotation from body to earth frame with yaw set to zero
     274             :     // using the rotation order of the measurement
     275        4424 :     Matrix3F Tbn_zeroYaw;
     276        4424 :     if (!buildTbnZeroYaw(yawAngData.order, Tbn_zeroYaw)) {
     277             :         // rotation order not supported: fail closed rather than fusing a
     278             :         // measurement known to need a correction that cannot be applied
     279             :         return false;
     280             :     }
     281             : 
     282        4424 :     const Vector3F antOffsetLevel = Tbn_zeroYaw * antOffsetBody;
     283        4424 :     const ftype antOffsetLevelXY = antOffsetLevel.xy().length();
     284             : 
     285             :     // the heading noise of the receiver scales inversely with the horizontal
     286             :     // separation of the antennas, so a baseline close to vertical at the
     287             :     // estimated attitude carries no usable yaw information. The same
     288             :     // threshold is enforced by the GPS driver against the receiver-reported
     289             :     // baseline
     290             :     const ftype minHorizontalSeparation = AP_GPS_MB_MIN_ANTENNA_SEPARATION_M;
     291        4424 :     if (antOffsetLevelXY < minHorizontalSeparation) {
     292             :         return false;
     293             :     }
     294             : 
     295             :     // an attitude error rotates the vertical component of the baseline into
     296             :     // the horizontal plane, changing the bearing of the offset by up to
     297             :     // |z| / |xy| radians per radian of tilt error, so the correction adds
     298             :     // that much uncertainty to the measurement
     299        3970 :     const ftype tiltSensitivity = fabsF(antOffsetLevel.z) / antOffsetLevelXY;
     300        3970 :     yawAngData.yawAngErr = sqrtF(sq(yawAngData.yawAngErr) + sq(tiltSensitivity) * MAX(tiltErrorVariance, 0.0f));
     301             : 
     302             :     // replace the body-frame bearing of the antenna offset subtracted by the
     303             :     // driver with the bearing of the offset at the estimated attitude
     304        3970 :     const ftype bearingBody = atan2F(-antOffsetBody.y, -antOffsetBody.x);
     305        3970 :     const ftype bearingLevel = atan2F(-antOffsetLevel.y, -antOffsetLevel.x);
     306        3970 :     yawAngData.yawAng = wrap_PI(yawAngData.yawAng + bearingBody - bearingLevel);
     307        3970 :     return true;
     308             : }
     309             : #endif // EK3_FEATURE_MOVING_BASELINE
     310             : 
     311             : // align the yaw angle for the quaternion states to the given yaw angle which should be at the fusion horizon
     312          27 : void NavEKF3_core::alignYawAngle(const yaw_elements &yawAngData)
     313             : {
     314             :     // update quaternion states and covariances
     315          27 :     resetQuatStateYawOnly(yawAngData.yawAng, sq(MAX(yawAngData.yawAngErr, 1.0e-2)), yawAngData.order);
     316             : 
     317             :     // send yaw alignment information to console
     318          27 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u yaw aligned",(unsigned)imu_index);
     319          27 : }
     320             : 
     321             : /********************************************************
     322             : *                   FUSE MEASURED_DATA                  *
     323             : ********************************************************/
     324             : 
     325             : // select fusion of magnetometer data
     326    19965274 : void NavEKF3_core::SelectMagFusion()
     327             : {
     328             :     // clear the flag that lets other processes know that the expensive magnetometer fusion operation has been performed on that time step
     329             :     // used for load levelling
     330    19965274 :     magFusePerformed = false;
     331             : 
     332             :     // Store yaw angle when moving for use as a static reference when not moving
     333    19965274 :     if (!onGroundNotMoving) {
     334    14178195 :         if (fabsF(prevTnb[0][2]) < fabsF(prevTnb[1][2])) {
     335             :             // A 321 rotation order is best conditioned because the X axis is closer to horizontal than the Y axis
     336     6949421 :             yawAngDataStatic.order = rotationOrder::TAIT_BRYAN_321;
     337     6949421 :             yawAngDataStatic.yawAng = atan2F(prevTnb[0][1], prevTnb[0][0]);
     338             :         } else {
     339             :             // A 312 rotation order is best conditioned because the Y axis is closer to horizontal than the X axis
     340     7228774 :             yawAngDataStatic.order = rotationOrder::TAIT_BRYAN_312;
     341     7228774 :             yawAngDataStatic.yawAng = atan2F(-prevTnb[1][0], prevTnb[1][1]);
     342             :         }
     343    14178195 :         yawAngDataStatic.yawAngErr = MAX(frontend->_yawNoise, 0.05f);
     344    14178195 :         yawAngDataStatic.time_ms = imuDataDelayed.time_ms;
     345             :     }
     346             : 
     347             :     // Handle case where we are not using a yaw sensor of any type and attempt to reset the yaw in
     348             :     // flight using the output from the GSF yaw estimator or GPS ground course.
     349    39930548 :     if ((yaw_source_last == AP_NavEKF_Source::SourceYaw::GSF) ||
     350    19965274 :         (!use_compass() &&
     351      118902 :          yaw_source_last != AP_NavEKF_Source::SourceYaw::GPS &&
     352       76667 :          yaw_source_last != AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK &&
     353             :          yaw_source_last != AP_NavEKF_Source::SourceYaw::EXTNAV)) {
     354             : 
     355       48398 :         if ((!yawAlignComplete || yaw_source_reset) && ((yaw_source_last != AP_NavEKF_Source::SourceYaw::GSF) || (EKFGSF_yaw_valid_count >= GSF_YAW_VALID_HISTORY_THRESHOLD))) {
     356       20704 :             realignYawGPS(false);
     357       20704 :             yaw_source_reset = false;
     358             :         } else {
     359       27694 :             yaw_source_reset = false;
     360             :         }
     361             : 
     362       48398 :         if (imuSampleTime_ms - lastSynthYawTime_ms > 140) {
     363             :             // use the EKF-GSF yaw estimator output as this is more robust than the EKF can achieve without a yaw measurement
     364             :             // for non fixed wing platform types
     365             :             ftype gsfYaw, gsfYawVariance;
     366        4461 :             const bool didUseEKFGSF = yawAlignComplete && (yaw_source_last == AP_NavEKF_Source::SourceYaw::GSF) && EKFGSF_getYaw(gsfYaw, gsfYawVariance) && !assume_zero_sideslip() && fuseEulerYaw(yawFusionMethod::GSF);
     367             : 
     368             :             // fallback methods
     369             :             if (!didUseEKFGSF) {
     370        4461 :                 if (onGroundNotMoving) {
     371             :                     // fuse last known good yaw angle before we stopped moving to allow yaw bias learning when on ground before flight
     372         846 :                     fuseEulerYaw(yawFusionMethod::STATIC);
     373        3615 :                 } else if (onGround || PV_AidingMode == AID_NONE || (P[0][0]+P[1][1]+P[2][2]+P[3][3] > 0.01f)) {
     374             :                     // prevent uncontrolled yaw variance growth that can destabilise the covariance matrix
     375             :                     // by fusing a zero innovation
     376         416 :                     fuseEulerYaw(yawFusionMethod::PREDICTED);
     377             :                 }
     378             :             }
     379        4461 :             magTestRatio.zero();
     380        4461 :             yawTestRatio = 0.0f;
     381        4461 :             lastSynthYawTime_ms = imuSampleTime_ms;
     382             :         }
     383       48398 :         return;
     384             :     }
     385             : 
     386             :     // Handle case where we are using GPS yaw sensor instead of a magnetomer
     387    19916876 :     if (yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS || yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK) {
     388             :         bool have_fused_gps_yaw = false;
     389             :         // correct the measurement for vehicle attitude using this core's own attitude
     390             :         // estimate. This is done once per recalled measurement, before all consumers of
     391             :         // yawAngDataDelayed (alignYawAngle, fuseEulerYaw and learnMagBiasFromGPS).
     392             :         // A measurement with no usable yaw information is treated the same as no
     393             :         // measurement: it is not fused or used for alignment (which matters most for
     394             :         // alignYawAngle as it accepts the measurement without an innovation check), it
     395             :         // does not refresh last_gps_yaw_ms (so the compass fallback and the yaw source
     396             :         // health reporting see the loss of usable yaw) and it does not block the
     397             :         // synthetic yaw fusion and yaw source reset recovery branches below
     398             : #if EK3_FEATURE_MOVING_BASELINE
     399       86284 :         if (storedYawAng.recall(yawAngDataDelayed,imuDataDelayed.time_ms) &&
     400        4954 :             correctGPSYawForAntennaOffset(yawAngDataDelayed)) {
     401             : #else
     402             :         if (storedYawAng.recall(yawAngDataDelayed,imuDataDelayed.time_ms)) {
     403             : #endif
     404        4500 :             if (tiltAlignComplete && (!yawAlignComplete || yaw_source_reset)) {
     405          22 :                 alignYawAngle(yawAngDataDelayed);
     406          22 :                 yaw_source_reset = false;
     407             :                 have_fused_gps_yaw = true;
     408          22 :                 lastSynthYawTime_ms = imuSampleTime_ms;
     409          22 :                 last_gps_yaw_fuse_ms = imuSampleTime_ms;
     410          22 :                 recordYawResetsCompleted();
     411        4478 :             } else if (tiltAlignComplete && yawAlignComplete) {
     412        4328 :                 have_fused_gps_yaw = fuseEulerYaw(yawFusionMethod::GPS);
     413        4328 :                 if (have_fused_gps_yaw) {
     414        4327 :                     last_gps_yaw_fuse_ms = imuSampleTime_ms;
     415             :                 }
     416             :             }
     417        4500 :             last_gps_yaw_ms = imuSampleTime_ms;
     418       76830 :         } else if (tiltAlignComplete && !yawAlignComplete) {
     419             :             // External yaw sources can take significant time to start providing yaw data so
     420             :             // wuile waiting, fuse a 'fake' yaw observation at 7Hz to keeop the filter stable
     421        1491 :             if (imuSampleTime_ms - lastSynthYawTime_ms > 140) {
     422         132 :                 yawAngDataDelayed.yawAngErr = MAX(frontend->_yawNoise, 0.05f);
     423             :                 // update the yaw angle using the last estimate which will be used as a static yaw reference when movement stops
     424         132 :                 if (!onGroundNotMoving) {
     425             :                     // prevent uncontrolled yaw variance growth by fusing a zero innovation
     426           2 :                     fuseEulerYaw(yawFusionMethod::PREDICTED);
     427             :                 } else {
     428             :                     // fuse last known good yaw angle before we stopped moving to allow yaw bias learning when on ground before flight
     429         130 :                     fuseEulerYaw(yawFusionMethod::STATIC);
     430             :                 }
     431         132 :                 lastSynthYawTime_ms = imuSampleTime_ms;
     432             :             }
     433       75339 :         } else if (tiltAlignComplete && yawAlignComplete && onGround && imuSampleTime_ms - last_gps_yaw_fuse_ms > 10000) {
     434             :             // handle scenario where we were using GPS yaw previously, but the yaw fusion has timed out.
     435        1206 :             yaw_source_reset = true;
     436             :         }
     437             : 
     438       81330 :         if (yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS) {
     439             :             // no fallback
     440             :             return;
     441             :         }
     442             : 
     443             :         // get new mag data into delay buffer
     444       39097 :         readMagData();
     445             : 
     446       39097 :         if (have_fused_gps_yaw) {
     447        1932 :             if (gps_yaw_mag_fallback_active) {
     448           0 :                 gps_yaw_mag_fallback_active = false;
     449           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u yaw external",(unsigned)imu_index);
     450             :             }
     451             :             // update mag bias from GPS yaw
     452        1932 :             gps_yaw_mag_fallback_ok = learnMagBiasFromGPS();
     453        1932 :             return;
     454             :         }
     455             : 
     456             :         // we don't have GPS yaw data and are configured for
     457             :         // fallback. If we've only just lost GPS yaw
     458       37165 :         if (imuSampleTime_ms - last_gps_yaw_ms < 10000) {
     459             :             // don't fallback to magnetometer fusion for 10s
     460             :             return;
     461             :         }
     462        6353 :         if (!gps_yaw_mag_fallback_ok) {
     463             :             // mag was not consistent enough with GPS to use it as
     464             :             // fallback
     465             :             return;
     466             :         }
     467        5775 :         if (!inFlight) {
     468             :             // don't fall back if not flying but reset to GPS yaw if it becomes available
     469             :             return;
     470             :         }
     471        5505 :         if (!gps_yaw_mag_fallback_active) {
     472           2 :             gps_yaw_mag_fallback_active = true;
     473           2 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u yaw fallback active",(unsigned)imu_index);
     474             :         }
     475             :         // fall through to magnetometer fusion
     476             :     }
     477             : 
     478             : #if EK3_FEATURE_EXTERNAL_NAV
     479             :     // Handle case where we are using an external nav for yaw
     480    19841051 :     const bool extNavYawDataToFuse = storedExtNavYawAng.recall(extNavYawAngDataDelayed, imuDataDelayed.time_ms);
     481    19841051 :     if (yaw_source_last == AP_NavEKF_Source::SourceYaw::EXTNAV) {
     482       28269 :         if (extNavYawDataToFuse) {
     483        6342 :             if (tiltAlignComplete && (!yawAlignComplete || yaw_source_reset)) {
     484           5 :                 alignYawAngle(extNavYawAngDataDelayed);
     485           5 :                 yaw_source_reset = false;
     486        6337 :             } else if (tiltAlignComplete && yawAlignComplete) {
     487        6254 :                 fuseEulerYaw(yawFusionMethod::EXTNAV);
     488             :             }
     489        6342 :             last_extnav_yaw_fusion_ms = imuSampleTime_ms;
     490       21927 :         } else if (tiltAlignComplete && !yawAlignComplete) {
     491             :             // External yaw sources can take significant time to start providing yaw data so
     492             :             // while waiting, fuse a 'fake' yaw observation at 7Hz to keep the filter stable
     493           2 :             if (imuSampleTime_ms - lastSynthYawTime_ms > 140) {
     494             :                 // update the yaw angle using the last estimate which will be used as a static yaw reference when movement stops
     495           1 :                 if (!onGroundNotMoving) {
     496             :                     // prevent uncontrolled yaw variance growth by fusing a zero innovation
     497           0 :                     fuseEulerYaw(yawFusionMethod::PREDICTED);
     498             :                 } else {
     499             :                     // fuse last known good yaw angle before we stopped moving to allow yaw bias learning when on ground before flight
     500           1 :                     fuseEulerYaw(yawFusionMethod::STATIC);
     501             :                 }
     502           1 :                 lastSynthYawTime_ms = imuSampleTime_ms;
     503             :             }
     504             :         }
     505             :     }
     506             : #endif // EK3_FEATURE_EXTERNAL_NAV
     507             : 
     508             :     // If we are using the compass and the magnetometer has been unhealthy for too long we declare a timeout
     509    19841051 :     if (magHealth) {
     510    19278043 :         magTimeout = false;
     511    19278043 :         lastHealthyMagTime_ms = imuSampleTime_ms;
     512      563008 :     } else if ((imuSampleTime_ms - lastHealthyMagTime_ms) > frontend->magFailTimeLimit_ms && use_compass()) {
     513       43266 :         magTimeout = true;
     514             :     }
     515             : 
     516    19841051 :     if (yaw_source_last != AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK) {
     517             :         // check for and read new magnetometer measurements. We don't
     518             :         // read for GPS_COMPASS_FALLBACK as it has already been read
     519             :         // above
     520    19835546 :         readMagData();
     521             :     }
     522             : 
     523             :     // check for availability of magnetometer or other yaw data to fuse
     524    19841051 :     magDataToFuse = storedMag.recall(magDataDelayed,imuDataDelayed.time_ms);
     525             : 
     526             :     // Control reset of yaw and magnetic field states if we are using compass data
     527    19841051 :     if (magDataToFuse) {
     528     2787354 :         if (yaw_source_reset && (yaw_source_last == AP_NavEKF_Source::SourceYaw::COMPASS ||
     529             :                                  yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK)) {
     530          33 :             magYawResetRequest = true;
     531          33 :             yaw_source_reset = false;
     532             :         }
     533     2787354 :         controlMagYawReset();
     534             :     }
     535             : 
     536             :     // determine if conditions are right to start a new fusion cycle
     537             :     // wait until the EKF time horizon catches up with the measurement
     538    19841051 :     bool dataReady = (magDataToFuse && statesInitialised && use_compass() && yawAlignComplete);
     539             :     if (dataReady) {
     540             :         // use the simple method of declination to maintain heading if we cannot use the magnetic field states
     541     2743449 :         if(inhibitMagStates || magStateResetRequest || !magStateInitComplete) {
     542     1070607 :             magFusionSel = MagFuseSel::FUSE_YAW;
     543     1070607 :             fuseEulerYaw(yawFusionMethod::MAGNETOMETER);
     544             : 
     545             :             // zero the test ratio output from the inactive 3-axis magnetometer fusion
     546     1070607 :             magTestRatio.zero();
     547             : 
     548             :         } else {
     549     1672842 :             magFusionSel = MagFuseSel::FUSE_MAG;
     550             :             // a stationary vehicle gives 3-axis fusion no yaw observability - a yaw error can be
     551             :             // absorbed by the body field states with zero innovation, so anchor the yaw to the
     552             :             // measured magnetic heading
     553             :             bool yawAnchored = false;
     554     1672842 :             if (onGroundNotMoving && effectiveMagCal == MagCal::GROUND_AND_INFLIGHT) {
     555        1298 :                 yawAnchored = fuseEulerYaw(yawFusionMethod::MAGNETOMETER);
     556             :                 // bad_yaw covers the case where FinishFusion skipped the update
     557        1298 :                 if (yawAnchored && !faultStatus.bad_yaw) {
     558        1298 :                     magFusionSel = MagFuseSel::FUSE_MAG_ANCHORED;
     559             :                 }
     560             :             }
     561             :             // if we are not doing aiding with earth relative observations (eg GPS) then the declination is
     562             :             // maintained by fusing declination as a synthesised observation
     563             :             // We also fuse declination if we are using the WMM tables
     564     1672842 :             if (PV_AidingMode != AID_ABSOLUTE ||
     565     1644478 :                 (frontend->_mag_ef_limit > 0 && have_table_earth_field)) {
     566     1672842 :                 FuseDeclination(0.34f);
     567             :             }
     568             :             // fuse the three magnetometer componenents using sequential fusion for each axis
     569     1672842 :             FuseMagnetometer();
     570     1672842 :             if (!yawAnchored) {
     571             :                 // zero the test ratio output from the inactive simple magnetometer yaw fusion
     572     1671544 :                 yawTestRatio = 0.0f;
     573             :             }
     574             :         }
     575             :     }
     576             : 
     577             :     // If the final yaw reset has been performed and the state variances are sufficiently low
     578             :     // record that the earth field has been learned.
     579    19841051 :     if (!magFieldLearned && finalInflightMagInit) {
     580      981782 :         magFieldLearned = (P[16][16] < sq(0.01f)) && (P[17][17] < sq(0.01f)) && (P[18][18] < sq(0.01f));
     581             :     }
     582             : 
     583             :     // record the last learned field variances
     584    19841051 :     if (magFieldLearned && !inhibitMagStates) {
     585     9068669 :         earthMagFieldVar.x = P[16][16];
     586     9068669 :         earthMagFieldVar.y = P[17][17];
     587     9068669 :         earthMagFieldVar.z = P[18][18];
     588     9068669 :         bodyMagFieldVar.x = P[19][19];
     589     9068669 :         bodyMagFieldVar.y = P[20][20];
     590     9068669 :         bodyMagFieldVar.z = P[21][21];
     591             :     }
     592             : }
     593             : 
     594             : /*
     595             :  * Fuse magnetometer measurements using explicit algebraic equations generated with Matlab symbolic toolbox.
     596             :  * The script file used to generate these and other equations in this filter can be found here:
     597             :  * https://github.com/PX4/ecl/blob/master/matlab/scripts/Inertial%20Nav%20EKF/GenerateNavFilterEquations.m
     598             : */
     599     1672842 : void NavEKF3_core::FuseMagnetometer()
     600             : {
     601             :     // perform sequential fusion of magnetometer measurements.
     602             :     // this assumes that the errors in the different components are
     603             :     // uncorrelated which is not true, however in the absence of covariance
     604             :     // data fit is the only assumption we can make
     605             :     // so we might as well take advantage of the computational efficiencies
     606             :     // associated with sequential fusion
     607             :     // calculate observation jacobians and Kalman gains
     608             : 
     609             :     // create aliases for state to make code easier to read:
     610     1672842 :     const ftype q0       = stateStruct.quat[0];
     611     1672842 :     const ftype q1       = stateStruct.quat[1];
     612     1672842 :     const ftype q2       = stateStruct.quat[2];
     613     1672842 :     const ftype q3       = stateStruct.quat[3];
     614     1672842 :     const ftype magN     = stateStruct.earth_magfield[0];
     615     1672842 :     const ftype magE     = stateStruct.earth_magfield[1];
     616     1672842 :     const ftype magD     = stateStruct.earth_magfield[2];
     617     1672842 :     const ftype magXbias = stateStruct.body_magfield[0];
     618     1672842 :     const ftype magYbias = stateStruct.body_magfield[1];
     619     1672842 :     const ftype magZbias = stateStruct.body_magfield[2];
     620             : 
     621             :     // rotate predicted earth components into body axes and calculate
     622             :     // predicted measurements
     623             :     const Matrix3F DCM {
     624     1672842 :         q0*q0 + q1*q1 - q2*q2 - q3*q3,
     625     1672842 :         2.0f*(q1*q2 + q0*q3),
     626     1672842 :         2.0f*(q1*q3-q0*q2),
     627     1672842 :         2.0f*(q1*q2 - q0*q3),
     628     1672842 :         q0*q0 - q1*q1 + q2*q2 - q3*q3,
     629     1672842 :         2.0f*(q2*q3 + q0*q1),
     630     1672842 :         2.0f*(q1*q3 + q0*q2),
     631     1672842 :         2.0f*(q2*q3 - q0*q1),
     632     1672842 :         q0*q0 - q1*q1 - q2*q2 + q3*q3
     633     1672842 :     };
     634             : 
     635             :     const Vector3F MagPred {
     636     1672842 :         DCM[0][0]*magN + DCM[0][1]*magE  + DCM[0][2]*magD + magXbias,
     637     1672842 :         DCM[1][0]*magN + DCM[1][1]*magE  + DCM[1][2]*magD + magYbias,
     638     1672842 :         DCM[2][0]*magN + DCM[2][1]*magE  + DCM[2][2]*magD + magZbias
     639     5018526 :     };
     640             : 
     641             :     // calculate the measurement innovation for each axis
     642     1672842 :     innovMag = MagPred - magDataDelayed.mag;
     643             : 
     644             :     // scale magnetometer observation error with total angular rate to allow for timing errors
     645     1672842 :     const ftype R_MAG = sq(constrain_ftype(frontend->_magNoise, 0.01f, 0.5f)) + sq(frontend->magVarRateScale*imuDataDelayed.delAng.length() / imuDataDelayed.delAngDT);
     646             : 
     647             :     // calculate common expressions used to calculate observation jacobians an innovation variance for each component
     648             :     const Vector9 SH_MAG {
     649     1672842 :         2.0f*magD*q3 + 2.0f*magE*q2 + 2.0f*magN*q1,
     650     1672842 :         2.0f*magD*q0 - 2.0f*magE*q1 + 2.0f*magN*q2,
     651     1672842 :         2.0f*magD*q1 + 2.0f*magE*q0 - 2.0f*magN*q3,
     652     1672842 :         sq(q3),
     653     1672842 :         sq(q2),
     654     1672842 :         sq(q1),
     655     1672842 :         sq(q0),
     656     1672842 :         2.0f*magN*q0,
     657     1672842 :         2.0f*magE*q3
     658             :     };
     659             : 
     660             :     // Calculate the innovation variance for each axis
     661             :     // X axis
     662     1672842 :     varInnovMag[0] = (P[19][19] + R_MAG + P[1][19]*SH_MAG[0] - P[2][19]*SH_MAG[1] + P[3][19]*SH_MAG[2] - P[16][19]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + (2.0f*q0*q3 + 2.0f*q1*q2)*(P[19][17] + P[1][17]*SH_MAG[0] - P[2][17]*SH_MAG[1] + P[3][17]*SH_MAG[2] - P[16][17]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][17]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][17]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][17]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - (2.0f*q0*q2 - 2.0f*q1*q3)*(P[19][18] + P[1][18]*SH_MAG[0] - P[2][18]*SH_MAG[1] + P[3][18]*SH_MAG[2] - P[16][18]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][18]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][18]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][18]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + (SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)*(P[19][0] + P[1][0]*SH_MAG[0] - P[2][0]*SH_MAG[1] + P[3][0]*SH_MAG[2] - P[16][0]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][0]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][0]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][0]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + P[17][19]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][19]*(2.0f*q0*q2 - 2.0f*q1*q3) + SH_MAG[0]*(P[19][1] + P[1][1]*SH_MAG[0] - P[2][1]*SH_MAG[1] + P[3][1]*SH_MAG[2] - P[16][1]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][1]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][1]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][1]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - SH_MAG[1]*(P[19][2] + P[1][2]*SH_MAG[0] - P[2][2]*SH_MAG[1] + P[3][2]*SH_MAG[2] - P[16][2]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][2]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][2]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][2]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + SH_MAG[2]*(P[19][3] + P[1][3]*SH_MAG[0] - P[2][3]*SH_MAG[1] + P[3][3]*SH_MAG[2] - P[16][3]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][3]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][3]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][3]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - (SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6])*(P[19][16] + P[1][16]*SH_MAG[0] - P[2][16]*SH_MAG[1] + P[3][16]*SH_MAG[2] - P[16][16]*(SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6]) + P[17][16]*(2.0f*q0*q3 + 2.0f*q1*q2) - P[18][16]*(2.0f*q0*q2 - 2.0f*q1*q3) + P[0][16]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + P[0][19]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2));
     663     1672842 :     if (varInnovMag[0] >= R_MAG) {
     664     1672842 :         faultStatus.bad_xmag = false;
     665             :     } else {
     666             :         // the calculation is badly conditioned, so we cannot perform fusion on this step
     667             :         // we reset the covariance matrix and try again next measurement
     668           0 :         CovarianceInit();
     669           0 :         faultStatus.bad_xmag = true;
     670        5640 :         return;
     671             :     }
     672             : 
     673             :     // Y axis
     674     1672842 :     varInnovMag[1] = (P[20][20] + R_MAG + P[0][20]*SH_MAG[2] + P[1][20]*SH_MAG[1] + P[2][20]*SH_MAG[0] - P[17][20]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - (2.0f*q0*q3 - 2.0f*q1*q2)*(P[20][16] + P[0][16]*SH_MAG[2] + P[1][16]*SH_MAG[1] + P[2][16]*SH_MAG[0] - P[17][16]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][16]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][16]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][16]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + (2.0f*q0*q1 + 2.0f*q2*q3)*(P[20][18] + P[0][18]*SH_MAG[2] + P[1][18]*SH_MAG[1] + P[2][18]*SH_MAG[0] - P[17][18]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][18]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][18]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][18]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - (SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)*(P[20][3] + P[0][3]*SH_MAG[2] + P[1][3]*SH_MAG[1] + P[2][3]*SH_MAG[0] - P[17][3]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][3]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][3]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][3]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - P[16][20]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][20]*(2.0f*q0*q1 + 2.0f*q2*q3) + SH_MAG[2]*(P[20][0] + P[0][0]*SH_MAG[2] + P[1][0]*SH_MAG[1] + P[2][0]*SH_MAG[0] - P[17][0]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][0]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][0]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][0]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + SH_MAG[1]*(P[20][1] + P[0][1]*SH_MAG[2] + P[1][1]*SH_MAG[1] + P[2][1]*SH_MAG[0] - P[17][1]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][1]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][1]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][1]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + SH_MAG[0]*(P[20][2] + P[0][2]*SH_MAG[2] + P[1][2]*SH_MAG[1] + P[2][2]*SH_MAG[0] - P[17][2]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][2]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][2]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][2]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - (SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6])*(P[20][17] + P[0][17]*SH_MAG[2] + P[1][17]*SH_MAG[1] + P[2][17]*SH_MAG[0] - P[17][17]*(SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6]) - P[16][17]*(2.0f*q0*q3 - 2.0f*q1*q2) + P[18][17]*(2.0f*q0*q1 + 2.0f*q2*q3) - P[3][17]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - P[3][20]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2));
     675     1672842 :     if (varInnovMag[1] >= R_MAG) {
     676     1672842 :         faultStatus.bad_ymag = false;
     677             :     } else {
     678             :         // the calculation is badly conditioned, so we cannot perform fusion on this step
     679             :         // we reset the covariance matrix and try again next measurement
     680           0 :         CovarianceInit();
     681           0 :         faultStatus.bad_ymag = true;
     682           0 :         return;
     683             :     }
     684             : 
     685             :     // Z axis
     686     1672842 :     varInnovMag[2] = (P[21][21] + R_MAG + P[0][21]*SH_MAG[1] - P[1][21]*SH_MAG[2] + P[3][21]*SH_MAG[0] + P[18][21]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + (2.0f*q0*q2 + 2.0f*q1*q3)*(P[21][16] + P[0][16]*SH_MAG[1] - P[1][16]*SH_MAG[2] + P[3][16]*SH_MAG[0] + P[18][16]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][16]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][16]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][16]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - (2.0f*q0*q1 - 2.0f*q2*q3)*(P[21][17] + P[0][17]*SH_MAG[1] - P[1][17]*SH_MAG[2] + P[3][17]*SH_MAG[0] + P[18][17]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][17]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][17]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][17]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + (SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)*(P[21][2] + P[0][2]*SH_MAG[1] - P[1][2]*SH_MAG[2] + P[3][2]*SH_MAG[0] + P[18][2]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][2]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][2]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][2]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + P[16][21]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][21]*(2.0f*q0*q1 - 2.0f*q2*q3) + SH_MAG[1]*(P[21][0] + P[0][0]*SH_MAG[1] - P[1][0]*SH_MAG[2] + P[3][0]*SH_MAG[0] + P[18][0]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][0]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][0]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][0]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) - SH_MAG[2]*(P[21][1] + P[0][1]*SH_MAG[1] - P[1][1]*SH_MAG[2] + P[3][1]*SH_MAG[0] + P[18][1]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][1]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][1]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][1]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + SH_MAG[0]*(P[21][3] + P[0][3]*SH_MAG[1] - P[1][3]*SH_MAG[2] + P[3][3]*SH_MAG[0] + P[18][3]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][3]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][3]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][3]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + (SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6])*(P[21][18] + P[0][18]*SH_MAG[1] - P[1][18]*SH_MAG[2] + P[3][18]*SH_MAG[0] + P[18][18]*(SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6]) + P[16][18]*(2.0f*q0*q2 + 2.0f*q1*q3) - P[17][18]*(2.0f*q0*q1 - 2.0f*q2*q3) + P[2][18]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2)) + P[2][21]*(SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2));
     687     1672842 :     if (varInnovMag[2] >= R_MAG) {
     688     1672842 :         faultStatus.bad_zmag = false;
     689             :     } else {
     690             :         // the calculation is badly conditioned, so we cannot perform fusion on this step
     691             :         // we reset the covariance matrix and try again next measurement
     692           0 :         CovarianceInit();
     693           0 :         faultStatus.bad_zmag = true;
     694           0 :         return;
     695             :     }
     696             : 
     697             :     // calculate the innovation test ratios
     698     6691368 :     for (uint8_t i = 0; i<=2; i++) {
     699     5018526 :         magTestRatio[i] = sq(innovMag[i]) / (sq(MAX(0.01f * (ftype)frontend->_magInnovGate, 1.0f)) * varInnovMag[i]);
     700             :     }
     701             : 
     702             :     // check the last values from all components and set magnetometer health accordingly
     703     1672842 :     magHealth = (magTestRatio[0] < 1.0f && magTestRatio[1] < 1.0f && magTestRatio[2] < 1.0f);
     704             : 
     705             :     // if the magnetometer is unhealthy, do not proceed further
     706     1672842 :     if (!magHealth) {
     707             :         return;
     708             :     }
     709             : 
     710             :     Vector24 H_MAG;
     711             :     // index of H_MAG which is exactly 1, for more efficient computation below
     712             :     int H_MAG_unit_index;
     713     6668808 :     for (uint8_t obsIndex = 0; obsIndex <= 2; obsIndex++) {
     714             : 
     715     5001606 :         if (obsIndex == 0) {
     716             : 
     717    39563628 :             for (uint8_t i = 0; i<=stateIndexLim; i++) H_MAG[i] = 0.0f;
     718     1667202 :             H_MAG[0] = SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2;
     719     1667202 :             H_MAG[1] = SH_MAG[0];
     720     1667202 :             H_MAG[2] = -SH_MAG[1];
     721     1667202 :             H_MAG[3] = SH_MAG[2];
     722     1667202 :             H_MAG[16] = SH_MAG[5] - SH_MAG[4] - SH_MAG[3] + SH_MAG[6];
     723     1667202 :             H_MAG[17] = 2.0f*q0*q3 + 2.0f*q1*q2;
     724     1667202 :             H_MAG[18] = 2.0f*q1*q3 - 2.0f*q0*q2;
     725             :             H_MAG[19] = 1.0f;
     726             :             H_MAG[20] = 0.0f;
     727             :             H_MAG[21] = 0.0f;
     728             :             H_MAG_unit_index = 19;
     729             : 
     730             :             // calculate Kalman gain
     731             :             const Vector5 SK_MX {
     732     1667202 :                 1.0f / varInnovMag[0],
     733             :                 SH_MAG[3] + SH_MAG[4] - SH_MAG[5] - SH_MAG[6],
     734             :                 SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2,
     735             :                 2.0f*q0*q2 - 2.0f*q1*q3,
     736             :                 2.0f*q0*q3 + 2.0f*q1*q2
     737             :             };
     738             : 
     739             :             uint32_t kalman_mask = (1<<10)-1; // values to calculate in Kfusion (others are set to zero)
     740             : 
     741     1667202 :             if (!inhibitDelAngBiasStates) {
     742             :                 kalman_mask |= (1<<10) | (1<<11) | (1<<12);
     743             :             }
     744             : 
     745     1667202 :             if (!inhibitDelVelBiasStates) {
     746     6668568 :                 for (uint8_t index = 0; index < 3; index++) {
     747     5001426 :                     const uint8_t stateIndex = index + 13;
     748     5001426 :                     if (!dvelBiasAxisInhibit[index]) {
     749     4997570 :                         kalman_mask |= (1<<stateIndex);
     750             :                     }
     751             :                 }
     752             :             }
     753             : 
     754             :             // zero Kalman gains to inhibit magnetic field state estimation
     755     1667202 :             if (!inhibitMagStates) {
     756     1667202 :                 kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
     757             :             }
     758             : 
     759             :             // zero Kalman gains to inhibit wind state estimation
     760     1667202 :             if (!inhibitWindStates && !treatWindStatesAsTruth) {
     761      602571 :                 kalman_mask |= (1<<22) | (1<<23);
     762             :             }
     763             : 
     764    41680050 :             for (auto i=0; i<24; i++) {
     765             :                 ftype res = 0;
     766    40012848 :                 if (kalman_mask & (1<<i)) {
     767    37879370 :                     res = SK_MX[0]*(P[i][19] + P[i][1]*SH_MAG[0] - P[i][2]*SH_MAG[1] + P[i][3]*SH_MAG[2] + P[i][0]*SK_MX[2] - P[i][16]*SK_MX[1] + P[i][17]*SK_MX[4] - P[i][18]*SK_MX[3]);
     768             :                 }
     769    40012848 :                 Kfusion[i] = res;
     770             :             }
     771             : 
     772             :             // set flags to indicate to other processes that fusion has been performed and is required on the next frame
     773             :             // this can be used by other fusion processes to avoid fusing on the same frame as this expensive step
     774     1667202 :             magFusePerformed = true;
     775     3334404 :         } else if (obsIndex == 1) { // Fuse Y axis
     776             : 
     777             :             // calculate observation jacobians
     778    39563628 :             for (uint8_t i = 0; i<=stateIndexLim; i++) H_MAG[i] = 0.0f;
     779     1667202 :             H_MAG[0] = SH_MAG[2];
     780     1667202 :             H_MAG[1] = SH_MAG[1];
     781     1667202 :             H_MAG[2] = SH_MAG[0];
     782     1667202 :             H_MAG[3] = 2.0f*magD*q2 - SH_MAG[8] - SH_MAG[7];
     783     1667202 :             H_MAG[16] = 2.0f*q1*q2 - 2.0f*q0*q3;
     784     1667202 :             H_MAG[17] = SH_MAG[4] - SH_MAG[3] - SH_MAG[5] + SH_MAG[6];
     785     1667202 :             H_MAG[18] = 2.0f*q0*q1 + 2.0f*q2*q3;
     786             :             H_MAG[19] = 0.0f;
     787             :             H_MAG[20] = 1.0f;
     788             :             H_MAG[21] = 0.0f;
     789             :             H_MAG_unit_index = 20;
     790             : 
     791             :             // calculate Kalman gain
     792             :             const Vector5 SK_MY {
     793     1667202 :                 1.0f / varInnovMag[1],
     794             :                 SH_MAG[3] - SH_MAG[4] + SH_MAG[5] - SH_MAG[6],
     795             :                 SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2,
     796             :                 2.0f*q0*q3 - 2.0f*q1*q2,
     797             :                 2.0f*q0*q1 + 2.0f*q2*q3
     798             :             };
     799             : 
     800             :             uint32_t kalman_mask = (1<<10)-1; // values to calculate in Kfusion (others are set to zero)
     801             : 
     802     1667202 :             if (!inhibitDelAngBiasStates) {
     803             :                 kalman_mask |= (1<<10) | (1<<11) | (1<<12);
     804             :             }
     805             : 
     806     1667202 :             if (!inhibitDelVelBiasStates) {
     807     6668568 :                 for (uint8_t index = 0; index < 3; index++) {
     808     5001426 :                     const uint8_t stateIndex = index + 13;
     809     5001426 :                     if (!dvelBiasAxisInhibit[index]) {
     810     4997570 :                         kalman_mask |= (1<<stateIndex);
     811             :                     }
     812             :                 }
     813             :             }
     814             : 
     815             :             // zero Kalman gains to inhibit magnetic field state estimation
     816     1667202 :             if (!inhibitMagStates) {
     817     1667202 :                 kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
     818             :             }
     819             : 
     820             :             // zero Kalman gains to inhibit wind state estimation
     821     1667202 :             if (!inhibitWindStates && !treatWindStatesAsTruth) {
     822      602571 :                 kalman_mask |= (1<<22) | (1<<23);
     823             :             }
     824             : 
     825    41680050 :             for (auto i=0; i<24; i++) {
     826             :                 ftype res = 0;
     827    40012848 :                 if (kalman_mask & (1<<i)) {
     828    37879370 :                     res = SK_MY[0]*(P[i][20] + P[i][0]*SH_MAG[2] + P[i][1]*SH_MAG[1] + P[i][2]*SH_MAG[0] - P[i][3]*SK_MY[2] - P[i][17]*SK_MY[1] - P[i][16]*SK_MY[3] + P[i][18]*SK_MY[4]);
     829             :                 }
     830    40012848 :                 Kfusion[i] = res;
     831             :             }
     832             : 
     833             :             // set flags to indicate to other processes that fusion has been performed and is required on the next frame
     834             :             // this can be used by other fusion processes to avoid fusing on the same frame as this expensive step
     835     1667202 :             magFusePerformed = true;
     836             :         }
     837             :         else if (obsIndex == 2) // we are now fusing the Z measurement
     838             :         {
     839             :             // calculate observation jacobians
     840    39563628 :             for (uint8_t i = 0; i<=stateIndexLim; i++) H_MAG[i] = 0.0f;
     841     1667202 :             H_MAG[0] = SH_MAG[1];
     842     1667202 :             H_MAG[1] = -SH_MAG[2];
     843     1667202 :             H_MAG[2] = SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2;
     844     1667202 :             H_MAG[3] = SH_MAG[0];
     845     1667202 :             H_MAG[16] = 2.0f*q0*q2 + 2.0f*q1*q3;
     846     1667202 :             H_MAG[17] = 2.0f*q2*q3 - 2.0f*q0*q1;
     847     1667202 :             H_MAG[18] = SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6];
     848             :             H_MAG[19] = 0.0f;
     849             :             H_MAG[20] = 0.0f;
     850             :             H_MAG[21] = 1.0f;
     851             :             H_MAG_unit_index = 21;
     852             : 
     853             :             // calculate Kalman gain
     854             :             const Vector5 SK_MZ {
     855     1667202 :                 1.0f / varInnovMag[2],
     856             :                 SH_MAG[3] - SH_MAG[4] - SH_MAG[5] + SH_MAG[6],
     857             :                 SH_MAG[7] + SH_MAG[8] - 2.0f*magD*q2,
     858             :                 2.0f*q0*q1 - 2.0f*q2*q3,
     859             :                 2.0f*q0*q2 + 2.0f*q1*q3
     860             :             };
     861             : 
     862             :             uint32_t kalman_mask = (1<<10)-1; // values to calculate in Kfusion (others are set to zero)
     863             : 
     864     1667202 :             if (!inhibitDelAngBiasStates) {
     865             :                 kalman_mask |= (1<<10) | (1<<11) | (1<<12);
     866             :             }
     867             : 
     868     1667202 :             if (!inhibitDelVelBiasStates) {
     869     6668568 :                 for (uint8_t index = 0; index < 3; index++) {
     870     5001426 :                     const uint8_t stateIndex = index + 13;
     871     5001426 :                     if (!dvelBiasAxisInhibit[index]) {
     872     4997570 :                         kalman_mask |= (1<<stateIndex);
     873             :                     }
     874             :                 }
     875             :             }
     876             : 
     877             :             // zero Kalman gains to inhibit magnetic field state estimation
     878     1667202 :             if (!inhibitMagStates) {
     879     1667202 :                 kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
     880             :             }
     881             : 
     882             :             // zero Kalman gains to inhibit wind state estimation
     883     1667202 :             if (!inhibitWindStates && !treatWindStatesAsTruth) {
     884      602571 :                 kalman_mask |= (1<<22) | (1<<23);
     885             :             }
     886             : 
     887    41680050 :             for (auto i=0; i<24; i++) {
     888             :                 ftype res = 0;
     889    40012848 :                 if (kalman_mask & (1<<i)) {
     890    37879370 :                     res = SK_MZ[0]*(P[i][21] + P[i][0]*SH_MAG[1] - P[i][1]*SH_MAG[2] + P[i][3]*SH_MAG[0] + P[i][2]*SK_MZ[2] + P[i][18]*SK_MZ[1] + P[i][16]*SK_MZ[4] - P[i][17]*SK_MZ[3]);
     891             :                 }
     892    40012848 :                 Kfusion[i] = res;
     893             :             }
     894             : 
     895             :             // set flags to indicate to other processes that fusion has been performed and is required on the next frame
     896             :             // this can be used by other fusion processes to avoid fusing on the same frame as this expensive step
     897     1667202 :             magFusePerformed = true;
     898             :         }
     899             :         // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
     900             :         // the zero elements of H to reduce the number of operations.
     901   118690884 :         for (unsigned i = 0; i<=stateIndexLim; i++) {
     902             :             // j as the inner loop allows the compiler to hoist the KH product
     903             :             // to save computation, and do the inner indexing more efficiently.
     904  2702548098 :             for (unsigned j = 0; j<=stateIndexLim; j++) {
     905             :                 ftype res = 0;
     906  2588858820 :                 res += (Kfusion[i] * H_MAG[0]) * P[0][j];
     907  2588858820 :                 res += (Kfusion[i] * H_MAG[1]) * P[1][j];
     908  2588858820 :                 res += (Kfusion[i] * H_MAG[2]) * P[2][j];
     909  2588858820 :                 res += (Kfusion[i] * H_MAG[3]) * P[3][j];
     910  2588858820 :                 res += (Kfusion[i] * H_MAG[16]) * P[16][j];
     911  2588858820 :                 res += (Kfusion[i] * H_MAG[17]) * P[17][j];
     912  2588858820 :                 res += (Kfusion[i] * H_MAG[18]) * P[18][j];
     913             :                 // one value in H is always 1, and the others not mentioned here
     914             :                 // are zero, so we can skip that H product to save an operation.
     915  2588858820 :                 res += Kfusion[i] * P[H_MAG_unit_index][j];
     916  2588858820 :                 KHP[i][j] = res;
     917             :             }
     918             :         }
     919             : 
     920             :         // finish fusion from KHP and Kfusion
     921     5001606 :         if (!FinishFusion(innovMag[obsIndex])) { // no fault?
     922             :             // add table constraint here for faster convergence
     923     5001606 :             if (have_table_earth_field && frontend->_mag_ef_limit > 0) {
     924     4992465 :                 MagTableConstrain();
     925             :             }
     926             :         } else {
     927             :             // record bad axis
     928           0 :             if (obsIndex == 0) {
     929           0 :                 faultStatus.bad_xmag = true;
     930           0 :             } else if (obsIndex == 1) {
     931           0 :                 faultStatus.bad_ymag = true;
     932             :             } else if (obsIndex == 2) {
     933           0 :                 faultStatus.bad_zmag = true;
     934             :             }
     935           0 :             CovarianceInit();
     936           0 :             return;
     937             :         }
     938             :     }
     939             : }
     940             : 
     941             : /*
     942             :  * Fuse direct yaw measurements using explicit algebraic equations auto-generated from
     943             :  * derivation/generate_2.py with output recorded in derivation/generated/yaw_generated.cpp
     944             :  * Returns true if the fusion was successful
     945             : */
     946     1083882 : bool NavEKF3_core::fuseEulerYaw(yawFusionMethod method)
     947             : {
     948     1083882 :     const ftype &q0 = stateStruct.quat[0];
     949     1083882 :     const ftype &q1 = stateStruct.quat[1];
     950     1083882 :     const ftype &q2 = stateStruct.quat[2];
     951     1083882 :     const ftype &q3 = stateStruct.quat[3];
     952             : 
     953             :     ftype gsfYaw, gsfYawVariance;
     954     1083882 :     if (method == yawFusionMethod::GSF) {
     955           0 :         if (!EKFGSF_getYaw(gsfYaw, gsfYawVariance)) {
     956             :             return false;
     957             :         }
     958             :     }
     959             : 
     960             :     // yaw measurement error variance (rad^2)
     961             :     ftype R_YAW;
     962     1083882 :     switch (method) {
     963        4328 :     case yawFusionMethod::GPS:
     964        4328 :         R_YAW = sq(yawAngDataDelayed.yawAngErr);
     965        4328 :         break;
     966             : 
     967           0 :     case yawFusionMethod::GSF:
     968           0 :         R_YAW = gsfYawVariance;
     969           0 :         break;
     970             : 
     971         977 :     case yawFusionMethod::STATIC:
     972         977 :         R_YAW = sq(yawAngDataStatic.yawAngErr);
     973         977 :         break;
     974             : 
     975     1072323 :     case yawFusionMethod::MAGNETOMETER:
     976             :     case yawFusionMethod::PREDICTED:
     977             :     default:
     978     1072323 :         R_YAW = sq(frontend->_yawNoise);
     979     1072323 :         break;
     980             : 
     981             : #if EK3_FEATURE_EXTERNAL_NAV
     982        6254 :     case yawFusionMethod::EXTNAV:
     983        6254 :         R_YAW = sq(MAX(extNavYawAngDataDelayed.yawAngErr, 0.05f));
     984        6254 :         break;
     985             : #endif
     986             :     }
     987             : 
     988             :     // determine if a 321 or 312 Euler sequence is best
     989             :     rotationOrder order;
     990     1083882 :     switch (method) {
     991        4328 :     case yawFusionMethod::GPS:
     992        4328 :         order = yawAngDataDelayed.order;
     993        4328 :         break;
     994             : 
     995         977 :     case yawFusionMethod::STATIC:
     996         977 :         order = yawAngDataStatic.order;
     997         977 :         break;
     998             : 
     999     1072323 :     case yawFusionMethod::MAGNETOMETER:
    1000             :     case yawFusionMethod::GSF:
    1001             :     case yawFusionMethod::PREDICTED:
    1002             :     default:
    1003             :         // determined automatically
    1004     1072323 :         order = (fabsF(prevTnb[0][2]) < fabsF(prevTnb[1][2])) ? rotationOrder::TAIT_BRYAN_321 : rotationOrder::TAIT_BRYAN_312;
    1005     1072323 :         break;
    1006             : 
    1007             : #if EK3_FEATURE_EXTERNAL_NAV
    1008        6254 :     case yawFusionMethod::EXTNAV:
    1009        6254 :         order = extNavYawAngDataDelayed.order;
    1010        6254 :         break;
    1011             : #endif
    1012             :     }
    1013             : 
    1014             :     // calculate observation jacobian, predicted yaw and zero yaw body to earth rotation matrix
    1015             :     ftype yawAngPredicted;
    1016             :     ftype H_YAW[4];
    1017     1083882 :     Matrix3F Tbn_zeroYaw;
    1018             : 
    1019     1083882 :     if (order == rotationOrder::TAIT_BRYAN_321) {
    1020             :         // calculate 321 yaw observation matrix - option A or B to avoid singularity in derivation at +-90 degrees yaw
    1021             :         bool canUseA = false;
    1022      862625 :         const ftype SA0 = 2*q3;
    1023      862625 :         const ftype SA1 = 2*q2;
    1024      862625 :         const ftype SA2 = SA0*q0 + SA1*q1;
    1025      862625 :         const ftype SA3 = sq(q0) + sq(q1) - sq(q2) - sq(q3);
    1026             :         ftype SA4, SA5_inv;
    1027      862625 :         if (is_positive(sq(SA3))) {
    1028      853330 :             SA4 = 1.0F/sq(SA3);
    1029      853330 :             SA5_inv = sq(SA2)*SA4 + 1;
    1030      853330 :             canUseA = is_positive(fabsF(SA5_inv));
    1031             :         }
    1032             : 
    1033             :         bool canUseB = false;
    1034      862625 :         const ftype SB0 = 2*q0;
    1035      862625 :         const ftype SB1 = 2*q1;
    1036      862625 :         const ftype SB2 = SB0*q3 + SB1*q2;
    1037      862625 :         const ftype SB4 = sq(q0) + sq(q1) - sq(q2) - sq(q3);
    1038             :         ftype SB3, SB5_inv;
    1039      862625 :         if (is_positive(sq(SB2))) {
    1040      860647 :             SB3 = 1.0F/sq(SB2);
    1041      860647 :             SB5_inv = SB3*sq(SB4) + 1;
    1042      860647 :             canUseB = is_positive(fabsF(SB5_inv));
    1043             :         }
    1044             : 
    1045      862625 :         if (canUseA && (!canUseB || fabsF(SA5_inv) >= fabsF(SB5_inv))) {
    1046      535628 :             const ftype SA5 = 1.0F/SA5_inv;
    1047      535628 :             const ftype SA6 = 1.0F/(SA3);
    1048      535628 :             const ftype SA7 = SA2*SA4;
    1049      535628 :             const ftype SA8 = 2*SA7;
    1050      535628 :             const ftype SA9 = 2*SA6;
    1051             : 
    1052      535628 :             H_YAW[0] = SA5*(SA0*SA6 - SA8*q0);
    1053      535628 :             H_YAW[1] = SA5*(SA1*SA6 - SA8*q1);
    1054      535628 :             H_YAW[2] = SA5*(SA1*SA7 + SA9*q1);
    1055      535628 :             H_YAW[3] = SA5*(SA0*SA7 + SA9*q0);
    1056      862625 :         } else if (canUseB && (!canUseA || fabsF(SB5_inv) > fabsF(SA5_inv))) {
    1057      326997 :             const ftype SB5 = 1.0F/SB5_inv;
    1058      326997 :             const ftype SB6 = 1.0F/(SB2);
    1059      326997 :             const ftype SB7 = SB3*SB4;
    1060      326997 :             const ftype SB8 = 2*SB7;
    1061      326997 :             const ftype SB9 = 2*SB6;
    1062             : 
    1063      326997 :             H_YAW[0] = -SB5*(SB0*SB6 - SB8*q3);
    1064      326997 :             H_YAW[1] = -SB5*(SB1*SB6 - SB8*q2);
    1065      326997 :             H_YAW[2] = -SB5*(-SB1*SB7 - SB9*q2);
    1066      326997 :             H_YAW[3] = -SB5*(-SB0*SB7 - SB9*q3);
    1067      326997 :         } else {
    1068             :             return false;
    1069             :         }
    1070             : 
    1071             :         // predicted yaw and zero yaw body to earth rotation matrix for this order
    1072      862625 :         if (!buildTbnZeroYaw(order, Tbn_zeroYaw, &yawAngPredicted)) {
    1073             :             // rotation order not supported: cannot build the predicted yaw and zero-yaw rotation
    1074             :             return false;
    1075             :         }
    1076             : 
    1077      221257 :     } else if (order == rotationOrder::TAIT_BRYAN_312) {
    1078             :         // calculate 312 yaw observation matrix - option A or B to avoid singularity in derivation at +-90 degrees yaw
    1079             :         bool canUseA = false;
    1080      221257 :         const ftype SA0 = 2*q3;
    1081      221257 :         const ftype SA1 = 2*q2;
    1082      221257 :         const ftype SA2 = SA0*q0 - SA1*q1;
    1083      221257 :         const ftype SA3 = sq(q0) - sq(q1) + sq(q2) - sq(q3);
    1084             :         ftype SA4, SA5_inv;
    1085      221257 :         if (is_positive(sq(SA3))) {
    1086      219500 :             SA4 = 1.0F/sq(SA3);
    1087      219500 :             SA5_inv = sq(SA2)*SA4 + 1;
    1088      219500 :             canUseA = is_positive(fabsF(SA5_inv));
    1089             :         }
    1090             : 
    1091             :         bool canUseB = false;
    1092      221257 :         const ftype SB0 = 2*q0;
    1093      221257 :         const ftype SB1 = 2*q1;
    1094      221257 :         const ftype SB2 = -SB0*q3 + SB1*q2;
    1095      221257 :         const ftype SB4 = -sq(q0) + sq(q1) - sq(q2) + sq(q3);
    1096             :         ftype SB3, SB5_inv;
    1097      221257 :         if (is_positive(sq(SB2))) {
    1098      220462 :             SB3 = 1.0F/sq(SB2);
    1099      220462 :             SB5_inv = SB3*sq(SB4) + 1;
    1100      220462 :             canUseB = is_positive(fabsF(SB5_inv));
    1101             :         }
    1102             : 
    1103      221257 :         if (canUseA && (!canUseB || fabsF(SA5_inv) >= fabsF(SB5_inv))) {
    1104       89320 :             const ftype SA5 = 1.0F/SA5_inv;
    1105       89320 :             const ftype SA6 = 1.0F/(SA3);
    1106       89320 :             const ftype SA7 = SA2*SA4;
    1107       89320 :             const ftype SA8 = 2*SA7;
    1108       89320 :             const ftype SA9 = 2*SA6;
    1109             : 
    1110       89320 :             H_YAW[0] = SA5*(SA0*SA6 - SA8*q0);
    1111       89320 :             H_YAW[1] = SA5*(-SA1*SA6 + SA8*q1);
    1112       89320 :             H_YAW[2] = SA5*(-SA1*SA7 - SA9*q1);
    1113       89320 :             H_YAW[3] = SA5*(SA0*SA7 + SA9*q0);
    1114      221257 :         } else if (canUseB && (!canUseA || fabsF(SB5_inv) > fabsF(SA5_inv))) {
    1115      131937 :             const ftype SB5 = 1.0F/SB5_inv;
    1116      131937 :             const ftype SB6 = 1.0F/(SB2);
    1117      131937 :             const ftype SB7 = SB3*SB4;
    1118      131937 :             const ftype SB8 = 2*SB7;
    1119      131937 :             const ftype SB9 = 2*SB6;
    1120             : 
    1121      131937 :             H_YAW[0] = -SB5*(-SB0*SB6 + SB8*q3);
    1122      131937 :             H_YAW[1] = -SB5*(SB1*SB6 - SB8*q2);
    1123      131937 :             H_YAW[2] = -SB5*(-SB1*SB7 - SB9*q2);
    1124      131937 :             H_YAW[3] = -SB5*(SB0*SB7 + SB9*q3);
    1125      131937 :         } else {
    1126             :             return false;
    1127             :         }
    1128             : 
    1129             :         // predicted yaw and zero yaw body to earth rotation matrix for this order
    1130      221257 :         if (!buildTbnZeroYaw(order, Tbn_zeroYaw, &yawAngPredicted)) {
    1131             :             // rotation order not supported: cannot build the predicted yaw and zero-yaw rotation
    1132             :             return false;
    1133             :         }
    1134             :     } else {
    1135             :         // order not supported
    1136             :         return false;
    1137             :     }
    1138             : 
    1139             :     // Calculate the innovation
    1140     1083882 :     switch (method) {
    1141     1071905 :     case yawFusionMethod::MAGNETOMETER:
    1142             :     {
    1143             :         // Use the difference between the horizontal projection and declination to give the measured yaw
    1144             :         // rotate measured mag components into earth frame
    1145     1071905 :         Vector3F magMeasNED = Tbn_zeroYaw*magDataDelayed.mag;
    1146     1071905 :         ftype yawAngMeasured = wrap_PI(-atan2F(magMeasNED.y, magMeasNED.x) + MagDeclination());
    1147     1071905 :         innovYaw = wrap_PI(yawAngPredicted - yawAngMeasured);
    1148             :         break;
    1149             :     }
    1150             : 
    1151        4328 :     case yawFusionMethod::GPS:
    1152        4328 :         innovYaw = wrap_PI(yawAngPredicted - yawAngDataDelayed.yawAng);
    1153        4328 :         break;
    1154             : 
    1155         977 :     case yawFusionMethod::STATIC:
    1156         977 :         innovYaw = wrap_PI(yawAngPredicted - yawAngDataStatic.yawAng);
    1157         977 :         break;
    1158             : 
    1159           0 :     case yawFusionMethod::GSF:
    1160           0 :         innovYaw = wrap_PI(yawAngPredicted - gsfYaw);
    1161           0 :         break;
    1162             : 
    1163         418 :     case yawFusionMethod::PREDICTED:
    1164             :     default:
    1165         418 :         innovYaw = 0.0f;
    1166         418 :         break;
    1167             : 
    1168             : #if EK3_FEATURE_EXTERNAL_NAV
    1169        6254 :     case yawFusionMethod::EXTNAV:
    1170        6254 :         innovYaw = wrap_PI(yawAngPredicted - extNavYawAngDataDelayed.yawAng);
    1171        6254 :         break;
    1172             : #endif
    1173             :     }
    1174             : 
    1175             :     // Calculate innovation variance and Kalman gains, taking advantage of the fact that only the first 4 elements in H are non zero
    1176             :     ftype PH[4];
    1177             :     ftype varInnov = R_YAW;
    1178     5419410 :     for (uint8_t rowIndex=0; rowIndex<=3; rowIndex++) {
    1179     4335528 :         PH[rowIndex] = 0.0f;
    1180    21677640 :         for (uint8_t colIndex=0; colIndex<=3; colIndex++) {
    1181    17342112 :             PH[rowIndex] += P[rowIndex][colIndex]*H_YAW[colIndex];
    1182             :         }
    1183     4335528 :         varInnov += H_YAW[rowIndex]*PH[rowIndex];
    1184             :     }
    1185             :     ftype varInnovInv;
    1186     1083882 :     if (varInnov >= R_YAW) {
    1187     1083882 :         varInnovInv = 1.0f / varInnov;
    1188             :         // output numerical health status
    1189     1083882 :         faultStatus.bad_yaw = false;
    1190             :     } else {
    1191             :         // the calculation is badly conditioned, so we cannot perform fusion on this step
    1192             :         // we reset the covariance matrix and try again next measurement
    1193           0 :         CovarianceInit();
    1194             :         // output numerical health status
    1195           0 :         faultStatus.bad_yaw = true;
    1196           0 :         return false;
    1197             :     }
    1198             : 
    1199             :     // calculate Kalman gain
    1200    18451124 :     for (uint8_t rowIndex=0; rowIndex<=stateIndexLim; rowIndex++) {
    1201    17367242 :         Kfusion[rowIndex] = 0.0f;
    1202    86836210 :         for (uint8_t colIndex=0; colIndex<=3; colIndex++) {
    1203    69468968 :             Kfusion[rowIndex] += P[rowIndex][colIndex]*H_YAW[colIndex];
    1204             :         }
    1205    17367242 :         Kfusion[rowIndex] *= varInnovInv;
    1206             :     }
    1207             : 
    1208             :     // calculate the innovation test ratio
    1209     1083882 :     yawTestRatio = sq(innovYaw) / (sq(MAX(0.01f * (ftype)frontend->_yawInnovGate, 1.0f)) * varInnov);
    1210             : 
    1211             :     // Declare the magnetometer unhealthy if the innovation test fails
    1212     1083882 :     if (yawTestRatio > 1.0f) {
    1213       24780 :         magHealth = false;
    1214             :         // On the ground a large innovation could be due to large initial gyro bias or magnetic interference from nearby objects
    1215             :         // If we are flying, then it is more likely due to a magnetometer fault and we should not fuse the data
    1216       24780 :         if (inFlight) {
    1217             :             return false;
    1218             :         }
    1219             :     } else {
    1220     1059102 :         magHealth = true;
    1221             :     }
    1222             : 
    1223             :     // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
    1224             :     // the zero elements of H to reduce the number of operations.
    1225    18444766 :     for (unsigned i = 0; i<=stateIndexLim; i++) {
    1226             :         // j as the inner loop allows the compiler to hoist the KH product
    1227             :         // to save computation, and do the inner indexing more efficiently.
    1228   295736574 :         for (unsigned j = 0; j<=stateIndexLim; j++) {
    1229             :             ftype res = 0;
    1230   278375316 :             res += (Kfusion[i] * H_YAW[0]) * P[0][j];
    1231   278375316 :             res += (Kfusion[i] * H_YAW[1]) * P[1][j];
    1232   278375316 :             res += (Kfusion[i] * H_YAW[2]) * P[2][j];
    1233   278375316 :             res += (Kfusion[i] * H_YAW[3]) * P[3][j];
    1234   278375316 :             KHP[i][j] = res;
    1235             :         }
    1236             :     }
    1237             : 
    1238     1083508 :     const ftype innovFusion = constrain_ftype(innovYaw, -0.5f, 0.5f);
    1239             :     // finish fusion from KHP and Kfusion then record health status
    1240     1083508 :     faultStatus.bad_yaw = FinishFusion(innovFusion);
    1241             : 
    1242     1083508 :     return true;
    1243             : }
    1244             : 
    1245             : /*
    1246             :  * Fuse declination angle using explicit algebraic equations generated in
    1247             :  * derivation/generate_2.py with output recorded in derivation/generated/yaw_generated.cpp
    1248             : */
    1249     1674940 : void NavEKF3_core::FuseDeclination(ftype declErr)
    1250             : {
    1251             :     // declination error variance (rad^2)
    1252     1674940 :     const ftype R_DECL = sq(declErr);
    1253             : 
    1254             :     // copy required states to local variables
    1255     1674940 :     ftype magN = stateStruct.earth_magfield.x;
    1256     1674940 :     ftype magE = stateStruct.earth_magfield.y;
    1257             : 
    1258             :     // prevent bad earth field states from causing numerical errors or exceptions
    1259     1674940 :     if (magN < 1e-3f) {
    1260             :         return;
    1261             :     }
    1262             : 
    1263             :     // Calculate observation Jacobian and Kalman gains
    1264             :     // Calculate intermediate variables
    1265     1674932 :     const ftype HK0 = sq(magE) + sq(magN);
    1266             :     // if the horizontal magnetic field is too small, this calculation will be badly conditioned
    1267     1674932 :     if (HK0 < 1e-4f) {
    1268             :         return;
    1269             :     }
    1270     1674932 :     const ftype HK1 = 1.0F/(HK0);
    1271     1674932 :     const ftype HK2 = P[16][16]*magE - P[16][17]*magN;
    1272     1674932 :     const ftype HK3 = P[16][17]*magE - P[17][17]*magN;
    1273     1674932 :     const ftype HK4_denom = (sq(HK0)*R_DECL + HK2*magE - HK3*magN);
    1274             :     ftype HK4;
    1275     1674932 :     if (fabsF(HK4_denom) > 1e-6f) {
    1276     1674932 :         HK4 = HK0/HK4_denom;
    1277             :     } else {
    1278             :         return;
    1279             :     }
    1280             : 
    1281             :     // Calculate the observation Jacobian
    1282             :     // Note only 2 terms are non-zero which can be used in matrix operations for calculation of Kalman gains and covariance update to significantly reduce cost
    1283             :     ftype Hfusion[24] = {};
    1284     1674932 :     Hfusion[16] = -HK1*magE;
    1285     1674932 :     Hfusion[17] = HK1*magN;
    1286             : 
    1287             :     uint32_t kalman_mask = (1<<10)-1; // values to calculate in Kfusion (others are set to zero)
    1288             : 
    1289     1674932 :     if (!inhibitDelAngBiasStates) {
    1290             :         kalman_mask |= (1<<10) | (1<<11) | (1<<12);
    1291             :     }
    1292             : 
    1293     1674932 :     if (!inhibitDelVelBiasStates) {
    1294     6699472 :         for (uint8_t index = 0; index < 3; index++) {
    1295     5024604 :             const uint8_t stateIndex = index + 13;
    1296     5024604 :             if (!dvelBiasAxisInhibit[index]) {
    1297     5020720 :                 kalman_mask |= (1<<stateIndex);
    1298             :             }
    1299             :         }
    1300             :     }
    1301             : 
    1302     1674932 :     if (!inhibitMagStates) {
    1303     1674932 :         kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
    1304             :     }
    1305             : 
    1306     1674932 :     if (!inhibitWindStates && !treatWindStatesAsTruth) {
    1307      605889 :         kalman_mask |= (1<<22) | (1<<23);
    1308             :     }
    1309             : 
    1310    41873300 :     for (auto i=0; i<24; i++) {
    1311             :         ftype res = 0;
    1312    40198368 :         if (kalman_mask & (1<<i)) {
    1313    38056014 :             res = -HK4*(P[i][16]*magE-P[i][17]*magN);
    1314             :         }
    1315    40198368 :         Kfusion[i] = res;
    1316             :     }
    1317             : 
    1318             :     // get the magnetic declination
    1319     1674932 :     ftype magDecAng = MagDeclination();
    1320             : 
    1321             :     // Calculate the innovation
    1322     1674932 :     ftype innovation = atan2F(magE , magN) - magDecAng;
    1323             : 
    1324             :     // limit the innovation to protect against data errors
    1325     1674932 :     if (innovation > 0.5f) {
    1326             :         innovation = 0.5f;
    1327     1674932 :     } else if (innovation < -0.5f) {
    1328             :         innovation = -0.5f;
    1329             :     }
    1330             : 
    1331             :     // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
    1332             :     // the zero elements of H to reduce the number of operations.
    1333    39748054 :     for (unsigned i = 0; i<=stateIndexLim; i++) {
    1334             :         // j as the inner loop allows the compiler to hoist the KH product
    1335             :         // to save computation, and do the inner indexing more efficiently.
    1336   905072638 :         for (unsigned j = 0; j<=stateIndexLim; j++) {
    1337             :             ftype res = 0;
    1338   866999516 :             res += (Kfusion[i] * Hfusion[16]) * P[16][j];
    1339   866999516 :             res += (Kfusion[i] * Hfusion[17]) * P[17][j];
    1340   866999516 :             KHP[i][j] = res;
    1341             :         }
    1342             :     }
    1343             : 
    1344             :     // finish fusion from KHP and Kfusion then record health status
    1345     1674932 :     faultStatus.bad_decl = FinishFusion(innovation);
    1346             : }
    1347             : 
    1348             : /********************************************************
    1349             : *                   MISC FUNCTIONS                      *
    1350             : ********************************************************/
    1351             : 
    1352             : // align the NE earth magnetic field states with the published declination
    1353        7819 : void NavEKF3_core::alignMagStateDeclination()
    1354             : {
    1355             :     // don't do this if we already have a learned magnetic field
    1356        7819 :     if (magFieldLearned) {
    1357           0 :         return;
    1358             :     }
    1359             : 
    1360             :     // get the magnetic declination
    1361        7819 :     ftype magDecAng = MagDeclination();
    1362             : 
    1363             :     // rotate the NE values so that the declination matches the published value
    1364        7819 :     Vector3F initMagNED = stateStruct.earth_magfield;
    1365        7819 :     ftype magLengthNE = initMagNED.xy().length();
    1366        7819 :     stateStruct.earth_magfield.x = magLengthNE * cosF(magDecAng);
    1367        7819 :     stateStruct.earth_magfield.y = magLengthNE * sinF(magDecAng);
    1368             : 
    1369        7819 :     if (!inhibitMagStates) {
    1370             :         // zero the corresponding state covariances if magnetic field state learning is active
    1371         735 :         ftype var_16 = P[16][16];
    1372         735 :         ftype var_17 = P[17][17];
    1373         735 :         zeroStatesVarCov(16, 17);
    1374         735 :         P[16][16] = var_16;
    1375         735 :         P[17][17] = var_17;
    1376             : 
    1377             :         // fuse the declination angle to establish covariances and prevent large swings in declination
    1378             :         // during initial fusion
    1379         735 :         FuseDeclination(0.1f);
    1380             : 
    1381             :     }
    1382             : }
    1383             : 
    1384             : // record a magnetic field state reset event
    1385        4205 : void NavEKF3_core::recordMagReset()
    1386             : {
    1387        4205 :     magStateResetRequest = false;
    1388        4205 :     magStateInitComplete = true;
    1389        4205 :     if (inFlight) {
    1390        1094 :         finalInflightMagInit = true;
    1391             :     }
    1392             :     // take a snap-shot of the vertical position, quaternion  and yaw innovation to use as a reference
    1393             :     // for post alignment checks
    1394        4205 :     posDownAtLastMagReset = stateStruct.position.z;
    1395        4205 :     quatAtLastMagReset = stateStruct.quat;
    1396        4205 :     yawInnovAtLastMagReset = innovYaw;
    1397        4205 : }
    1398             : 
    1399             : /*
    1400             :   learn magnetometer biases from GPS yaw. Return true if the
    1401             :   resulting mag vector is close enough to the one predicted by GPS
    1402             :   yaw to use it for fallback
    1403             : */
    1404        1932 : bool NavEKF3_core::learnMagBiasFromGPS(void)
    1405             : {
    1406        1932 :     if (!have_table_earth_field) {
    1407             :         // we need the earth field from WMM
    1408             :         return false;
    1409             :     }
    1410        1518 :     if (!inFlight) {
    1411             :         // don't start learning till we've started flying
    1412             :         return false;
    1413             :     }
    1414             : 
    1415         632 :     mag_elements mag_data;
    1416         632 :     if (!storedMag.recall(mag_data, imuDataDelayed.time_ms)) {
    1417             :         // no mag data to correct
    1418             :         return false;
    1419             :     }
    1420             : 
    1421             :     // combine yaw with current quaternion to get yaw corrected quaternion
    1422         632 :     QuaternionF quat = stateStruct.quat;
    1423         632 :     if (yawAngDataDelayed.order == rotationOrder::TAIT_BRYAN_321) {
    1424         632 :         Vector3F euler321;
    1425         632 :         quat.to_euler(euler321.x, euler321.y, euler321.z);
    1426         632 :         quat.from_euler(euler321.x, euler321.y, yawAngDataDelayed.yawAng);
    1427           0 :     } else if (yawAngDataDelayed.order == rotationOrder::TAIT_BRYAN_312) {
    1428           0 :         Vector3F euler312 = quat.to_vector312();
    1429           0 :         quat.from_vector312(euler312.x, euler312.y, yawAngDataDelayed.yawAng);
    1430             :     } else {
    1431             :         // rotation order not supported
    1432             :         return false;
    1433             :     }
    1434             : 
    1435             :     // build the expected body field from orientation and table earth field
    1436         632 :     Matrix3F dcm;
    1437         632 :     quat.rotation_matrix(dcm);
    1438         632 :     Vector3F expected_body_field = dcm.transposed() * table_earth_field_ga;
    1439             : 
    1440             :     // calculate error in field
    1441         632 :     Vector3F err = (expected_body_field - mag_data.mag) + stateStruct.body_magfield;
    1442             : 
    1443             :     // learn body frame mag biases
    1444         632 :     stateStruct.body_magfield -= err * EK3_GPS_MAG_LEARN_RATE;
    1445             : 
    1446             :     // check if error is below threshold. If it is then we can
    1447             :     // fallback to magnetometer on failure of external yaw
    1448         632 :     ftype err_length = err.length();
    1449             : 
    1450             :     // we allow for yaw backback to compass if we have had 50 samples
    1451             :     // in a row below the threshold. This corresponds to 10 seconds
    1452             :     // for a 5Hz GPS
    1453             :     const uint8_t fallback_count_threshold = 50;
    1454             : 
    1455         632 :     if (err_length > EK3_GPS_MAG_LEARN_LIMIT) {
    1456           2 :         gps_yaw_fallback_good_counter = 0;
    1457         630 :     } else if (gps_yaw_fallback_good_counter < fallback_count_threshold) {
    1458         318 :         gps_yaw_fallback_good_counter++;
    1459             :     }
    1460         632 :     bool ok = gps_yaw_fallback_good_counter >= fallback_count_threshold;
    1461         632 :     if (ok) {
    1462             :         // mark mag healthy to prevent a magTimeout when we start using it
    1463         318 :         lastHealthyMagTime_ms = imuSampleTime_ms;
    1464             :     }
    1465             :     return ok;
    1466             : }
    1467             : 
    1468             : // Reset states using yaw from EKF-GSF and velocity and position from GPS
    1469        1670 : bool NavEKF3_core::EKFGSF_resetMainFilterYaw(bool emergency_reset)
    1470             : {
    1471             :     // Don't do a reset unless permitted by the EK3_GSF_USE_MASK and EK3_GSF_RUN_MASK parameter masks
    1472        1670 :     if ((yawEstimator == nullptr)
    1473        1670 :         || !(frontend->_gsfUseMask & (1U<<core_index))) {
    1474           0 :         return false;
    1475             :     };
    1476             : 
    1477             :     // limit the number of emergency resets
    1478        1670 :     if (emergency_reset && (EKFGSF_yaw_reset_count >= frontend->_gsfResetMaxCount)) {
    1479             :         return false;
    1480             :     }
    1481             : 
    1482             :     ftype yawEKFGSF, yawVarianceEKFGSF;
    1483        1670 :     if (EKFGSF_getYaw(yawEKFGSF, yawVarianceEKFGSF)) {
    1484             :         // keep roll and pitch and reset yaw
    1485             :         rotationOrder order;
    1486           4 :         bestRotationOrder(order);
    1487           4 :         resetQuatStateYawOnly(yawEKFGSF, yawVarianceEKFGSF, order);
    1488             : 
    1489             :         // record the emergency reset event
    1490           4 :         EKFGSF_yaw_reset_request_ms = 0;
    1491           4 :         EKFGSF_yaw_reset_ms = imuSampleTime_ms;
    1492           4 :         EKFGSF_yaw_reset_count++;
    1493             : 
    1494           4 :         if ((yaw_source_last == AP_NavEKF_Source::SourceYaw::GSF) ||
    1495           6 :             !use_compass() || (dal.compass().get_num_enabled() == 0)) {
    1496           2 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u yaw aligned using GPS",(unsigned)imu_index);
    1497             :         } else {
    1498           2 :             GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "EKF3 IMU%u emergency yaw reset",(unsigned)imu_index);
    1499             :         }
    1500             : 
    1501             :         // Fail the magnetomer so it doesn't get used and pull the yaw away from the correct value
    1502           4 :         if (emergency_reset) {
    1503           2 :             allMagSensorsFailed = true;
    1504             :         }
    1505             : 
    1506             :         // record the yaw reset event
    1507           4 :         recordYawResetsCompleted();
    1508             : 
    1509             :         // reset velocity and position states to GPS - if yaw is fixed then the filter should start to operate correctly
    1510           4 :         ResetVelocity(resetDataSource::DEFAULT);
    1511           4 :         ResetPosition(resetDataSource::DEFAULT);
    1512             : 
    1513             :         // reset test ratios that are reported to prevent a race condition with the external state machine requesting the reset
    1514           4 :         velTestRatio = 0.0f;
    1515           4 :         posTestRatio = 0.0f;
    1516             : 
    1517             :         return true;
    1518             : 
    1519             :     }
    1520             : 
    1521             :     return false;
    1522             : 
    1523             : }
    1524             : 
    1525             : // returns true on success and populates yaw (in radians) and yawVariance (rad^2)
    1526     1009421 : bool NavEKF3_core::EKFGSF_getYaw(ftype &yaw, ftype &yawVariance) const
    1527             : {
    1528             :     // return immediately if no yaw estimator
    1529     1009421 :     if (yawEstimator == nullptr) {
    1530             :         return false;
    1531             :     }
    1532             : 
    1533             :     ftype velInnovLength;
    1534     2017652 :     if (yawEstimator->getYawData(yaw, yawVariance) &&
    1535     1008231 :         is_positive(yawVariance) &&
    1536     2674763 :         yawVariance < sq(radians(GSF_YAW_ACCURACY_THRESHOLD_DEG)) &&
    1537      999733 :         (assume_zero_sideslip() || (yawEstimator->getVelInnovLength(velInnovLength) && velInnovLength < frontend->maxYawEstVelInnov))) {
    1538      655897 :         return true;
    1539             :     }
    1540             : 
    1541             :     return false;
    1542             : }
    1543             : 
    1544        4237 : void NavEKF3_core::resetQuatStateYawOnly(ftype yaw, ftype yawVariance, rotationOrder order)
    1545             : {
    1546        4237 :     QuaternionF quatBeforeReset = stateStruct.quat;
    1547             : 
    1548             :     // check if we should use a 321 or 312 Rotation order and update the quaternion
    1549             :     // states using the preferred yaw definition
    1550        4237 :     stateStruct.quat.inverse().rotation_matrix(prevTnb);
    1551        4237 :     Vector3F eulerAngles;
    1552        4237 :     if (order == rotationOrder::TAIT_BRYAN_321) {
    1553             :         // rolled more than pitched so use 321 rotation order
    1554        3017 :         stateStruct.quat.to_euler(eulerAngles.x, eulerAngles.y, eulerAngles.z);
    1555        3017 :         stateStruct.quat.from_euler(eulerAngles.x, eulerAngles.y, yaw);
    1556        1220 :     } else if (order == rotationOrder::TAIT_BRYAN_312) {
    1557             :         // pitched more than rolled so use 312 rotation order
    1558        1220 :         eulerAngles = stateStruct.quat.to_vector312();
    1559        1220 :         stateStruct.quat.from_vector312(eulerAngles.x, eulerAngles.y, yaw);
    1560             :     } else {
    1561             :         // rotation order not supported
    1562           0 :         return;
    1563             :     }
    1564             : 
    1565             :     // Update the rotation matrix
    1566        4237 :     stateStruct.quat.inverse().rotation_matrix(prevTnb);
    1567             :     
    1568             :     // calculate the change in the quaternion state and apply it to the output history buffer
    1569        4237 :     QuaternionF quat_delta = stateStruct.quat / quatBeforeReset;
    1570        4237 :     StoreQuatRotate(quat_delta);
    1571             : 
    1572             :     // assume tilt uncertainty split equally between roll and pitch
    1573        4237 :     Vector3F angleErrVarVec = Vector3F(0.5 * tiltErrorVariance, 0.5 * tiltErrorVariance, yawVariance);
    1574        4237 :     CovariancePrediction(&angleErrVarVec);
    1575             : 
    1576             :     // record the yaw reset event
    1577        4237 :     yawResetCount++;
    1578             : 
    1579             :     // record the yaw reset event
    1580        4237 :     recordYawResetsCompleted();
    1581             : }

Generated by: LCOV version 1.14