LCOV - code coverage report
Current view: top level - AP_NavEKF3 - AP_NavEKF3_Measurements.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 611 657 93.0 %
Date: 2026-09-09 11:02:08 Functions: 32 34 94.1 %

          Line data    Source code
       1             : #include "AP_NavEKF3_core.h"
       2             : 
       3             : #include "AP_NavEKF3.h"
       4             : #include "AP_NavEKF3_feature.h"
       5             : 
       6             : #include <GCS_MAVLink/GCS.h>
       7             : #include <AP_Logger/AP_Logger.h>
       8             : #include <AP_DAL/AP_DAL.h>
       9             : #include <AP_InternalError/AP_InternalError.h>
      10             : 
      11             : #if AP_RANGEFINDER_ENABLED
      12             : /********************************************************
      13             : *              OPT FLOW AND RANGE FINDER                *
      14             : ********************************************************/
      15             : 
      16             : // Read the range finder and take new measurements if available
      17             : // Apply a median filter
      18    18761238 : void NavEKF3_core::readRangeFinder(void)
      19             : {
      20             :     uint8_t midIndex;
      21             :     uint8_t maxIndex;
      22             :     uint8_t minIndex;
      23             :     // get theoretical correct range when the vehicle is on the ground
      24             :     // don't allow range to go below 5cm because this can cause problems with optical flow processing
      25    18761238 :     const auto *_rng = dal.rangefinder();
      26    18761238 :     if (_rng == nullptr) {
      27             :         return;
      28             :     }
      29     1802100 :     rngOnGnd = MAX(_rng->ground_clearance_orient(ROTATION_PITCH_270), 0.05f);
      30             : 
      31             :     // limit update rate to maximum allowed by data buffers
      32     1802100 :     if ((imuSampleTime_ms - lastRngMeasTime_ms) > frontend->sensorIntervalMin_ms) {
      33             : 
      34             :         // reset the timer used to control the measurement rate
      35      387544 :         lastRngMeasTime_ms =  imuSampleTime_ms;
      36             : 
      37             :         // store samples and sample time into a ring buffer if valid
      38             :         // use data from two range finders if available
      39             : 
      40     1162632 :         for (uint8_t sensorIndex = 0; sensorIndex < ARRAY_SIZE(rngMeasIndex); sensorIndex++) {
      41      775088 :             const auto *sensor = _rng->get_backend(sensorIndex);
      42      775088 :             if (sensor == nullptr) {
      43      556878 :                 continue;
      44             :             }
      45      410917 :             if ((sensor->orientation() == ROTATION_PITCH_270) && (sensor->status() == AP_DAL_RangeFinder::Status::Good)) {
      46      218210 :                 rngMeasIndex[sensorIndex] ++;
      47      218210 :                 if (rngMeasIndex[sensorIndex] > 2) {
      48       72679 :                     rngMeasIndex[sensorIndex] = 0;
      49             :                 }
      50      218210 :                 storedRngMeasTime_ms[sensorIndex][rngMeasIndex[sensorIndex]] = imuSampleTime_ms - 25;
      51      218210 :                 storedRngMeas[sensorIndex][rngMeasIndex[sensorIndex]] = sensor->distance();
      52             :             } else {
      53      192707 :                 continue;
      54             :             }
      55             : 
      56             :             // check for three fresh samples
      57      218210 :             bool sampleFresh[DOWNWARD_RANGEFINDER_MAX_INSTANCES][3] = {};
      58      872840 :             for (uint8_t index = 0; index <= 2; index++) {
      59      654630 :                 sampleFresh[sensorIndex][index] = (imuSampleTime_ms - storedRngMeasTime_ms[sensorIndex][index]) < 500;
      60             :             }
      61             : 
      62             :             // find the median value if we have three fresh samples
      63      218210 :             if (sampleFresh[sensorIndex][0] && sampleFresh[sensorIndex][1] && sampleFresh[sensorIndex][2]) {
      64      217726 :                 if (storedRngMeas[sensorIndex][0] > storedRngMeas[sensorIndex][1]) {
      65             :                     minIndex = 1;
      66             :                     maxIndex = 0;
      67             :                 } else {
      68             :                     minIndex = 0;
      69             :                     maxIndex = 1;
      70             :                 }
      71      217726 :                 if (storedRngMeas[sensorIndex][2] > storedRngMeas[sensorIndex][maxIndex]) {
      72             :                     midIndex = maxIndex;
      73      180411 :                 } else if (storedRngMeas[sensorIndex][2] < storedRngMeas[sensorIndex][minIndex]) {
      74             :                     midIndex = minIndex;
      75             :                 } else {
      76             :                     midIndex = 2;
      77             :                 }
      78             : 
      79             :                 // don't allow time to go backwards
      80      217726 :                 if (storedRngMeasTime_ms[sensorIndex][midIndex] > rangeDataNew.time_ms) {
      81      132551 :                     rangeDataNew.time_ms = storedRngMeasTime_ms[sensorIndex][midIndex];
      82             :                 }
      83             : 
      84             :                 // limit the measured range to be no less than the on-ground range
      85      217726 :                 rangeDataNew.rng = MAX(storedRngMeas[sensorIndex][midIndex],rngOnGnd);
      86             : 
      87             :                 // get position in body frame for the current sensor
      88      217726 :                 rangeDataNew.sensor_idx = sensorIndex;
      89             : 
      90             :                 // write data to buffer with time stamp to be fused when the fusion time horizon catches up with it
      91      217726 :                 storedRange.push(rangeDataNew);
      92             : 
      93             :                 // indicate we have updated the measurement
      94      217726 :                 rngValidMeaTime_ms = imuSampleTime_ms;
      95             : 
      96         484 :             } else if (onGround && ((imuSampleTime_ms - rngValidMeaTime_ms) > 200)) {
      97             :                 // before takeoff we assume on-ground range value if there is no data
      98          26 :                 rangeDataNew.time_ms = imuSampleTime_ms;
      99          26 :                 rangeDataNew.rng = rngOnGnd;
     100             : 
     101             :                 // write data to buffer with time stamp to be fused when the fusion time horizon catches up with it
     102          26 :                 storedRange.push(rangeDataNew);
     103             : 
     104             :                 // indicate we have updated the measurement
     105          26 :                 rngValidMeaTime_ms = imuSampleTime_ms;
     106             : 
     107             :             }
     108             :         }
     109             :     }
     110             : }
     111             : #endif  // AP_RANGEFINDER_ENABLED
     112             : 
     113        7162 : void NavEKF3_core::writeBodyFrameOdom(float quality, const Vector3f &delPos, const Vector3f &delAng, float delTime, uint32_t timeStamp_ms, uint16_t delay_ms, const Vector3f &posOffset)
     114             : {
     115             : #if EK3_FEATURE_BODY_ODOM
     116             :     // protect against NaN
     117        7162 :     if (isnan(quality) || delPos.is_nan() || delAng.is_nan() || isnan(delTime) || posOffset.is_nan()) {
     118           0 :         return;
     119             :     }
     120             : 
     121             :     // limit update rate to maximum allowed by sensor buffers and fusion process
     122             :     // don't try to write to buffer until the filter has been initialised
     123        7162 :     if (((timeStamp_ms - bodyOdmMeasTime_ms) < frontend->sensorIntervalMin_ms) || (delTime < dtEkfAvg) || !statesInitialised) {
     124             :         return;
     125             :     }
     126             : 
     127             :     // subtract delay from timestamp
     128        4170 :     timeStamp_ms -= delay_ms;
     129             : 
     130        4170 :     bodyOdmDataNew.body_offset = posOffset.toftype();
     131        4170 :     bodyOdmDataNew.vel = delPos.toftype() * (1.0/delTime);
     132        4170 :     bodyOdmDataNew.time_ms = timeStamp_ms;
     133        4170 :     bodyOdmDataNew.angRate = (delAng * (1.0/delTime)).toftype();
     134        4170 :     bodyOdmMeasTime_ms = timeStamp_ms;
     135             : 
     136             :     // simple model of accuracy
     137             :     // TODO move this calculation outside of EKF into the sensor driver
     138        4170 :     bodyOdmDataNew.velErr = frontend->_visOdmVelErrMin + (frontend->_visOdmVelErrMax - frontend->_visOdmVelErrMin) * (1.0f - 0.01f * quality);
     139             : 
     140        4170 :     storedBodyOdm.push(bodyOdmDataNew);
     141             : #endif // EK3_FEATURE_BODY_ODOM
     142             : }
     143             : 
     144        8306 : void NavEKF3_core::writeWheelOdom(float delAng, float delTime, uint32_t timeStamp_ms, const Vector3f &posOffset, float radius)
     145             : {
     146             : #if EK3_FEATURE_BODY_ODOM
     147             :     // This is a simple hack to get wheel encoder data into the EKF and verify the interface sign conventions and units
     148             :     // It uses the exisiting body frame velocity fusion.
     149             :     // TODO implement a dedicated wheel odometry observation model
     150             : 
     151             :     // rate limiting to 50hz should be done by the caller
     152             :     // limit update rate to maximum allowed by sensor buffers and fusion process
     153             :     // don't try to write to buffer until the filter has been initialised
     154        8306 :     if ((delTime < dtEkfAvg) || !statesInitialised) {
     155        2656 :         return;
     156             :     }
     157             : 
     158        5650 :     wheel_odm_elements wheelOdmDataNew = {};
     159        5650 :     wheelOdmDataNew.hub_offset = posOffset.toftype();
     160        5650 :     wheelOdmDataNew.delAng = delAng;
     161        5650 :     wheelOdmDataNew.radius = radius;
     162        5650 :     wheelOdmDataNew.delTime = delTime;
     163             : 
     164             :     // because we are currently converting to an equivalent velocity measurement before fusing
     165             :     // the measurement time is moved back to the middle of the sampling period
     166        5650 :     wheelOdmDataNew.time_ms = timeStamp_ms - (uint32_t)(500.0f * delTime);
     167             : 
     168        5650 :     storedWheelOdm.push(wheelOdmDataNew);
     169             : #endif // EK3_FEATURE_BODY_ODOM
     170             : }
     171             : 
     172             : #if EK3_FEATURE_OPTFLOW_FUSION
     173             : // write the raw optical flow measurements
     174             : // this needs to be called externally.
     175       19976 : void NavEKF3_core::writeOptFlowMeas(const uint8_t rawFlowQuality, const Vector2f &rawFlowRates, const Vector2f &rawGyroRates, const uint32_t msecFlowMeas, const Vector3f &posOffset, float heightOverride)
     176             : {
     177             :     // limit update rate to maximum allowed by sensor buffers
     178       19976 :     if ((imuSampleTime_ms - flowMeaTime_ms) < frontend->sensorIntervalMin_ms) {
     179             :         return;
     180             :     }
     181             : 
     182             :     // The raw measurements need to be optical flow rates in radians/second averaged across the time since the last update
     183             :     // The PX4Flow sensor outputs flow rates with the following axis and sign conventions:
     184             :     // A positive X rate is produced by a positive sensor rotation about the X axis
     185             :     // A positive Y rate is produced by a positive sensor rotation about the Y axis
     186             :     // This filter uses a different definition of optical flow rates to the sensor with a positive optical flow rate produced by a
     187             :     // negative rotation about that axis. For example a positive rotation of the flight vehicle about its X (roll) axis would produce a negative X flow rate
     188       19976 :     flowMeaTime_ms = imuSampleTime_ms;
     189             :     // calculate bias errors on flow sensor gyro rates, but protect against spikes in data
     190             :     // reset the accumulated body delta angle and time
     191             :     // don't do the calculation if not enough time lapsed for a reliable body rate measurement
     192       19976 :     if (delTimeOF > 0.01f) {
     193       19668 :         flowGyroBias.x = 0.99f * flowGyroBias.x + 0.01f * constrain_ftype((rawGyroRates.x - delAngBodyOF.x/delTimeOF),-0.1f,0.1f);
     194       19668 :         flowGyroBias.y = 0.99f * flowGyroBias.y + 0.01f * constrain_ftype((rawGyroRates.y - delAngBodyOF.y/delTimeOF),-0.1f,0.1f);
     195       19668 :         delAngBodyOF.zero();
     196       19668 :         delTimeOF = 0.0f;
     197             :     }
     198             :     // by definition if this function is called, then flow measurements have been provided so we
     199             :     // need to run the optical flow takeoff detection
     200       19976 :     detectOptFlowTakeoff();
     201             : 
     202             :     // don't use data with a low quality indicator or extreme rates (helps catch corrupt sensor data)
     203       19976 :     if ((rawFlowQuality > 0) && rawFlowRates.length() < 4.2f && rawGyroRates.length() < 4.2f) {
     204             :         // correct flow sensor body rates for bias and write
     205       19976 :         of_elements ofDataNew {};
     206       19976 :         ofDataNew.bodyRadXYZ.x = rawGyroRates.x - flowGyroBias.x;
     207       19976 :         ofDataNew.bodyRadXYZ.y = rawGyroRates.y - flowGyroBias.y;
     208             :         // the sensor interface doesn't provide a z axis rate so use the rate from the nav sensor instead
     209       19976 :         if (delTimeOF > 0.001f) {
     210             :             // first preference is to use the rate averaged over the same sampling period as the flow sensor
     211           8 :             ofDataNew.bodyRadXYZ.z = delAngBodyOF.z / delTimeOF;
     212       19968 :         } else if (imuDataNew.delAngDT > 0.001f){
     213             :             // second preference is to use most recent IMU data
     214       19968 :             ofDataNew.bodyRadXYZ.z = imuDataNew.delAng.z / imuDataNew.delAngDT;
     215             :         } else {
     216             :             // third preference is use zero
     217           0 :             ofDataNew.bodyRadXYZ.z =  0.0f;
     218             :         }
     219             :         // write uncorrected flow rate measurements
     220             :         // note correction for different axis and sign conventions used by the px4flow sensor
     221       19976 :         ofDataNew.flowRadXY = - rawFlowRates.toftype(); // raw (non motion compensated) optical flow angular rate about the X axis (rad/sec)
     222             :         // write the flow sensor position in body frame
     223       19976 :         ofDataNew.body_offset = posOffset.toftype();
     224             :         // write the flow sensor height override
     225       19976 :         ofDataNew.heightOverride = heightOverride;
     226             :         // write flow rate measurements corrected for body rates
     227       19976 :         ofDataNew.flowRadXYcomp.x = ofDataNew.flowRadXY.x + ofDataNew.bodyRadXYZ.x;
     228       19976 :         ofDataNew.flowRadXYcomp.y = ofDataNew.flowRadXY.y + ofDataNew.bodyRadXYZ.y;
     229             :         // record time last observation was received so we can detect loss of data elsewhere
     230       19976 :         flowValidMeaTime_ms = imuSampleTime_ms;
     231             :         // estimate sample time of the measurement
     232       19976 :         ofDataNew.time_ms = imuSampleTime_ms - frontend->_flowDelay_ms - frontend->flowTimeDeltaAvg_ms/2;
     233             :         // Correct for the average intersampling delay due to the filter updaterate
     234       19976 :         ofDataNew.time_ms -= localFilterTimeStep_ms/2;
     235             :         // Prevent time delay exceeding age of oldest IMU data in the buffer
     236       19976 :         ofDataNew.time_ms = MAX(ofDataNew.time_ms,imuDataDelayed.time_ms);
     237             :         // Save data to buffer
     238       19976 :         storedOF.push(ofDataNew);
     239             :     }
     240             : }
     241             : #endif  // EK3_FEATURE_OPTFLOW_FUSION
     242             : 
     243             : 
     244             : /********************************************************
     245             : *                      MAGNETOMETER                     *
     246             : ********************************************************/
     247             : 
     248             : // try changing compass, return true if a new compass is found
     249        5315 : void NavEKF3_core::tryChangeCompass(void)
     250             : {
     251        5315 :     const auto &compass = dal.compass();
     252        5315 :     const uint8_t maxCount = compass.get_count();
     253             : 
     254             :     // search through the list of magnetometers
     255       15945 :     for (uint8_t i=1; i<maxCount; i++) {
     256       10630 :         uint8_t tempIndex = magSelectIndex + i;
     257             :         // loop back to the start index if we have exceeded the bounds
     258       10630 :         if (tempIndex >= maxCount) {
     259          19 :             tempIndex -= maxCount;
     260             :         }
     261       10630 :         tryChangeCompass(tempIndex);
     262             :     }
     263        5315 : }
     264             : 
     265             : // try changing to a specific compass index
     266       10645 : void NavEKF3_core::tryChangeCompass(uint8_t mag_index)
     267             : {
     268       10645 :     const auto &compass = dal.compass();
     269       10645 :     const uint8_t maxCount = compass.get_count();
     270             : 
     271       10645 :     if (mag_index >= maxCount) {
     272             :         // we assume the passed index should be valid
     273             :         return;
     274             :     }
     275             : 
     276             :      // if the magnetometer is allowed to be used for yaw and has a different index, we start using it
     277       10645 :     if (compass.healthy(mag_index) && compass.use_for_yaw(mag_index) && mag_index != magSelectIndex) {
     278          35 :         magSelectIndex = mag_index;
     279          35 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u switching to compass %u",(unsigned)imu_index,magSelectIndex);
     280             :         // reset the timeout flag and timer
     281          35 :         magTimeout = false;
     282          35 :         lastHealthyMagTime_ms = imuSampleTime_ms;
     283             :         // zero the learned magnetometer bias states
     284          35 :         stateStruct.body_magfield.zero();
     285             :         // clear the measurement buffer
     286          35 :         storedMag.reset();
     287             :         // clear the data waiting flag so that we do not use any data pending from the previous sensor
     288          35 :         magDataToFuse = false;
     289             :         // request a reset of the magnetic field states
     290          35 :         magStateResetRequest = true;
     291             :         // declare the field unlearned so that the reset request will be obeyed
     292          35 :         magFieldLearned = false;
     293             :         // reset body mag variances on next CovariancePrediction
     294          35 :         needMagBodyVarReset = true;
     295          35 :         return;
     296             :     }
     297             : }
     298             : 
     299             : // check for new magnetometer data and update store measurements if available
     300    20784458 : void NavEKF3_core::readMagData()
     301             : {
     302    20784458 :     const auto &compass = dal.compass();
     303             : 
     304    20784458 :     if (!compass.available()) {
     305         604 :         allMagSensorsFailed = true;
     306         604 :         return;        
     307             :     }
     308             : 
     309             :     // If we are a vehicle with a sideslip constraint to aid yaw estimation and we have timed out on our last avialable
     310             :     // magnetometer, then declare the magnetometers as failed for this flight
     311    20783854 :     const uint8_t maxCount = compass.get_count();
     312    20783854 :     if (allMagSensorsFailed || (magTimeout && assume_zero_sideslip() && magSelectIndex >= maxCount-1 && inFlight)) {
     313           0 :         allMagSensorsFailed = true;
     314           0 :         return;
     315             :     }
     316             : 
     317    20783854 :     if (compass.learn_offsets_enabled()) {
     318           2 :         wasLearningCompass_ms = imuSampleTime_ms;
     319    20783852 :     } else if (wasLearningCompass_ms != 0 && imuSampleTime_ms - wasLearningCompass_ms > 1000) {
     320             :         // allow time for old data to clear the buffer before signalling other code that compass data can be used
     321           0 :         wasLearningCompass_ms = 0;
     322             :     }
     323             : 
     324             :     // If the magnetometer has timed out (been rejected for too long), we find another magnetometer to use if available
     325             :     // Don't do this if we are on the ground because there can be magnetic interference and we need to know if there is a problem
     326             :     // before taking off. Don't do this within the first 30 seconds from startup because the yaw error could be due to large yaw gyro bias affsets
     327             :     // if the timeout is due to a sensor failure, then declare a timeout regardless of onground status
     328    20783854 :     if (maxCount > 1) {
     329    20783854 :         bool fusionTimeout = magTimeout && !onGround && imuSampleTime_ms - ekfStartTime_ms > 30000 && !(frontend->_affinity & EKF_AFFINITY_MAG);
     330    20783854 :         bool sensorTimeout = !compass.healthy(magSelectIndex) && imuSampleTime_ms - lastMagRead_ms > frontend->magFailTimeLimit_ms;
     331    20783854 :         if (fusionTimeout || sensorTimeout) {
     332        5315 :             tryChangeCompass();
     333             :         }
     334             :     }
     335             : 
     336             :     // limit compass update rate to prevent high processor loading because magnetometer fusion is an expensive step and we could overflow the FIFO buffer
     337    20783854 :     if (use_compass() &&
     338    20783854 :         compass.healthy(magSelectIndex) &&
     339    19846541 :         ((compass.last_update_usec(magSelectIndex) - lastMagUpdate_us) > 1000 * frontend->sensorIntervalMin_ms)) {
     340             : 
     341             :         // detect changes to magnetometer offset parameters and reset states
     342     2794118 :         Vector3F nowMagOffsets = compass.get_offsets(magSelectIndex).toftype();
     343     2794118 :         bool changeDetected = lastMagOffsetsValid && (nowMagOffsets != lastMagOffsets);
     344             :         if (changeDetected) {
     345             :             // zero the learned magnetometer bias states
     346          67 :             stateStruct.body_magfield.zero();
     347             :             // clear the measurement buffer
     348          67 :             storedMag.reset();
     349             : 
     350             :             // reset body mag variances on next
     351             :             // CovariancePrediction. This copes with possible errors
     352             :             // in the new offsets
     353          67 :             needMagBodyVarReset = true;
     354             :         }
     355     2794118 :         lastMagOffsets = nowMagOffsets;
     356     2794118 :         lastMagOffsetsValid = true;
     357             : 
     358             :         // store time of last measurement update
     359     2794118 :         lastMagUpdate_us = compass.last_update_usec(magSelectIndex);
     360             : 
     361             :         // Magnetometer data at the current time horizon
     362     2794118 :         mag_elements magDataNew;
     363             : 
     364             :         // estimate of time magnetometer measurement was taken, allowing for delays
     365     2794118 :         magDataNew.time_ms = imuSampleTime_ms - frontend->magDelay_ms;
     366             : 
     367             :         // Correct for the average intersampling delay due to the filter updaterate
     368     2794118 :         magDataNew.time_ms -= localFilterTimeStep_ms/2;
     369             : 
     370             :         // read compass data and scale to improve numerical conditioning
     371     2794118 :         magDataNew.mag = (compass.get_field(magSelectIndex) * 0.001f).toftype();
     372             : 
     373             :         // check for consistent data between magnetometers
     374     2794118 :         consistentMagData = compass.consistent();
     375             : 
     376             :         // save magnetometer measurement to buffer to be fused later
     377     2794118 :         storedMag.push(magDataNew);
     378             : 
     379             :         // remember time we read compass, to detect compass sensor failure
     380     2794118 :         lastMagRead_ms = imuSampleTime_ms;
     381             :     }
     382             : }
     383             : 
     384             : /********************************************************
     385             : *                Inertial Measurements                  *
     386             : ********************************************************/
     387             : 
     388             : /*
     389             :  *  Read IMU delta angle and delta velocity measurements and downsample to 100Hz
     390             :  *  for storage in the data buffers used by the EKF. If the IMU data arrives at
     391             :  *  lower rate than 100Hz, then no downsampling or upsampling will be performed.
     392             :  *  Downsampling is done using a method that does not introduce coning or sculling
     393             :  *  errors.
     394             :  */
     395    79223583 : void NavEKF3_core::readIMUData(bool startPredictEnabled)
     396             : {
     397    79223583 :     const auto &ins = dal.ins();
     398             : 
     399             :     // calculate an averaged IMU update rate using a spike and lowpass filter combination
     400    79223583 :     dtIMUavg = 0.02f * constrain_ftype(ins.get_loop_delta_t(),0.5f * dtIMUavg, 2.0f * dtIMUavg) + 0.98f * dtIMUavg;
     401             : 
     402             :     // the imu sample time is used as a common time reference throughout the filter
     403    79223583 :     imuSampleTime_ms = frontend->imuSampleTime_us / 1000;
     404             : 
     405             :     uint8_t accel_active, gyro_active;
     406             : 
     407    79223583 :     if (ins.use_accel(imu_index)) {
     408             :         accel_active = imu_index;
     409             :     } else {
     410           0 :         accel_active = ins.get_first_usable_accel();
     411             :     }
     412             : 
     413    79223583 :     if (ins.use_gyro(imu_index)) {
     414             :         gyro_active = imu_index;
     415             :     } else {
     416           0 :         gyro_active = ins.get_first_usable_gyro();
     417             :     }
     418             : 
     419    79223583 :     if (gyro_active != gyro_index_active) {
     420             :         // we are switching active gyro at runtime. Copy over the
     421             :         // bias we have learned from the previously inactive
     422             :         // gyro. We don't re-init the bias uncertainty as it should
     423             :         // have the same uncertainty as the previously active gyro
     424           0 :         stateStruct.gyro_bias = inactiveBias[gyro_active].gyro_bias;
     425           0 :         gyro_index_active = gyro_active;
     426             :     }
     427             : 
     428    79223583 :     if (accel_active != accel_index_active) {
     429             :         // switch to the learned accel bias for this IMU
     430           0 :         stateStruct.accel_bias = inactiveBias[accel_active].accel_bias;
     431           0 :         accel_index_active = accel_active;
     432             :     }
     433             : 
     434             :     // update the inactive bias states
     435    79223583 :     learnInactiveBiases();
     436             : 
     437             :     // run movement check using IMU data
     438    79223583 :     updateMovementCheck();
     439             : 
     440    79223583 :     readDeltaVelocity(accel_index_active, imuDataNew.delVel, imuDataNew.delVelDT);
     441    79223583 :     accelPosOffset = ins.get_imu_pos_offset(accel_index_active).toftype();
     442    79223583 :     imuDataNew.accel_index = accel_index_active;
     443             :     
     444             :     // Get delta angle data from primary gyro or primary if not available
     445    79223583 :     readDeltaAngle(gyro_index_active, imuDataNew.delAng, imuDataNew.delAngDT);
     446    79223583 :     imuDataNew.delAngDT = MAX(imuDataNew.delAngDT, 1.0e-4f);
     447    79223583 :     imuDataNew.gyro_index = gyro_index_active;
     448             : 
     449             :     // Get current time stamp
     450    79223583 :     imuDataNew.time_ms = imuSampleTime_ms;
     451             : 
     452             :     // Accumulate the measurement time interval for the delta velocity and angle data
     453    79223583 :     imuDataDownSampledNew.delAngDT += imuDataNew.delAngDT;
     454    79223583 :     imuDataDownSampledNew.delVelDT += imuDataNew.delVelDT;
     455             : 
     456             :     // use the most recent IMU index for the downsampled IMU
     457             :     // data. This isn't strictly correct if we switch IMUs between
     458             :     // samples
     459    79223583 :     imuDataDownSampledNew.gyro_index = imuDataNew.gyro_index;
     460    79223583 :     imuDataDownSampledNew.accel_index = imuDataNew.accel_index;
     461             : 
     462             :     // Rotate quaternon atitude from previous to new and normalise.
     463             :     // Accumulation using quaternions prevents introduction of coning errors due to downsampling
     464    79223583 :     imuQuatDownSampleNew.rotate(imuDataNew.delAng);
     465    79223583 :     imuQuatDownSampleNew.normalize();
     466             : 
     467             :     // Rotate the latest delta velocity into body frame at the start of accumulation
     468    79223583 :     Matrix3F deltaRotMat;
     469    79223583 :     imuQuatDownSampleNew.rotation_matrix(deltaRotMat);
     470             : 
     471             :     // Apply the delta velocity to the delta velocity accumulator
     472    79223583 :     imuDataDownSampledNew.delVel += deltaRotMat*imuDataNew.delVel;
     473             : 
     474             :     // Keep track of the number of IMU frames since the last state prediction
     475    79223583 :     framesSincePredict++;
     476             : 
     477             :     /*
     478             :      * If the target EKF time step has been accumulated, and the frontend has allowed start of a new predict cycle,
     479             :      * then store the accumulated IMU data to be used by the state prediction, ignoring the frontend permission if more
     480             :      * than twice the target time has lapsed. Adjust the target EKF step time threshold to allow for timing jitter in the
     481             :      * IMU data.
     482             :      */
     483    79223583 :     if ((imuDataDownSampledNew.delAngDT >= (EKF_TARGET_DT-(dtIMUavg*0.5f)) && startPredictEnabled) ||
     484             :         (imuDataDownSampledNew.delAngDT >= 2.0f*EKF_TARGET_DT)) {
     485             : 
     486             :         // convert the accumulated quaternion to an equivalent delta angle
     487    20078569 :         imuQuatDownSampleNew.to_axis_angle(imuDataDownSampledNew.delAng);
     488             : 
     489             :         // Time stamp the data
     490    20078569 :         imuDataDownSampledNew.time_ms = imuSampleTime_ms;
     491             : 
     492             :         // Write data to the FIFO IMU buffer
     493    20078569 :         storedIMU.push_youngest_element(imuDataDownSampledNew);
     494             : 
     495             :         // calculate the achieved average time step rate for the EKF using a combination spike and LPF
     496    20078569 :         ftype dtNow = constrain_ftype(0.5f*(imuDataDownSampledNew.delAngDT+imuDataDownSampledNew.delVelDT),0.5f * dtEkfAvg, 2.0f * dtEkfAvg);
     497    20078569 :         dtEkfAvg = 0.98f * dtEkfAvg + 0.02f * dtNow;
     498             : 
     499             :         // do an addtional down sampling for data used to sample XY body frame drag specific forces
     500    20078569 :         SampleDragData(imuDataDownSampledNew);
     501             : 
     502             :         // zero the accumulated IMU data and quaternion
     503    20078569 :         imuDataDownSampledNew.delAng.zero();
     504    20078569 :         imuDataDownSampledNew.delVel.zero();
     505    20078569 :         imuDataDownSampledNew.delAngDT = 0.0f;
     506    20078569 :         imuDataDownSampledNew.delVelDT = 0.0f;
     507    20078569 :         imuQuatDownSampleNew[0] = 1.0f;
     508    20078569 :         imuQuatDownSampleNew[3] = imuQuatDownSampleNew[2] = imuQuatDownSampleNew[1] = 0.0f;
     509             : 
     510             :         // reset the counter used to let the frontend know how many frames have elapsed since we started a new update cycle
     511    20078569 :         framesSincePredict = 0;
     512             : 
     513             :         // set the flag to let the filter know it has new IMU data and needs to run
     514    20078569 :         runUpdates = true;
     515             : 
     516             :         // extract the oldest available data from the FIFO buffer
     517    20078569 :         imuDataDelayed = storedIMU.get_oldest_element();
     518             : 
     519             :         // protect against delta time going to zero
     520    20078569 :         ftype minDT = 0.1f * dtEkfAvg;
     521    20078569 :         imuDataDelayed.delAngDT = MAX(imuDataDelayed.delAngDT,minDT);
     522    20078569 :         imuDataDelayed.delVelDT = MAX(imuDataDelayed.delVelDT,minDT);
     523             : 
     524    20078569 :         updateTimingStatistics();
     525             :         
     526             :         // correct the extracted IMU data for sensor errors
     527    20078569 :         delAngCorrected = imuDataDelayed.delAng;
     528    20078569 :         delVelCorrected = imuDataDelayed.delVel;
     529    20078569 :         correctDeltaAngle(delAngCorrected, imuDataDelayed.delAngDT, imuDataDelayed.gyro_index);
     530    20078569 :         correctDeltaVelocity(delVelCorrected, imuDataDelayed.delVelDT, imuDataDelayed.accel_index);
     531             : 
     532    20078569 :     } else {
     533             :         // we don't have new IMU data in the buffer so don't run filter updates on this time step
     534    59145014 :         runUpdates = false;
     535             :     }
     536    79223583 : }
     537             : 
     538             : // read the delta velocity and corresponding time interval from the IMU
     539             : // return false if data is not available
     540    79223583 : bool NavEKF3_core::readDeltaVelocity(uint8_t ins_index, Vector3F &dVel, ftype &dVel_dt) {
     541    79223583 :     const auto &ins = dal.ins();
     542             : 
     543    79223583 :     if (ins_index < ins.get_accel_count()) {
     544    79223583 :         Vector3f dVelF;
     545             :         float dVel_dtF;
     546    79223583 :         ins.get_delta_velocity(ins_index, dVelF, dVel_dtF);
     547    79223583 :         dVel = dVelF.toftype();
     548    79223583 :         dVel_dt = dVel_dtF;
     549    79223583 :         dVel_dt = MAX(dVel_dt,1.0e-4);
     550             :         return true;
     551             :     }
     552             :     return false;
     553             : }
     554             : 
     555             : /********************************************************
     556             : *             Global Position Measurement               *
     557             : ********************************************************/
     558             : 
     559             : // check for new valid GPS data and update stored measurement if available
     560    19666871 : void NavEKF3_core::readGpsData()
     561             : {
     562             :     // check for new GPS data
     563    19666871 :     const auto &gps = dal.gps();
     564             : 
     565             :     // limit update rate to avoid overflowing the FIFO buffer
     566    19666871 :     if (gps.last_message_time_ms(selected_gps) - lastTimeGpsReceived_ms <= frontend->sensorIntervalMin_ms) {
     567    18320425 :         return;
     568             :     }
     569             : 
     570     1555411 :     if (gps.status(selected_gps) < AP_GPS_FixType::FIX_3D) {
     571             :         // report GPS fix status
     572      208965 :         gpsCheckStatus.bad_fix = true;
     573      208965 :         dal.snprintf(prearm_fail_string, sizeof(prearm_fail_string), "Waiting for 3D fix");
     574      208965 :         return;
     575             :     }
     576             : 
     577             :     // report GPS fix status
     578     1346446 :     gpsCheckStatus.bad_fix = false;
     579             : 
     580             :     // store fix time from previous read
     581             :     const uint32_t secondLastGpsTime_ms = lastTimeGpsReceived_ms;
     582             : 
     583             :     // get current fix time
     584     1346446 :     lastTimeGpsReceived_ms = gps.last_message_time_ms(selected_gps);
     585             : 
     586             :     // estimate when the GPS fix was valid, allowing for GPS processing and other delays
     587             :     // ideally we should be using a timing signal from the GPS receiver to set this time
     588             :     // Use the driver specified delay
     589     1346446 :     float gps_delay_sec = 0;
     590     1346446 :     gps.get_lag(selected_gps, gps_delay_sec);
     591     1346446 :     gpsDataNew.time_ms = lastTimeGpsReceived_ms - (uint32_t)(gps_delay_sec * 1000.0f);
     592             : 
     593             :     // Correct for the average intersampling delay due to the filter updaterate
     594     1346446 :     gpsDataNew.time_ms -= localFilterTimeStep_ms/2;
     595             : 
     596             :     // Prevent the time stamp falling outside the oldest and newest IMU data in the buffer
     597     1346446 :     gpsDataNew.time_ms = MIN(MAX(gpsDataNew.time_ms,imuDataDelayed.time_ms),imuDataDownSampledNew.time_ms);
     598             : 
     599             :     // Get which GPS we are using for position information
     600     1346446 :     gpsDataNew.sensor_idx = selected_gps;
     601             : 
     602             :     // read the NED velocity from the GPS
     603     1346446 :     gpsDataNew.vel = gps.velocity(selected_gps).toftype();
     604     1346446 :     gpsDataNew.have_vz = gps.have_vertical_velocity(selected_gps);
     605             : 
     606             :     // position and velocity are not yet corrected for sensor position
     607     1346446 :     gpsDataNew.corrected = false;
     608             : 
     609             :     // Use the speed and position accuracy from the GPS if available, otherwise set it to zero.
     610             :     // Apply a decaying envelope filter with a 5 second time constant to the raw accuracy data
     611     1346446 :     ftype alpha = constrain_ftype(0.0002f * (lastTimeGpsReceived_ms - secondLastGpsTime_ms),0.0f,1.0f);
     612     1346446 :     gpsSpdAccuracy *= (1.0f - alpha);
     613             :     float gpsSpdAccRaw;
     614     1346446 :     if (!gps.speed_accuracy(selected_gps, gpsSpdAccRaw)) {
     615        2943 :         gpsSpdAccuracy = 0.0f;
     616             :     } else {
     617     1343503 :         gpsSpdAccuracy = MAX(gpsSpdAccuracy,gpsSpdAccRaw);
     618     1343503 :         gpsSpdAccuracy = MIN(gpsSpdAccuracy,50.0f);
     619     1343503 :         gpsSpdAccuracy = MAX(gpsSpdAccuracy,frontend->_gpsHorizVelNoise);
     620             :     }
     621     1346446 :     gpsPosAccuracy *= (1.0f - alpha);
     622             :     float gpsPosAccRaw;
     623     1346446 :     if (!gps.horizontal_accuracy(selected_gps, gpsPosAccRaw)) {
     624         786 :         gpsPosAccuracy = 0.0f;
     625             :     } else {
     626     1345660 :         gpsPosAccuracy = MAX(gpsPosAccuracy,gpsPosAccRaw);
     627     1345660 :         gpsPosAccuracy = MIN(gpsPosAccuracy,100.0f);
     628     1345660 :         gpsPosAccuracy = MAX(gpsPosAccuracy, frontend->_gpsHorizPosNoise);
     629             :     }
     630     1346446 :     gpsHgtAccuracy *= (1.0f - alpha);
     631             :     float gpsHgtAccRaw;
     632     1346446 :     if (!gps.vertical_accuracy(selected_gps, gpsHgtAccRaw)) {
     633         786 :         gpsHgtAccuracy = 0.0f;
     634             :     } else {
     635     1345660 :         gpsHgtAccuracy = MAX(gpsHgtAccuracy,gpsHgtAccRaw);
     636     1345660 :         gpsHgtAccuracy = MIN(gpsHgtAccuracy,100.0f);
     637     1345660 :         gpsHgtAccuracy = MAX(gpsHgtAccuracy, 1.5f * frontend->_gpsHorizPosNoise);
     638             :     }
     639             : 
     640             :     // check if we have enough GPS satellites and increase the gps noise scaler if we don't
     641     1346446 :     if (gps.num_sats(selected_gps) >= 6 && (PV_AidingMode == AID_ABSOLUTE)) {
     642     1124882 :         gpsNoiseScaler = 1.0f;
     643      221564 :     } else if (gps.num_sats(selected_gps) == 5 && (PV_AidingMode == AID_ABSOLUTE)) {
     644           0 :         gpsNoiseScaler = 1.4f;
     645             :     } else { // <= 4 satellites or in constant position mode
     646      221564 :         gpsNoiseScaler = 2.0f;
     647             :     }
     648             : 
     649             :     // Check if GPS can output vertical velocity, vertical velocity use is permitted and set GPS fusion mode accordingly
     650     1346446 :     if (gpsDataNew.have_vz && frontend->sources.useVelZSource(AP_NavEKF_Source::SourceZ::GPS, core_index)) {
     651     1295891 :         useGpsVertVel = true;
     652             :     } else {
     653       50555 :         useGpsVertVel = false;
     654             :     }
     655             : 
     656     1346446 :     if (frontend->option_is_enabled(NavEKF3::Option::JammingExpected) &&
     657        5742 :         (lastTimeGpsReceived_ms - secondLastGpsTime_ms) > frontend->gpsNoFixTimeout_ms) {
     658          16 :         const bool doingBodyVelNav = (imuSampleTime_ms - prevBodyVelFuseTime_ms < 1000);
     659          16 :         const bool doingFlowNav = (imuSampleTime_ms - prevFlowFuseTime_ms < 1000);;
     660          16 :         const bool canDoWindRelNav = assume_zero_sideslip();
     661          16 :         const bool canDeadReckon = ((doingFlowNav && gndOffsetValid) || canDoWindRelNav || doingBodyVelNav);
     662             :         if (canDeadReckon) {
     663             :             // If we can do dead reckoning with a data source other than GPS there is time to wait
     664             :             // for GPS alignment checks to pass before using GPS inside the EKF.
     665             :             // this gets set back to false in calcGpsGoodToAlign() when GPS checks pass
     666          16 :             waitingForGpsChecks = true;
     667             :             // force GPS checks to restart
     668          16 :             lastPreAlignGpsCheckTime_ms = 0;
     669          16 :             lastGpsVelFail_ms = imuSampleTime_ms;
     670          16 :             lastGpsVelPass_ms = 0;
     671          16 :             gpsGoodToAlign = false;
     672             :         } else {
     673           0 :             waitingForGpsChecks = false;
     674             :         }
     675             :     }
     676             : 
     677             :     // Monitor quality of the GPS velocity data before and after alignment
     678     1346446 :     calcGpsGoodToAlign();
     679             : 
     680             :     // Post-alignment checks
     681     1346446 :     calcGpsGoodForFlight();
     682             : 
     683             :     // Read the GPS location in WGS-84 lat,long,height coordinates
     684     1346446 :     const Location &gpsloc = gps.location(selected_gps);
     685             : 
     686             :     // Set the EKF origin and magnetic field declination if not previously set and GPS checks have passed
     687     1346446 :     if (gpsGoodToAlign && !validOrigin) {
     688        1401 :         Location gpsloc_fieldelevation = gpsloc; 
     689             :         // if flying, correct for height change from takeoff so that the origin is at field elevation
     690        1401 :         if (inFlight) {
     691          63 :             gpsloc_fieldelevation.alt += (int32_t)(100.0f * stateStruct.position.z);
     692             :         }
     693             : 
     694        1401 :         if (!setOriginLLH(gpsloc_fieldelevation)) {
     695             :             // set an error as an attempt was made to set the origin more than once
     696           0 :             INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
     697           0 :             return;
     698             :         }
     699             : 
     700             :         // set the NE earth magnetic field states using the published declination
     701             :         // and set the corresponding variances and covariances
     702        1401 :         alignMagStateDeclination();
     703             : 
     704             :         // Set the height of the NED origin
     705        1401 :         ekfGpsRefHgt = (double)0.01 * (double)gpsloc.alt + (double)outputDataNew.position.z;
     706             : 
     707             :         // Set the uncertainty of the GPS origin height
     708        1401 :         ekfOriginHgtVar = sq(gpsHgtAccuracy);
     709             : 
     710             :     }
     711             : 
     712     1346446 :     if (gpsGoodToAlign && !have_table_earth_field) {
     713         170 :         setEarthFieldFromLocation(gpsloc);
     714             :     }
     715             : 
     716             :     // convert GPS measurements to local NED and save to buffer to be fused later if we have a valid origin
     717             :     // and are not waiting for GPs checks to pass
     718     1346446 :     if (validOrigin && !waitingForGpsChecks) {
     719     1248420 :         gpsDataNew.lat = gpsloc.lat;
     720     1248420 :         gpsDataNew.lng = gpsloc.lng;
     721     1248420 :         if ((frontend->_originHgtMode & (1<<2)) == 0) {
     722             :             // the height adjustment to match GPS is being achieved by adjusting the origin height
     723     1232971 :             gpsDataNew.hgt = (ftype)((double)0.01 * (double)gpsloc.alt - ekfGpsRefHgt);
     724             :         } else {
     725             :             // the height adjustment to match GPS is being achieved by adjusting the measurements
     726       15449 :             gpsDataNew.hgt = 0.01 * (gpsloc.alt - EKF_origin.alt);
     727             :         }
     728     1248420 :         storedGPS.push(gpsDataNew);
     729             :         // declare GPS in use
     730     1248420 :         gpsIsInUse = true;
     731             :     }
     732             : }
     733             : 
     734             : // check for new valid GPS yaw data
     735    19666871 : void NavEKF3_core::readGpsYawData()
     736             : {
     737    19666871 :     const auto &gps = dal.gps();
     738             : 
     739             :     // if the GPS has yaw data then fuse it as an Euler yaw angle
     740             :     float yaw_deg, yaw_accuracy_deg;
     741             :     uint32_t yaw_time_ms;
     742    17816285 :     if (gps.status(selected_gps) >= AP_GPS_FixType::FIX_3D &&
     743    19666871 :         dal.gps().gps_yaw_deg(selected_gps, yaw_deg, yaw_accuracy_deg, yaw_time_ms) &&
     744       84010 :         yaw_time_ms != yawMeasTime_ms) {
     745             :         // GPS modules are rather too optimistic about their
     746             :         // accuracy. Set to min of 5 degrees here to prevent
     747             :         // the user constantly receiving warnings about high
     748             :         // normalised yaw innovations
     749       10752 :         const ftype min_yaw_accuracy_deg = 5.0f;
     750       10752 :         yaw_accuracy_deg = MAX(yaw_accuracy_deg, min_yaw_accuracy_deg);
     751             : #if EK3_FEATURE_MOVING_BASELINE
     752       10752 :         writeEulerYawAngle(radians(yaw_deg), radians(yaw_accuracy_deg), yaw_time_ms, 2, gps.get_mb_yaw_offset(selected_gps));
     753             : #else
     754             :         writeEulerYawAngle(radians(yaw_deg), radians(yaw_accuracy_deg), yaw_time_ms, 2);
     755             : #endif
     756             :     }
     757    19666871 : }
     758             : 
     759             : // read the delta angle and corresponding time interval from the IMU
     760             : // return false if data is not available
     761    79223583 : bool NavEKF3_core::readDeltaAngle(uint8_t ins_index, Vector3F &dAng, ftype &dAngDT) {
     762    79223583 :     const auto &ins = dal.ins();
     763             : 
     764    79223583 :     if (ins_index < ins.get_gyro_count()) {
     765    79223583 :         Vector3f dAngF;
     766             :         float dAngDTF;
     767    79223583 :         ins.get_delta_angle(ins_index, dAngF, dAngDTF);
     768    79223583 :         dAng = dAngF.toftype();
     769    79223583 :         dAngDT = dAngDTF;
     770             :         return true;
     771             :     }
     772             :     return false;
     773             : }
     774             : 
     775             : 
     776             : /********************************************************
     777             : *                  Height Measurements                  *
     778             : ********************************************************/
     779             : 
     780             : // check for new pressure altitude measurement data and update stored measurement if available
     781    19666871 : void NavEKF3_core::readBaroData()
     782             : {
     783             :     // check to see if baro measurement has changed so we know if a new measurement has arrived
     784             :     // limit update rate to avoid overflowing the FIFO buffer
     785    19666871 :     const auto &baro = dal.baro();
     786    19666871 :     if (baro.get_last_update(selected_baro) - lastBaroReceived_ms > frontend->sensorIntervalMin_ms) {
     787             : 
     788     2845431 :         baroDataNew.hgt = baro.get_altitude(selected_baro);
     789             : 
     790             :         // time stamp used to check for new measurement
     791     2845431 :         lastBaroReceived_ms = baro.get_last_update(selected_baro);
     792             : 
     793             :         // estimate of time height measurement was taken, allowing for delays
     794     2845431 :         baroDataNew.time_ms = lastBaroReceived_ms - frontend->_hgtDelay_ms;
     795             : 
     796             :         // Correct for the average intersampling delay due to the filter updaterate
     797     2845431 :         baroDataNew.time_ms -= localFilterTimeStep_ms/2;
     798             : 
     799             :         // Prevent time delay exceeding age of oldest IMU data in the buffer
     800     2845431 :         baroDataNew.time_ms = MAX(baroDataNew.time_ms,imuDataDelayed.time_ms);
     801             : 
     802             :         // save baro measurement to buffer to be fused later
     803     2845431 :         storedBaro.push(baroDataNew);
     804             :     }
     805    19666871 : }
     806             : 
     807             : // calculate filtered offset between baro height measurement and EKF height estimate
     808             : // offset should be subtracted from baro measurement to match filter estimate
     809             : // offset is used to enable reversion to baro from alternate height data source
     810        9847 : void NavEKF3_core::calcFiltBaroOffset()
     811             : {
     812             :     // Apply a first order LPF with spike protection
     813        9847 :     baroHgtOffset += 0.1f * constrain_ftype(baroDataDelayed.hgt + stateStruct.position.z - baroHgtOffset, -5.0f, 5.0f);
     814        9847 : }
     815             : 
     816             : // correct the height of the EKF origin to be consistent with GPS Data using a Bayes filter.
     817       30882 : void NavEKF3_core::correctEkfOriginHeight()
     818             : {
     819             :     // Estimate the WGS-84 height of the EKF's origin using a Bayes filter
     820             : 
     821             :     // calculate the variance of our a-priori estimate of the ekf origin height
     822       30882 :     ftype deltaTime = constrain_ftype(0.001f * (imuDataDelayed.time_ms - lastOriginHgtTime_ms), 0.0, 1.0);
     823       30882 :     if (activeHgtSource == AP_NavEKF_Source::SourceZ::BARO) {
     824             :         // Use the baro drift rate
     825             :         const ftype baroDriftRate = 0.05;
     826       30882 :         ekfOriginHgtVar += sq(baroDriftRate * deltaTime);
     827           0 :     } else if (activeHgtSource == AP_NavEKF_Source::SourceZ::RANGEFINDER) {
     828             :         // use the worse case expected terrain gradient and vehicle horizontal speed
     829             :         const ftype maxTerrGrad = 0.25;
     830           0 :         ekfOriginHgtVar += sq(maxTerrGrad * stateStruct.velocity.xy().length() * deltaTime);
     831             :     } else {
     832             :         // by definition our height source is absolute so cannot run this filter
     833             :         return;
     834             :     }
     835       30882 :     lastOriginHgtTime_ms = imuDataDelayed.time_ms;
     836             : 
     837             :     // calculate the observation variance assuming EKF error relative to datum is independent of GPS observation error
     838             :     // when not using GPS as height source
     839       30882 :     ftype originHgtObsVar = sq(gpsHgtAccuracy) + P[9][9];
     840             : 
     841             :     // calculate the correction gain
     842       30882 :     ftype gain = ekfOriginHgtVar / (ekfOriginHgtVar + originHgtObsVar);
     843             : 
     844             :     // calculate the innovation
     845       30882 :     ftype innovation = - stateStruct.position.z - gpsDataDelayed.hgt;
     846             : 
     847             :     // check the innovation variance ratio
     848       30882 :     ftype ratio = sq(innovation) / (ekfOriginHgtVar + originHgtObsVar);
     849             : 
     850             :     // correct the EKF origin and variance estimate if the innovation is less than 5-sigma
     851       30882 :     if (ratio < 25.0f && gpsAccuracyGood) {
     852       30882 :         ekfGpsRefHgt -= (double)(gain * innovation);
     853       30882 :         ekfOriginHgtVar -= MAX(gain * ekfOriginHgtVar , 0.0f);
     854             :     }
     855             : }
     856             : 
     857             : /********************************************************
     858             : *                Air Speed Measurements                 *
     859             : ********************************************************/
     860             : 
     861             : // check for new airspeed data and update stored measurements if available
     862    18761238 : void NavEKF3_core::readAirSpdData()
     863             : {
     864    18761238 :     const float EAS2TAS = dal.get_EAS2TAS();
     865             :     // if airspeed reading is valid and is set by the user to be used and has been updated then
     866             :     // we take a new reading, convert from EAS to TAS and set the flag letting other functions
     867             :     // know a new measurement is available
     868             : 
     869    18761238 :     if (useAirspeed()) {
     870     5612455 :         const auto *airspeed = dal.airspeed();
     871     5612455 :         if (airspeed &&
     872     5612455 :             (airspeed->last_update_ms(selected_airspeed) - timeTasReceived_ms) > frontend->sensorIntervalMin_ms) {
     873      953700 :             tasDataNew.allowFusion = airspeed->healthy(selected_airspeed) && airspeed->use(selected_airspeed);
     874      953700 :             if (tasDataNew.allowFusion) {
     875      953700 :                 tasDataNew.tas = airspeed->get_airspeed(selected_airspeed) * EAS2TAS;
     876      953700 :                 timeTasReceived_ms = airspeed->last_update_ms(selected_airspeed);
     877      953700 :                 tasDataNew.time_ms = timeTasReceived_ms - frontend->tasDelay_ms;
     878      953700 :                 tasDataNew.tasVariance = sq(MAX(frontend->_easNoise * EAS2TAS, 0.5f));
     879             :                 // Correct for the average intersampling delay due to the filter update rate
     880      953700 :                 tasDataNew.time_ms -= localFilterTimeStep_ms/2;
     881             :                 // Save data into the buffer to be fused when the fusion time horizon catches up with it
     882      953700 :                 storedTAS.push(tasDataNew);
     883             :             }
     884             :         }
     885             :         // Check the buffer for measurements that have been overtaken by the fusion time horizon and need to be fused
     886     5612455 :         tasDataToFuse = storedTAS.recall(tasDataDelayed,imuDataDelayed.time_ms);
     887    13148783 :     } else if (assume_zero_sideslip()) {
     888             :         // synthetic airspeed is only valid for 'fly forward' vehicles that
     889             :         // do not sideslip; fusing it on a multicopter corrupts the attitude
     890             :         // and velocity states when dead reckoning (see issue #33451)
     891      154797 :         if (is_positive(defaultAirSpeed)) {
     892             :             // this is the preferred method with the autopilot providing a model based airspeed estimate
     893           0 :             if (imuDataDelayed.time_ms - prevTasStep_ms > 200 ) {
     894           0 :                 tasDataDelayed.tas = defaultAirSpeed * EAS2TAS;
     895           0 :                 tasDataDelayed.tasVariance = MAX(defaultAirSpeedVariance, sq(MAX(frontend->_easNoise, 0.5f)));
     896           0 :                 tasDataToFuse = true;
     897           0 :                 tasDataDelayed.allowFusion = true;
     898           0 :                 tasDataDelayed.time_ms = imuDataDelayed.time_ms;
     899             :             } else {
     900           0 :                 tasDataToFuse = false;
     901           0 :                 tasDataDelayed.allowFusion = false;
     902             :             }
     903      154797 :         } else if (lastAspdEstIsValid && !windStateIsObservable) {
     904             :             // this uses the last airspeed estimated before dead reckoning started and
     905             :             // wind states became unobservable
     906        6760 :             if (lastAspdEstIsValid && imuDataDelayed.time_ms - prevTasStep_ms > 200) {
     907        6760 :                 tasDataDelayed.tas = lastAirspeedEstimate;
     908             :                 // this airspeed estimate has a lot of uncertainty
     909        6760 :                 tasDataDelayed.tasVariance = sq(MAX(MAX(frontend->_easNoise, 0.5f), 0.5f * lastAirspeedEstimate));
     910        6760 :                 tasDataToFuse = true;
     911        6760 :                 tasDataDelayed.allowFusion = true;
     912        6760 :                 tasDataDelayed.time_ms = imuDataDelayed.time_ms;
     913             :             } else {
     914           0 :                 tasDataToFuse = false;
     915           0 :                 tasDataDelayed.allowFusion = false;
     916             :             }
     917             :         }
     918             :     } else {
     919    12993986 :         tasDataToFuse = false;
     920    12993986 :         tasDataDelayed.allowFusion = false;
     921             :     }
     922    18761238 : }
     923             : 
     924             : #if EK3_FEATURE_BEACON_FUSION
     925             : /********************************************************
     926             : *              Range Beacon Measurements                *
     927             : ********************************************************/
     928             : 
     929             : // check for new range beacon data and push to data buffer if available
     930    19965274 : void NavEKF3_core::readRngBcnData()
     931             : {
     932             :     // check that arrays are large enough
     933             :     static_assert(ARRAY_SIZE(rngBcn.lastTime_ms) >= AP_BEACON_MAX_BEACONS, "lastTimeRngBcn_ms should have at least AP_BEACON_MAX_BEACONS elements");
     934             : 
     935             :     // get the location of the beacon data
     936    19965274 :     const AP_DAL_Beacon *beacon = dal.beacon();
     937             : 
     938             :     // exit immediately if no beacon object
     939    19965274 :     if (beacon == nullptr) {
     940    19897480 :         return;
     941             :     }
     942             : 
     943             :     // get the number of beacons in use
     944       67794 :     rngBcn.N = MIN(beacon->count(), ARRAY_SIZE(rngBcn.lastTime_ms));
     945             : 
     946             :     // search through all the beacons for new data and if we find it stop searching and push the data into the observation buffer
     947             :     bool newDataPushed = false;
     948             :     uint8_t numRngBcnsChecked = 0;
     949             :     // start the search one index up from where we left it last time
     950       67794 :     uint8_t index = rngBcn.lastChecked;
     951      176397 :     while (!newDataPushed && (numRngBcnsChecked < rngBcn.N)) {
     952             :         // track the number of beacons checked
     953      108603 :         numRngBcnsChecked++;
     954             : 
     955             :         // move to next beacon, wrap index if necessary
     956      108603 :         index++;
     957      108603 :         if (index >= rngBcn.N) {
     958             :             index = 0;
     959             :         }
     960             : 
     961             :         // check that the beacon is healthy and has new data
     962      108603 :         if (beacon->beacon_healthy(index) && beacon->beacon_last_update_ms(index) != rngBcn.lastTime_ms[index]) {
     963       54191 :             rng_bcn_elements rngBcnDataNew = {};
     964             : 
     965             :             // set the timestamp, correcting for measurement delay and average intersampling delay due to the filter update rate
     966       54191 :             rngBcn.lastTime_ms[index] = beacon->beacon_last_update_ms(index);
     967       54191 :             rngBcnDataNew.time_ms = rngBcn.lastTime_ms[index] - frontend->_rngBcnDelay_ms - localFilterTimeStep_ms/2;
     968             : 
     969             :             // set the range noise
     970             :             // TODO the range library should provide the noise/accuracy estimate for each beacon
     971       54191 :             rngBcnDataNew.rngErr = frontend->_rngBcnNoise.get();
     972             : 
     973             :             // set the range measurement
     974       54191 :             rngBcnDataNew.rng = beacon->beacon_distance(index);
     975             : 
     976             :             // set the beacon position
     977       54191 :             rngBcnDataNew.beacon_posNED = beacon->beacon_position(index).toftype();
     978             : 
     979             :             // identify the beacon identifier
     980       54191 :             rngBcnDataNew.beacon_ID = index;
     981             : 
     982             :             // indicate we have new data to push to the buffer
     983             :             newDataPushed = true;
     984             : 
     985             :             // update the last checked index
     986       54191 :             rngBcn.lastChecked = index;
     987             : 
     988             :             // Save data into the buffer to be fused when the fusion time horizon catches up with it
     989       54191 :             rngBcn.storedRange.push(rngBcnDataNew);
     990             :         }
     991             :     }
     992             : 
     993             :     // Check if the beacon system has returned a 3D fix
     994       67794 :     Vector3f bp;
     995             :     float bperr;
     996       67794 :     if (beacon->get_vehicle_position_ned(bp, bperr)) {
     997       67401 :         rngBcn.last3DmeasTime_ms = imuSampleTime_ms;
     998             :     }
     999       67794 :     rngBcn.vehiclePosNED = bp.toftype();
    1000       67794 :     rngBcn.vehiclePosErr = bperr;
    1001             : 
    1002             :     // Check if the range beacon data can be used to align the vehicle position
    1003       67794 :     if ((imuSampleTime_ms - rngBcn.last3DmeasTime_ms < 250) && (rngBcn.vehiclePosErr < 1.0f) && rngBcn.alignmentCompleted) {
    1004             :         // check for consistency between the position reported by the beacon and the position from the 3-State alignment filter
    1005       64323 :         const ftype posDiffSq = sq(rngBcn.receiverPos.x - rngBcn.vehiclePosNED.x) + sq(rngBcn.receiverPos.y - rngBcn.vehiclePosNED.y);
    1006       64323 :         const ftype posDiffVar = sq(rngBcn.vehiclePosErr) + rngBcn.receiverPosCov[0][0] + rngBcn.receiverPosCov[1][1];
    1007       64323 :         if (posDiffSq < 9.0f * posDiffVar) {
    1008       40404 :             rngBcn.goodToAlign = true;
    1009             :             // Set the EKF origin and magnetic field declination if not previously set
    1010       40404 :             if (!validOrigin && (PV_AidingMode != AID_ABSOLUTE)) {
    1011             :                 // get origin from beacon system
    1012           6 :                 Location origin_loc;
    1013           6 :                 if (beacon->get_origin(origin_loc)) {
    1014           6 :                     setOriginLLH(origin_loc);
    1015             : 
    1016             :                     // set the NE earth magnetic field states using the published declination
    1017             :                     // and set the corresponding variances and covariances
    1018           6 :                     alignMagStateDeclination();
    1019             : 
    1020             :                     // Set the uncertainty of the origin height
    1021           6 :                     ekfOriginHgtVar = sq(rngBcn.vehiclePosErr);
    1022             :                 }
    1023             :             }
    1024             :         } else {
    1025       23919 :             rngBcn.goodToAlign = false;
    1026             :         }
    1027             :     } else {
    1028        3471 :         rngBcn.goodToAlign = false;
    1029             :     }
    1030             : 
    1031             :     // Check the buffer for measurements that have been overtaken by the fusion time horizon and need to be fused
    1032       67794 :     rngBcn.dataToFuse = rngBcn.storedRange.recall(rngBcn.dataDelayed, imuDataDelayed.time_ms);
    1033             : 
    1034             :     // Correct the range beacon earth frame origin for estimated offset relative to the EKF earth frame origin
    1035       67794 :     if (rngBcn.dataToFuse) {
    1036       48202 :         rngBcn.dataDelayed.beacon_posNED.x += rngBcn.posOffsetNED.x;
    1037       48202 :         rngBcn.dataDelayed.beacon_posNED.y += rngBcn.posOffsetNED.y;
    1038             :     }
    1039             : 
    1040             : }
    1041             : #endif  // EK3_FEATURE_BEACON_FUSION
    1042             : 
    1043             : /********************************************************
    1044             : *              Independant yaw sensor measurements      *
    1045             : ********************************************************/
    1046             : 
    1047       10752 : void NavEKF3_core::writeEulerYawAngle(float yawAngle, float yawAngleErr, uint32_t timeStamp_ms, uint8_t type, const Vector3f &antOffset)
    1048             : {
    1049             :     // limit update rate to maximum allowed by sensor buffers and fusion process
    1050             :     // don't try to write to buffer until the filter has been initialised
    1051       10752 :     if (((timeStamp_ms - yawMeasTime_ms) < frontend->sensorIntervalMin_ms) || !statesInitialised) {
    1052             :         return;
    1053             :     }
    1054             : 
    1055        5098 :     yawAngDataNew.yawAng = yawAngle;
    1056        5098 :     yawAngDataNew.yawAngErr = yawAngleErr;
    1057        5098 :     if (type == 2) {
    1058        5098 :         yawAngDataNew.order = rotationOrder::TAIT_BRYAN_321;
    1059           0 :     } else if (type == 1) {
    1060           0 :         yawAngDataNew.order = rotationOrder::TAIT_BRYAN_312;
    1061             :     } else {
    1062             :         return;
    1063             :     }
    1064             :     // Prevent time delay exceeding age of oldest IMU data in the buffer. Without
    1065             :     // this a configured delay larger than the fusion time horizon leaves every
    1066             :     // measurement outside the recall window and the yaw source silently stops
    1067             :     // fusing. The raw timestamp is still used for new-measurement detection and
    1068             :     // rate limiting via yawMeasTime_ms
    1069        5098 :     yawAngDataNew.time_ms = MAX(timeStamp_ms, imuDataDelayed.time_ms);
    1070             : #if EK3_FEATURE_MOVING_BASELINE
    1071        5098 :     yawAngDataNew.antOffset = antOffset.toftype();
    1072             : #endif
    1073             : 
    1074        5098 :     storedYawAng.push(yawAngDataNew);
    1075             : 
    1076        5098 :     yawMeasTime_ms = timeStamp_ms;
    1077             : }
    1078             : 
    1079             : // Writes the default equivalent airspeed and 1-sigma uncertainty in m/s to be used in forward flight if a measured airspeed is required and not available.
    1080           0 : void NavEKF3_core::writeDefaultAirSpeed(float airspeed, float uncertainty)
    1081             : {
    1082           0 :     defaultAirSpeed = airspeed;
    1083           0 :     defaultAirSpeedVariance = sq(uncertainty);
    1084           0 : }
    1085             : 
    1086             : /********************************************************
    1087             : *            External Navigation Measurements           *
    1088             : ********************************************************/
    1089             : 
    1090       19896 : void NavEKF3_core::writeExtNavData(const Vector3f &pos, const Quaternion &quat, float posErr, float angErr, uint32_t timeStamp_ms, uint16_t delay_ms, uint32_t resetTime_ms)
    1091             : {
    1092             : #if EK3_FEATURE_EXTERNAL_NAV
    1093             :     // protect against NaN
    1094       19896 :     if (pos.is_nan() || isnan(posErr)) {
    1095         248 :         return;
    1096             :     }
    1097             : 
    1098             :     // limit update rate to maximum allowed by sensor buffers and fusion process
    1099             :     // don't try to write to buffer until the filter has been initialised
    1100       19896 :     if (((timeStamp_ms - extNavMeasTime_ms) < frontend->extNavIntervalMin_ms) || !statesInitialised) {
    1101             :         return;
    1102             :     } else {
    1103       19648 :         extNavMeasTime_ms = timeStamp_ms;
    1104             :     }
    1105             : 
    1106       19648 :     ext_nav_elements extNavDataNew {};
    1107             : 
    1108       19648 :     if (resetTime_ms != extNavLastPosResetTime_ms) {
    1109           0 :         extNavDataNew.posReset = true;
    1110           0 :         extNavLastPosResetTime_ms = resetTime_ms;
    1111             :     } else {
    1112       19648 :         extNavDataNew.posReset = false;
    1113             :     }
    1114             : 
    1115       19648 :     extNavDataNew.pos = pos.toftype();
    1116       19648 :     extNavDataNew.posErr = posErr;
    1117             : 
    1118             :     // calculate timestamp
    1119       19648 :     timeStamp_ms = timeStamp_ms - delay_ms;
    1120             :     // Correct for the average intersampling delay due to the filter update rate
    1121       19648 :     timeStamp_ms -= localFilterTimeStep_ms/2;
    1122             :     // Prevent time delay exceeding age of oldest IMU data in the buffer
    1123       19648 :     timeStamp_ms = MAX(timeStamp_ms, imuDataDelayed.time_ms);
    1124       19648 :     extNavDataNew.time_ms = timeStamp_ms;
    1125             : 
    1126             :     // store position data to buffer
    1127       19648 :     storedExtNav.push(extNavDataNew);
    1128             : 
    1129             :     // protect against attitude or angle being NaN
    1130       19648 :     if (!quat.is_nan() && !isnan(angErr)) {
    1131             :         // extract yaw from the attitude
    1132             :         ftype roll_rad, pitch_rad, yaw_rad;
    1133       19648 :         quat.to_euler(roll_rad, pitch_rad, yaw_rad);
    1134       19648 :         yaw_elements extNavYawAngDataNew;
    1135       19648 :         extNavYawAngDataNew.yawAng = yaw_rad;
    1136       19648 :         extNavYawAngDataNew.yawAngErr = MAX(angErr, radians(5.0f)); // ensure yaw accuracy is no better than 5 degrees (some callers may send zero)
    1137       19648 :         extNavYawAngDataNew.order = rotationOrder::TAIT_BRYAN_321; // Euler rotation order is 321 (ZYX)
    1138       19648 :         extNavYawAngDataNew.time_ms = timeStamp_ms;
    1139       19648 :         storedExtNavYawAng.push(extNavYawAngDataNew);
    1140             :     }
    1141             : #endif // EK3_FEATURE_EXTERNAL_NAV
    1142             : }
    1143             : 
    1144        8468 : void NavEKF3_core::writeExtNavVelData(const Vector3f &vel, float err, uint32_t timeStamp_ms, uint16_t delay_ms)
    1145             : {
    1146             : #if EK3_FEATURE_EXTERNAL_NAV
    1147             :     // sanity check for NaNs
    1148        8468 :     if (vel.is_nan() || isnan(err)) {
    1149           0 :         return;
    1150             :     }
    1151             : 
    1152        8468 :     if ((timeStamp_ms - extNavVelMeasTime_ms) < frontend->extNavIntervalMin_ms) {
    1153             :         return;
    1154             :     }
    1155             : 
    1156        8468 :     extNavVelMeasTime_ms = timeStamp_ms;
    1157        8468 :     useExtNavVel = true;
    1158             :     // calculate timestamp
    1159        8468 :     timeStamp_ms = timeStamp_ms - delay_ms;
    1160             :     // Correct for the average intersampling delay due to the filter updaterate
    1161        8468 :     timeStamp_ms -= localFilterTimeStep_ms/2;
    1162             :     // Prevent time delay exceeding age of oldest IMU data in the buffer
    1163        8468 :     timeStamp_ms = MAX(timeStamp_ms,imuDataDelayed.time_ms);
    1164             : 
    1165        8468 :     ext_nav_vel_elements extNavVelNew;
    1166        8468 :     extNavVelNew.time_ms = timeStamp_ms;
    1167        8468 :     extNavVelNew.vel = vel.toftype();
    1168        8468 :     extNavVelNew.err = err;
    1169        8468 :     extNavVelNew.corrected = false;
    1170             : 
    1171        8468 :     storedExtNavVel.push(extNavVelNew);
    1172             : #endif // EK3_FEATURE_EXTERNAL_NAV
    1173             : }
    1174             : 
    1175             : /*
    1176             :  * Write terrain altitude (derived from SRTM) in meters above the origin
    1177             :  * only used by optical flow when out of rangefinder range
    1178             :  */
    1179             : #if EK3_FEATURE_OPTFLOW_SRTM
    1180           0 : void NavEKF3_core::writeTerrainData(float alt_m)
    1181             : {
    1182           0 :     terrain_srtm_alt = alt_m;
    1183           0 :     terrain_srtm_alt_ms = imuSampleTime_ms;
    1184           0 : }
    1185             : #endif
    1186             : 
    1187             : /*
    1188             :   update the GPS selection
    1189             :  */
    1190    79784903 : void NavEKF3_core::update_gps_selection(void)
    1191             : {
    1192    79784903 :     const auto &gps = dal.gps();
    1193             : 
    1194             :     // in normal operation use the primary GPS
    1195    79784903 :     selected_gps = gps.primary_sensor();
    1196    79784903 :     preferred_gps = selected_gps;
    1197             : 
    1198    79784903 :     if (frontend->_affinity & EKF_AFFINITY_GPS) {
    1199      229862 :         if (core_index < gps.num_sensors() ) {
    1200             :             // always prefer our core_index, unless we don't have that
    1201             :             // many GPS sensors available
    1202      213063 :             preferred_gps = core_index;
    1203             :         }
    1204      229862 :         if (gps.status(preferred_gps) >= AP_GPS_FixType::FIX_3D) {
    1205             :             // select our preferred_gps if it has a 3D fix, otherwise
    1206             :             // use the primary GPS
    1207      213608 :             selected_gps = preferred_gps;
    1208             :         }
    1209             :     }
    1210    79784903 : }
    1211             : 
    1212             : /*
    1213             :   update the mag selection
    1214             :  */
    1215    79784903 : void NavEKF3_core::update_mag_selection(void)
    1216             : {
    1217    79784903 :     const auto &compass = dal.compass();
    1218             : 
    1219    79784903 :     if (frontend->_affinity & EKF_AFFINITY_MAG) {
    1220     1500316 :         if (core_index < compass.get_count() &&
    1221      750158 :             compass.healthy(core_index) &&
    1222      750158 :             compass.use_for_yaw(core_index)) {
    1223             :             // use core_index compass if it is healthy
    1224      750158 :             magSelectIndex = core_index;
    1225             :         }
    1226             :     } else {
    1227             :         // if we are not armed, use the primary compass (if it is healthy)
    1228    79034745 :         const uint8_t primary_compass = compass.get_first_usable();
    1229    79034745 :         if (!dal.get_armed() && (magSelectIndex != primary_compass)) {
    1230          15 :             tryChangeCompass(primary_compass);
    1231             :         }
    1232             :     }
    1233    79784903 : }
    1234             : 
    1235             : /*
    1236             :   update the baro selection
    1237             :  */
    1238    79784903 : void NavEKF3_core::update_baro_selection(void)
    1239             : {
    1240    79784903 :     auto &baro = dal.baro();
    1241             : 
    1242             :     // in normal operation use the primary baro
    1243    79784903 :     selected_baro = baro.get_primary();
    1244             : 
    1245    79784903 :     if (frontend->_affinity & EKF_AFFINITY_BARO) {
    1246       27764 :         if (core_index < baro.num_instances() &&
    1247       27764 :             baro.healthy(core_index)) {
    1248             :             // use core_index baro if it is healthy
    1249       27764 :             selected_baro = core_index;
    1250             :         }
    1251             :     }
    1252    79784903 : }
    1253             : 
    1254             : /*
    1255             :   update the airspeed selection
    1256             :  */
    1257    79784903 : void NavEKF3_core::update_airspeed_selection(void)
    1258             : {
    1259    79784903 :     const auto *arsp = dal.airspeed();
    1260    79784903 :     if (arsp == nullptr) {
    1261             :         return;
    1262             :     }
    1263             : 
    1264             :     // in normal operation use the primary airspeed sensor
    1265    16177488 :     selected_airspeed = arsp->get_primary();
    1266             : 
    1267    16177488 :     if (frontend->_affinity & EKF_AFFINITY_ARSP) {
    1268       55470 :         if (core_index < arsp->get_num_sensors() &&
    1269       27764 :             arsp->healthy(core_index) &&
    1270       27672 :             arsp->use(core_index)) {
    1271             :             // use core_index airspeed if it is healthy
    1272       27672 :             selected_airspeed = core_index;
    1273             :         }
    1274             :     }
    1275             : }
    1276             : 
    1277             : /*
    1278             :   update sensor selections
    1279             :  */
    1280    79784903 : void NavEKF3_core::update_sensor_selection(void)
    1281             : {
    1282    79784903 :     update_gps_selection();
    1283    79784903 :     update_mag_selection();
    1284    79784903 :     update_baro_selection();
    1285    79784903 :     update_airspeed_selection();
    1286    79784903 : }
    1287             : 
    1288             : /*
    1289             :   update timing statistics structure
    1290             :  */
    1291    20078569 : void NavEKF3_core::updateTimingStatistics(void)
    1292             : {
    1293    20078569 :     if (timing.count == 0) {
    1294       59103 :         timing.dtIMUavg_max = dtIMUavg;
    1295       59103 :         timing.dtIMUavg_min = dtIMUavg;
    1296       59103 :         timing.dtEKFavg_max = dtEkfAvg;
    1297       59103 :         timing.dtEKFavg_min = dtEkfAvg;
    1298       59103 :         timing.delAngDT_max = imuDataDelayed.delAngDT;
    1299       59103 :         timing.delAngDT_min = imuDataDelayed.delAngDT;
    1300       59103 :         timing.delVelDT_max = imuDataDelayed.delVelDT;
    1301       59103 :         timing.delVelDT_min = imuDataDelayed.delVelDT;
    1302             :     } else {
    1303    20019466 :         timing.dtIMUavg_max = MAX(timing.dtIMUavg_max, dtIMUavg);
    1304    20019466 :         timing.dtIMUavg_min = MIN(timing.dtIMUavg_min, dtIMUavg);
    1305    20019466 :         timing.dtEKFavg_max = MAX(timing.dtEKFavg_max, dtEkfAvg);
    1306    20019466 :         timing.dtEKFavg_min = MIN(timing.dtEKFavg_min, dtEkfAvg);
    1307    20019466 :         timing.delAngDT_max = MAX(timing.delAngDT_max, imuDataDelayed.delAngDT);
    1308    20019466 :         timing.delAngDT_min = MIN(timing.delAngDT_min, imuDataDelayed.delAngDT);
    1309    20019466 :         timing.delVelDT_max = MAX(timing.delVelDT_max, imuDataDelayed.delVelDT);
    1310    20019466 :         timing.delVelDT_min = MIN(timing.delVelDT_min, imuDataDelayed.delVelDT);
    1311             :     }
    1312    20078569 :     timing.count++;
    1313    20078569 : }
    1314             : 
    1315             : /*
    1316             :   update estimates of inactive bias states. This keeps inactive IMUs
    1317             :   as hot-spares so we can switch to them without causing a jump in the
    1318             :   error
    1319             :  */
    1320    79223583 : void NavEKF3_core::learnInactiveBiases(void)
    1321             : {
    1322             : #if INS_MAX_INSTANCES == 1
    1323             :     inactiveBias[0].gyro_bias = stateStruct.gyro_bias;
    1324             :     inactiveBias[0].accel_bias = stateStruct.accel_bias;
    1325             : #else
    1326    79223583 :     const auto &ins = dal.ins();
    1327             : 
    1328             :     // learn gyro biases
    1329   316894332 :     for (uint8_t i=0; i<INS_MAX_INSTANCES; i++) {
    1330   237670749 :         if (!ins.use_gyro(i)) {
    1331             :             // can't use this gyro
    1332    79032269 :             continue;
    1333             :         }
    1334   158638480 :         if (gyro_index_active == i) {
    1335             :             // use current estimates from main filter of gyro bias
    1336    79223583 :             inactiveBias[i].gyro_bias = stateStruct.gyro_bias;
    1337             :         } else {
    1338             :             // get filtered gyro and use the difference between the
    1339             :             // corrected gyro on the active IMU and the inactive IMU
    1340             :             // to move the inactive bias towards the right value
    1341    79414897 :             Vector3F filtered_gyro_active = ins.get_gyro(gyro_index_active).toftype() - (stateStruct.gyro_bias/dtEkfAvg);
    1342    79414897 :             Vector3F filtered_gyro_inactive = ins.get_gyro(i).toftype() - (inactiveBias[i].gyro_bias/dtEkfAvg);
    1343    79414897 :             Vector3F error = filtered_gyro_active - filtered_gyro_inactive;
    1344             : 
    1345             :             // prevent a single large error from contaminating bias estimate
    1346             :             const ftype bias_limit = radians(5);
    1347    79414897 :             error.x = constrain_ftype(error.x, -bias_limit, bias_limit);
    1348    79414897 :             error.y = constrain_ftype(error.y, -bias_limit, bias_limit);
    1349    79414897 :             error.z = constrain_ftype(error.z, -bias_limit, bias_limit);
    1350             : 
    1351             :             // slowly bring the inactive gyro in line with the active gyro. This corrects a 5 deg/sec
    1352             :             // gyro bias error in around 1 minute
    1353    79414897 :             inactiveBias[i].gyro_bias -= error * (1.0e-4f * dtEkfAvg);
    1354             :         }
    1355             :     }
    1356             : 
    1357             :     // learn accel biases
    1358   316894332 :     for (uint8_t i=0; i<INS_MAX_INSTANCES; i++) {
    1359   237670749 :         if (!ins.use_accel(i)) {
    1360             :             // can't use this accel
    1361    79032269 :             continue;
    1362             :         }
    1363   158638480 :         if (accel_index_active == i) {
    1364             :             // use current estimates from main filter of accel bias
    1365    79223583 :             inactiveBias[i].accel_bias = stateStruct.accel_bias;
    1366             :         } else {
    1367             :             // get filtered accel and use the difference between the
    1368             :             // corrected accel on the active IMU and the inactive IMU
    1369             :             // to move the inactive bias towards the right value
    1370    79414897 :             Vector3F filtered_accel_active = ins.get_accel(accel_index_active).toftype() - (stateStruct.accel_bias/dtEkfAvg);
    1371    79414897 :             Vector3F filtered_accel_inactive = ins.get_accel(i).toftype() - (inactiveBias[i].accel_bias/dtEkfAvg);
    1372    79414897 :             Vector3F error = filtered_accel_active - filtered_accel_inactive;
    1373             : 
    1374             :             // prevent a single large error from contaminating bias estimate
    1375             :             const ftype bias_limit = 1.0; // m/s/s
    1376    79414897 :             error.x = constrain_ftype(error.x, -bias_limit, bias_limit);
    1377    79414897 :             error.y = constrain_ftype(error.y, -bias_limit, bias_limit);
    1378    79414897 :             error.z = constrain_ftype(error.z, -bias_limit, bias_limit);
    1379             : 
    1380             :             // slowly bring the inactive accel in line with the active
    1381             :             // accel. This corrects a 0.5 m/s/s accel bias error in
    1382             :             // around 1 minute
    1383    79414897 :             inactiveBias[i].accel_bias -= error * (1.0e-4f * dtEkfAvg);
    1384             :         }
    1385             :     }
    1386             : #endif
    1387    79223583 : }
    1388             : 
    1389             : /*
    1390             :   return declination in radians
    1391             : */
    1392     2758838 : ftype NavEKF3_core::MagDeclination(void) const
    1393             : {
    1394             :     // if we are using the WMM tables then use the table declination
    1395             :     // to ensure consistency with the table mag field. Otherwise use
    1396             :     // the declination from the compass library
    1397     2758838 :     if (have_table_earth_field && frontend->_mag_ef_limit > 0) {
    1398     2510323 :         return table_declination;
    1399             :     }
    1400      248515 :     if (!use_compass()) {
    1401             :         return 0;
    1402             :     }
    1403      248494 :     return dal.compass().get_declination();
    1404             : }
    1405             : 
    1406             : /*
    1407             :   Update the on ground and not moving check.
    1408             :   Should be called once per IMU update.
    1409             :   Used for yaw fusion strategy selection, zero velocity fusion gating,
    1410             :   and accel bias learning inhibition during ground movement.
    1411             : */
    1412    79223583 : void NavEKF3_core::updateMovementCheck(void)
    1413             : {
    1414    79223583 :     if (!onGround)
    1415             :     {
    1416    54406915 :         onGroundNotMoving = false;
    1417    54406915 :         return;
    1418             :     }
    1419             : 
    1420             :     const ftype gyro_limit = radians(3.0f);
    1421             :     const ftype gyro_diff_limit = 0.2f;
    1422             :     const ftype accel_limit = 1.0f;
    1423             :     const ftype accel_diff_limit = 5.0f;
    1424             :     const ftype hysteresis_ratio = 0.7f;
    1425    24816668 :     const ftype dtEkfAvgInv = 1.0f / dtEkfAvg;
    1426             : 
    1427             :     // get latest bias corrected gyro and accelerometer data
    1428    24816668 :     const auto &ins = dal.ins();
    1429    24816668 :     Vector3F gyro = ins.get_gyro(gyro_index_active).toftype() - stateStruct.gyro_bias * dtEkfAvgInv;
    1430    24816668 :     Vector3F accel = ins.get_accel(accel_index_active).toftype() - stateStruct.accel_bias * dtEkfAvgInv;
    1431             : 
    1432    24816668 :     if (!prevOnGround) {
    1433           0 :         gyro_prev = gyro;
    1434           0 :         accel_prev = accel;
    1435           0 :         onGroundNotMoving = false;
    1436           0 :         gyro_diff = gyro_diff_limit;
    1437           0 :         accel_diff = accel_diff_limit;
    1438           0 :         return;
    1439             :     }
    1440             : 
    1441             :     // calculate a gyro rate of change metric
    1442    24816668 :     Vector3F temp = (gyro - gyro_prev) * dtEkfAvgInv;
    1443    24816668 :     gyro_prev = gyro;
    1444    24816668 :     gyro_diff = 0.99f * gyro_diff + 0.01f * temp.length();
    1445             : 
    1446             :     // calculate a acceleration rate of change metric
    1447    24816668 :     temp = (accel - accel_prev) * dtEkfAvgInv;
    1448    24816668 :     accel_prev = accel;
    1449    24816668 :     accel_diff = 0.99f * accel_diff + 0.01f * temp.length();
    1450             : 
    1451    24816668 :     const ftype gyro_length_ratio = gyro.length() / gyro_limit;
    1452    24816668 :     const ftype accel_length_ratio = (accel.length() - GRAVITY_MSS) / accel_limit;
    1453    24816668 :     const ftype gyro_diff_ratio = gyro_diff / gyro_diff_limit;
    1454    24816668 :     const ftype accel_diff_ratio = accel_diff / accel_diff_limit;
    1455             :     bool logStatusChange = false;
    1456    24816668 :     if (onGroundNotMoving) {
    1457    23135882 :         if (gyro_length_ratio > frontend->_ognmTestScaleFactor ||
    1458    23135667 :             fabsF(accel_length_ratio) > frontend->_ognmTestScaleFactor ||
    1459    46271382 :             gyro_diff_ratio > frontend->_ognmTestScaleFactor ||
    1460    23135499 :             accel_diff_ratio > frontend->_ognmTestScaleFactor)
    1461             :         {
    1462         383 :             onGroundNotMoving = false;
    1463             :             logStatusChange = true;
    1464             :         }
    1465     1680786 :     } else if (gyro_length_ratio < frontend->_ognmTestScaleFactor * hysteresis_ratio &&
    1466      208780 :             fabsF(accel_length_ratio) < frontend->_ognmTestScaleFactor * hysteresis_ratio &&
    1467     1783457 :             gyro_diff_ratio < frontend->_ognmTestScaleFactor * hysteresis_ratio &&
    1468       61536 :             accel_diff_ratio < frontend->_ognmTestScaleFactor * hysteresis_ratio)
    1469             :     {
    1470        5268 :         onGroundNotMoving = true;
    1471             :         logStatusChange = true;
    1472             :     }
    1473             : 
    1474    24811017 :     if (logStatusChange || imuSampleTime_ms - lastMoveCheckLogTime_ms > 200) {
    1475      422749 :         lastMoveCheckLogTime_ms = imuSampleTime_ms;
    1476             : #if HAL_LOGGING_ENABLED
    1477      422749 :         const struct log_XKFM pkt{
    1478             :             LOG_PACKET_HEADER_INIT(LOG_XKFM_MSG),
    1479      422749 :             time_us            : dal.micros64(),
    1480      422749 :             core               : core_index,
    1481      422749 :             ongroundnotmoving  : onGroundNotMoving,
    1482      422749 :             gyro_length_ratio  : float(gyro_length_ratio),
    1483      422749 :             accel_length_ratio : float(accel_length_ratio),
    1484      422749 :             gyro_diff_ratio    : float(gyro_diff_ratio),
    1485      422749 :             accel_diff_ratio   : float(accel_diff_ratio),
    1486      422749 :         };
    1487      422749 :         AP::logger().WriteBlock(&pkt, sizeof(pkt));
    1488             : #endif
    1489             :     }
    1490             : }
    1491             : 
    1492    20078569 : void NavEKF3_core::SampleDragData(const imu_elements &imu)
    1493             : {
    1494             : #if EK3_FEATURE_DRAG_FUSION
    1495             :     // Average and down sample to 5Hz
    1496    20078569 :     const ftype bcoef_x = frontend->_ballisticCoef_x.get();
    1497    20078569 :     const ftype bcoef_y = frontend->_ballisticCoef_y.get();
    1498    20078569 :     const ftype mcoef = frontend->_momentumDragCoef.get();
    1499    20078569 :     const bool using_bcoef_x = bcoef_x > 1.0f;
    1500    20078569 :     const bool using_bcoef_y = bcoef_y > 1.0f;
    1501             :     const bool using_mcoef = mcoef > 0.001f;
    1502    20078569 :     if (!using_bcoef_x && !using_bcoef_y && !using_mcoef) {
    1503             :         // nothing to do
    1504    19931860 :         dragFusionEnabled = false;
    1505    19931860 :         return;
    1506             :     }
    1507             : 
    1508      146709 :     dragFusionEnabled = true;
    1509             : 
    1510             :     // down-sample the drag specific force data by accumulating and calculating the mean when
    1511             :     // sufficient samples have been collected
    1512             : 
    1513      146709 :     dragSampleCount ++;
    1514             : 
    1515             :     // note acceleration is accumulated as a delta velocity
    1516      146709 :     dragDownSampled.accelXY.x += imu.delVel.x;
    1517      146709 :     dragDownSampled.accelXY.y += imu.delVel.y;
    1518      146709 :     dragDownSampled.time_ms += imu.time_ms;
    1519      146709 :     dragSampleTimeDelta += imu.delVelDT;
    1520             : 
    1521             :     // calculate and store means from accumulated values
    1522      146709 :     if (dragSampleTimeDelta > 0.2f - 0.5f * EKF_TARGET_DT) {
    1523             :         // note conversion from accumulated delta velocity to acceleration
    1524        9209 :         dragDownSampled.accelXY.x /= dragSampleTimeDelta;
    1525        9209 :         dragDownSampled.accelXY.y /= dragSampleTimeDelta;
    1526        9209 :         dragDownSampled.time_ms /= dragSampleCount;
    1527             : 
    1528             :         // write to buffer
    1529        9209 :         storedDrag.push(dragDownSampled);
    1530             : 
    1531             :         // reset accumulators
    1532        9209 :         dragSampleCount = 0;
    1533        9209 :         dragDownSampled.accelXY.zero();
    1534        9209 :         dragDownSampled.time_ms = 0;
    1535        9209 :         dragSampleTimeDelta = 0.0f;
    1536             :     }
    1537             : #endif // EK3_FEATURE_DRAG_FUSION
    1538             : }
    1539             : 
    1540             : /*
    1541             :   get the earth mag field
    1542             :  */
    1543      224185 : void NavEKF3_core::getEarthFieldTable(const Location &loc)
    1544             : {
    1545      224185 :     table_earth_field_ga = AP_Declination::get_earth_field_ga(loc).toftype();
    1546      224185 :     table_declination = radians(AP_Declination::get_declination(loc.lat*1.0e-7,
    1547      224185 :                                                                 loc.lng*1.0e-7));
    1548      224185 :     have_table_earth_field = true;
    1549      224185 : }
    1550             : 
    1551             : /*
    1552             :   update earth field, called at 1Hz
    1553             :  */
    1554      280584 : void NavEKF3_core::checkUpdateEarthField(void)
    1555             : {
    1556      280584 :     if (have_table_earth_field && filterStatus.flags.using_gps) {
    1557      222459 :         Location loc = EKF_origin;
    1558      222459 :         loc.offset(stateStruct.position.x, stateStruct.position.y);
    1559      222459 :         getEarthFieldTable(loc);
    1560             :     }
    1561      280584 : }

Generated by: LCOV version 1.14