Line data Source code
1 : #include <AP_HAL/AP_HAL.h>
2 :
3 : #include "AP_NavEKF3.h"
4 : #include "AP_NavEKF3_core.h"
5 : #include <GCS_MAVLink/GCS.h>
6 :
7 : #include "AP_DAL/AP_DAL.h"
8 :
9 : // Control filter mode transitions
10 78317950 : void NavEKF3_core::controlFilterModes()
11 : {
12 : // Determine motor arm status
13 78317950 : prevMotorsArmed = motorsArmed;
14 78317950 : motorsArmed = dal.get_armed();
15 78317950 : if (motorsArmed && !prevMotorsArmed) {
16 : // set the time at which we arm to assist with checks
17 2122 : timeAtArming_ms = imuSampleTime_ms;
18 : }
19 :
20 : // Detect if we are in flight on or ground
21 78317950 : detectFlight();
22 :
23 : // Determine if learning of wind and magnetic field will be enabled and set corresponding indexing limits to
24 : // avoid unnecessary operations
25 78317950 : setWindMagStateLearningMode();
26 :
27 : // Check the alignmnent status of the tilt and yaw attitude
28 : // Used during initial bootstrap alignment of the filter
29 78317950 : checkAttitudeAlignmentStatus();
30 :
31 : // Set the type of inertial navigation aiding used
32 78317950 : setAidingMode();
33 :
34 78317950 : }
35 :
36 : /*
37 : return effective value for _magCal for this core
38 : */
39 3144 : NavEKF3_core::MagCal NavEKF3_core::effective_magCal(void) const
40 : {
41 : // force use of simple magnetic heading fusion for specified cores
42 3144 : if (frontend->_magMask & core_index) {
43 : return MagCal::NEVER;
44 : }
45 :
46 : // handle deprecated MagCal::EXTERNAL_YAW and MagCal::EXTERNAL_YAW_FALLBACK values
47 3144 : const int8_t magCalParamVal = frontend->_magCal.get();
48 3144 : if (magCalParamVal == 5) {
49 : return MagCal::NEVER;
50 : }
51 3144 : if (magCalParamVal == 6) {
52 : return MagCal::WHEN_FLYING;
53 : }
54 :
55 3144 : return MagCal(magCalParamVal);
56 : }
57 :
58 : // Determine if learning of wind and magnetic field will be enabled and set corresponding indexing limits to
59 : // avoid unnecessary operations
60 78317950 : void NavEKF3_core::setWindMagStateLearningMode()
61 : {
62 78317950 : const bool recentGpsYawFusion = (yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS ||
63 377920 : yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK) &&
64 78317950 : last_gps_yaw_fuse_ms != 0 &&
65 347302 : imuSampleTime_ms - last_gps_yaw_fuse_ms < 5000;
66 78003260 : const bool yawInitialised = recentGpsYawFusion || finalInflightYawInit;
67 78317950 : const bool canEstimateWind = ((yawInitialised && dragFusionEnabled) || assume_zero_sideslip()) &&
68 9427338 : !onGround &&
69 7493809 : PV_AidingMode != AID_NONE;
70 78317950 : if (!inhibitWindStates && !canEstimateWind) {
71 487 : inhibitWindStates = true;
72 487 : lastAspdEstIsValid = false;
73 487 : updateStateIndexLim();
74 78317463 : } else if (inhibitWindStates && canEstimateWind &&
75 217958 : (sq(stateStruct.velocity.x) + sq(stateStruct.velocity.y) > sq(5.0f) || dragFusionEnabled)) {
76 511 : inhibitWindStates = false;
77 511 : updateStateIndexLim();
78 : // set states and variances
79 511 : if (yawAlignComplete && assume_zero_sideslip()) {
80 : // if we have a valid heading, set the wind states to the reciprocal of the vehicle heading
81 : // which assumes the vehicle has launched into the wind
82 : // use airspeed if if recent data available
83 499 : Vector3F tempEuler;
84 499 : stateStruct.quat.to_euler(tempEuler.x, tempEuler.y, tempEuler.z);
85 : ftype trueAirspeedVariance;
86 499 : const bool haveAirspeedMeasurement = (tasDataDelayed.allowFusion && (imuDataDelayed.time_ms - tasDataDelayed.time_ms < 500) && useAirspeed());
87 : if (haveAirspeedMeasurement) {
88 485 : trueAirspeedVariance = constrain_ftype(tasDataDelayed.tasVariance, WIND_VEL_VARIANCE_MIN, WIND_VEL_VARIANCE_MAX);
89 485 : const ftype windSpeed = sqrtF(sq(stateStruct.velocity.x) + sq(stateStruct.velocity.y)) - tasDataDelayed.tas;
90 485 : stateStruct.wind_vel.x = windSpeed * cosF(tempEuler.z);
91 485 : stateStruct.wind_vel.y = windSpeed * sinF(tempEuler.z);
92 : } else {
93 14 : trueAirspeedVariance = sq(WIND_VEL_VARIANCE_MAX); // use 2-sigma for faster initial convergence
94 : }
95 :
96 : // set the wind state variances to the measurement uncertainty
97 499 : zeroStatesVarCov(22, 23);
98 499 : P[22][22] = P[23][23] = trueAirspeedVariance;
99 :
100 499 : windStatesAligned = true;
101 :
102 : } else {
103 : // set the variances using a typical max wind speed for small UAV operation
104 12 : zeroStatesVarCov(22, 23);
105 36 : for (uint8_t index=22; index<=23; index++) {
106 24 : P[index][index] = sq(WIND_VEL_VARIANCE_MAX);
107 : }
108 : }
109 : }
110 :
111 : // determine if the vehicle is manoeuvring
112 78317950 : manoeuvring = accNavMagHoriz > 0.5f;
113 :
114 : // Determine if learning of magnetic field states has been requested by the user
115 : bool magCalRequested =
116 78317950 : ((effectiveMagCal == MagCal::WHEN_FLYING) && inFlight) || // when flying
117 66633941 : ((effectiveMagCal == MagCal::WHEN_MANOEUVRING) && manoeuvring) || // when manoeuvring
118 66633941 : ((effectiveMagCal == MagCal::AFTER_FIRST_CLIMB) && finalInflightYawInit && finalInflightMagInit) || // when initial in-air yaw and mag field reset is complete
119 108654873 : (effectiveMagCal == MagCal::ALWAYS) || // all the time
120 53200 : ((effectiveMagCal == MagCal::GROUND_AND_INFLIGHT) && (!inFlight || (finalInflightYawInit && finalInflightMagInit))); // on ground and after initial in-air yaw and mag field reset
121 :
122 : // Deny mag calibration request if we aren't using the compass, it has been inhibited by the user,
123 : // we do not have an absolute position reference or are on the ground (unless explicitly requested by the user)
124 78317950 : bool magCalDenied = !use_compass() || (effectiveMagCal == MagCal::NEVER) ||
125 76582823 : (onGround && effectiveMagCal != MagCal::ALWAYS && effectiveMagCal != MagCal::GROUND_AND_INFLIGHT);
126 :
127 : // Inhibit the magnetic field calibration if not requested or denied
128 78317950 : bool setMagInhibit = !magCalRequested || magCalDenied;
129 78317950 : if (!inhibitMagStates && setMagInhibit) {
130 1311 : inhibitMagStates = true;
131 1311 : updateStateIndexLim();
132 : // variances will be reset in CovariancePrediction
133 78316639 : } else if (inhibitMagStates && !setMagInhibit) {
134 1363 : inhibitMagStates = false;
135 1363 : updateStateIndexLim();
136 1363 : if (magFieldLearned) {
137 : // if we have already learned the field states, then retain the learned variances
138 884 : P[16][16] = earthMagFieldVar.x;
139 884 : P[17][17] = earthMagFieldVar.y;
140 884 : P[18][18] = earthMagFieldVar.z;
141 884 : P[19][19] = bodyMagFieldVar.x;
142 884 : P[20][20] = bodyMagFieldVar.y;
143 884 : P[21][21] = bodyMagFieldVar.z;
144 : } else {
145 : // set the variances equal to the observation variances
146 3353 : for (uint8_t index=16; index<=21; index++) {
147 2874 : P[index][index] = sq(frontend->_magNoise);
148 : }
149 :
150 : // set the NE earth magnetic field states using the published declination
151 : // and set the corresponding variances and covariances
152 479 : alignMagStateDeclination();
153 :
154 : }
155 : // request a reset of the yaw and magnetic field states if not done before
156 1363 : if (!magStateInitComplete || (!finalInflightMagInit && inFlight)) {
157 479 : magYawResetRequest = true;
158 : }
159 : }
160 :
161 : // inhibit delta velocity bias learning if we have not yet aligned the tilt
162 78317950 : if (tiltAlignComplete && inhibitDelVelBiasStates) {
163 : // activate the states
164 2722 : inhibitDelVelBiasStates = false;
165 2722 : updateStateIndexLim();
166 :
167 : // set the initial covariance values
168 2722 : P[13][13] = sq(ACCEL_BIAS_LIM_SCALER * frontend->_accBiasLim * dtEkfAvg);
169 2722 : P[14][14] = P[13][13];
170 2722 : P[15][15] = P[13][13];
171 : }
172 :
173 78317950 : if (tiltAlignComplete && inhibitDelAngBiasStates) {
174 : // activate the states
175 2722 : inhibitDelAngBiasStates = false;
176 2722 : updateStateIndexLim();
177 :
178 : // set the initial covariance values
179 2722 : P[10][10] = sq(radians(InitialGyroBiasUncertainty() * dtEkfAvg));
180 2722 : P[11][11] = P[10][10];
181 2722 : P[12][12] = P[10][10];
182 : }
183 :
184 : // If on ground we clear the flag indicating that the magnetic field in-flight initialisation has been completed
185 : // because we want it re-done for each takeoff
186 78317950 : if (onGround) {
187 24813474 : finalInflightYawInit = false;
188 24813474 : finalInflightMagInit = false;
189 24813474 : magFieldLearned = false;
190 : }
191 :
192 78317950 : updateStateIndexLim();
193 78317950 : }
194 :
195 : // Adjust the indexing limits used to address the covariance, states and other EKF arrays to avoid unnecessary operations
196 : // if we are not using those states
197 78327066 : void NavEKF3_core::updateStateIndexLim()
198 : {
199 78327066 : if (inhibitWindStates) {
200 71053577 : if (inhibitMagStates) {
201 30322380 : if (inhibitDelVelBiasStates) {
202 1162599 : if (inhibitDelAngBiasStates) {
203 1162599 : stateIndexLim = 9;
204 : } else {
205 0 : stateIndexLim = 12;
206 : }
207 : } else {
208 29159781 : stateIndexLim = 15;
209 : }
210 : } else {
211 40731197 : stateIndexLim = 21;
212 : }
213 : } else {
214 7273489 : stateIndexLim = 23;
215 : }
216 78327066 : }
217 :
218 : // set the default yaw source
219 78317950 : void NavEKF3_core::setYawSource()
220 : {
221 78317950 : AP_NavEKF_Source::SourceYaw yaw_source = frontend->sources.getYawSource(core_index);
222 78317950 : if (wasLearningCompass_ms > 0) {
223 : // can't use compass while it is being calibrated
224 40059 : if (yaw_source == AP_NavEKF_Source::SourceYaw::COMPASS) {
225 : yaw_source = AP_NavEKF_Source::SourceYaw::NONE;
226 39371 : } else if (yaw_source == AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK) {
227 : yaw_source = AP_NavEKF_Source::SourceYaw::GPS;
228 : }
229 : }
230 78317950 : if (yaw_source != yaw_source_last) {
231 88 : yaw_source_last = yaw_source;
232 88 : yaw_source_reset = true;
233 : }
234 78317950 : }
235 :
236 : // Set inertial navigation aiding mode
237 78317950 : void NavEKF3_core::setAidingMode()
238 : {
239 : resetDataSource posResetSource = resetDataSource::DEFAULT;
240 : resetDataSource velResetSource = resetDataSource::DEFAULT;
241 :
242 : // Save the previous status so we can detect when it has changed
243 78317950 : PV_AidingModePrev = PV_AidingMode;
244 :
245 78317950 : setYawSource();
246 :
247 : // Check that the gyro bias variance has converged
248 78317950 : checkGyroCalStatus();
249 :
250 : // Handle the special case where we are on ground and disarmed without a yaw measurement
251 : // and in AID_ABSOLUTE mode. This can occur if not using a magnetometer and yaw was aligned
252 : // using GPS during the previous flight. AID_RELATIVE is excluded because optical flow and
253 : // body odometry are body-frame sensors that do not require yaw alignment.
254 78317950 : if (yaw_source_last == AP_NavEKF_Source::SourceYaw::NONE &&
255 158468 : !motorsArmed &&
256 38384 : onGround &&
257 38384 : PV_AidingMode == AID_ABSOLUTE)
258 : {
259 38 : PV_AidingMode = AID_NONE;
260 38 : yawAlignComplete = false;
261 38 : yawAlignGpsValidCount = 0;
262 38 : finalInflightYawInit = false;
263 38 : ResetVelocity(resetDataSource::DEFAULT);
264 38 : ResetPosition(resetDataSource::DEFAULT);
265 38 : ResetHeight();
266 : // preserve quaternion 4x4 covariances, but zero the other rows and columns
267 190 : for (uint8_t row=0; row<4; row++) {
268 3192 : for (uint8_t col=4; col<24; col++) {
269 3040 : P[row][col] = 0.0f;
270 : }
271 : }
272 190 : for (uint8_t col=0; col<4; col++) {
273 3192 : for (uint8_t row=4; row<24; row++) {
274 3040 : P[row][col] = 0.0f;
275 : }
276 : }
277 : // keep the IMU bias state variances, but zero the covariances
278 : ftype oldBiasVariance[6];
279 266 : for (uint8_t row=0; row<6; row++) {
280 228 : oldBiasVariance[row] = P[row+10][row+10];
281 : }
282 38 : zeroStatesVarCov(10, 15);
283 266 : for (uint8_t row=0; row<6; row++) {
284 228 : P[row+10][row+10] = oldBiasVariance[row];
285 : }
286 : }
287 :
288 : // Determine if we should change aiding mode
289 78317950 : switch (PV_AidingMode) {
290 16493864 : case AID_NONE: {
291 : // Don't allow filter to start position or velocity aiding until the tilt and yaw alignment is complete
292 : // and IMU gyro bias estimates have stabilised
293 : // If GPS usage has been prohiited then we use flow aiding provided optical flow data is present
294 : // GPS aiding is the preferred option unless excluded by the user
295 16493864 : if (readyToUseGPS() || readyToUseRangeBeacon() || readyToUseExtNav()) {
296 1594 : PV_AidingMode = AID_ABSOLUTE;
297 : } else if (
298 : #if EK3_FEATURE_OPTFLOW_FUSION
299 32984522 : readyToUseOptFlow() ||
300 : #endif
301 16492252 : readyToUseBodyOdm()) {
302 24 : PV_AidingMode = AID_RELATIVE;
303 : }
304 : break;
305 : }
306 375746 : case AID_RELATIVE: {
307 : // Check if the fusion has timed out (flow measurements have been rejected for too long)
308 375746 : bool flowFusionTimeout = ((imuSampleTime_ms - prevFlowFuseTime_ms) > 5000);
309 : // Check if the fusion has timed out (body odometry measurements have been rejected for too long)
310 375746 : bool bodyOdmFusionTimeout = ((imuSampleTime_ms - prevBodyVelFuseTime_ms) > 5000);
311 : // Enable switch to absolute position mode if GPS or range beacon data is available
312 : // If GPS or range beacons data is not available and flow fusion has timed out, then fall-back to no-aiding
313 375746 : if (readyToUseGPS() || readyToUseRangeBeacon() || readyToUseExtNav()) {
314 8 : PV_AidingMode = AID_ABSOLUTE;
315 375738 : } else if (flowFusionTimeout && bodyOdmFusionTimeout) {
316 0 : PV_AidingMode = AID_NONE;
317 : }
318 : break;
319 : }
320 61448340 : case AID_ABSOLUTE: {
321 : // Find the minimum time without data required to trigger any check
322 61448340 : uint16_t minTestTime_ms = MIN(frontend->tiltDriftTimeMax_ms, MIN(frontend->posRetryTimeNoVel_ms,frontend->posRetryTimeUseVel_ms));
323 :
324 : // Check if optical flow data is being used
325 61448340 : bool optFlowUsed = (imuSampleTime_ms - prevFlowFuseTime_ms <= minTestTime_ms);
326 :
327 : // Check if body odometry data is being used
328 61448340 : bool bodyOdmUsed = (imuSampleTime_ms - prevBodyVelFuseTime_ms <= minTestTime_ms);
329 :
330 : // Check if airspeed data is being used
331 61448340 : bool airSpdUsed = (imuSampleTime_ms - lastTasPassTime_ms <= minTestTime_ms);
332 :
333 : // check if drag data is being used
334 61448340 : bool dragUsed = (imuSampleTime_ms - lastDragPassTime_ms <= minTestTime_ms);
335 :
336 : #if EK3_FEATURE_BEACON_FUSION
337 : // Check if range beacon data is being used
338 61448340 : const bool rngBcnUsed = (imuSampleTime_ms - rngBcn.lastPassTime_ms <= minTestTime_ms);
339 : #else
340 : const bool rngBcnUsed = false;
341 : #endif
342 :
343 : // Check if GPS or external nav is being used
344 61448340 : bool posUsed = (imuSampleTime_ms - lastGpsPosPassTime_ms <= minTestTime_ms);
345 61448340 : bool gpsVelUsed = (imuSampleTime_ms - lastVelPassTime_ms <= minTestTime_ms);
346 :
347 : // Check if attitude drift has been constrained by a measurement source
348 61448340 : bool attAiding = posUsed || gpsVelUsed || optFlowUsed || airSpdUsed || dragUsed || rngBcnUsed || bodyOdmUsed;
349 :
350 : // Check if velocity drift has been constrained by a measurement source
351 : // Currently these are all the same source as will stabilise attitude because we do not currently have
352 : // a sensor that only observes attitude
353 61448340 : velAiding = posUsed || gpsVelUsed || optFlowUsed || airSpdUsed || dragUsed || rngBcnUsed || bodyOdmUsed;
354 :
355 : // Store the last valid airspeed estimate
356 61448340 : windStateIsObservable = !inhibitWindStates && (posUsed || gpsVelUsed || optFlowUsed || rngBcnUsed || bodyOdmUsed);
357 61448340 : if (windStateIsObservable) {
358 7181751 : lastAirspeedEstimate = (stateStruct.velocity - Vector3F(stateStruct.wind_vel.x, stateStruct.wind_vel.y, 0.0F)).length();
359 7181751 : lastAspdEstIsValid = true;
360 : }
361 :
362 : // check if position drift has been constrained by a measurement source
363 61448340 : bool posAiding = posUsed || rngBcnUsed;
364 :
365 : // Check if the loss of attitude aiding has become critical
366 : bool attAidLossCritical = false;
367 61448340 : if (!attAiding) {
368 118284 : attAidLossCritical = (imuSampleTime_ms - prevFlowFuseTime_ms > frontend->tiltDriftTimeMax_ms) &&
369 59142 : (imuSampleTime_ms - lastTasPassTime_ms > frontend->tiltDriftTimeMax_ms) &&
370 : #if EK3_FEATURE_BEACON_FUSION
371 59123 : (imuSampleTime_ms - rngBcn.lastPassTime_ms > frontend->tiltDriftTimeMax_ms) &&
372 : #endif
373 118148 : (imuSampleTime_ms - lastGpsPosPassTime_ms > frontend->tiltDriftTimeMax_ms) &&
374 22 : (imuSampleTime_ms - lastVelPassTime_ms > frontend->tiltDriftTimeMax_ms);
375 : }
376 :
377 : // Check if the loss of position accuracy has become critical
378 : bool posAidLossCritical = false;
379 61448340 : if (!posAiding) {
380 : uint16_t maxLossTime_ms;
381 283301 : if (!velAiding) {
382 59142 : maxLossTime_ms = frontend->posRetryTimeNoVel_ms;
383 : } else {
384 224159 : maxLossTime_ms = frontend->posRetryTimeUseVel_ms;
385 : }
386 : posAidLossCritical =
387 : #if EK3_FEATURE_BEACON_FUSION
388 283301 : (imuSampleTime_ms - rngBcn.lastPassTime_ms > maxLossTime_ms) &&
389 : #endif
390 282116 : (imuSampleTime_ms - lastGpsPosPassTime_ms > maxLossTime_ms);
391 : }
392 :
393 61448340 : if (attAidLossCritical) {
394 : // if the loss of attitude data is critical, then put the filter into a constant position mode
395 22 : PV_AidingMode = AID_NONE;
396 22 : posTimeout = true;
397 22 : velTimeout = true;
398 22 : tasTimeout = true;
399 22 : dragTimeout = true;
400 22 : gpsIsInUse = false;
401 61448318 : } else if (posAidLossCritical) {
402 : // if the loss of position is critical, declare all sources of position aiding as being timed out
403 224271 : posTimeout = true;
404 224271 : velTimeout = !optFlowUsed && !gpsVelUsed && !bodyOdmUsed;
405 224271 : gpsIsInUse = false;
406 :
407 : }
408 : break;
409 : }
410 : }
411 :
412 : // check to see if we are starting or stopping aiding and set states and modes as required
413 78317950 : if (PV_AidingMode != PV_AidingModePrev) {
414 : // set various usage modes based on the condition when we start aiding. These are then held until aiding is stopped.
415 1686 : switch (PV_AidingMode) {
416 60 : case AID_NONE:
417 : // We have ceased aiding
418 60 : GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "EKF3 IMU%u stopped aiding",(unsigned)imu_index);
419 : // When not aiding, estimate orientation & height fusing synthetic constant position and zero velocity measurement to constrain tilt errors
420 60 : posTimeout = true;
421 60 : velTimeout = true;
422 : // Reset the normalised innovation to avoid false failing bad fusion tests
423 60 : velTestRatio = 0.0f;
424 60 : posTestRatio = 0.0f;
425 : // store the current position to be used to keep reporting the last known position
426 60 : lastKnownPositionNE.x = stateStruct.position.x;
427 60 : lastKnownPositionNE.y = stateStruct.position.y;
428 : // initialise filtered altitude used to provide a takeoff reference to current baro on disarm
429 : // this reduces the time required for the baro noise filter to settle before the filtered baro data can be used
430 60 : meaHgtAtTakeOff = baroDataDelayed.hgt;
431 : // reset the vertical position state to faster recover from baro errors experienced during touchdown
432 60 : stateStruct.position.z = -meaHgtAtTakeOff;
433 : // store the current height to be used to keep reporting
434 : // the last known position
435 60 : lastKnownPositionD = stateStruct.position.z;
436 : // reset relative aiding sensor fusion activity status
437 60 : flowFusionActive = false;
438 60 : bodyVelFusionActive = false;
439 60 : break;
440 :
441 24 : case AID_RELATIVE:
442 : // We are doing relative position navigation where velocity errors are constrained, but position drift will occur
443 24 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u started relative aiding",(unsigned)imu_index);
444 : #if EK3_FEATURE_OPTFLOW_FUSION
445 24 : if (readyToUseOptFlow()) {
446 : // Reset time stamps
447 18 : flowValidMeaTime_ms = imuSampleTime_ms;
448 18 : prevFlowFuseTime_ms = imuSampleTime_ms;
449 : } else
450 : #endif
451 6 : if (readyToUseBodyOdm()) {
452 : // Reset time stamps
453 6 : lastbodyVelPassTime_ms = imuSampleTime_ms;
454 6 : prevBodyVelFuseTime_ms = imuSampleTime_ms;
455 : }
456 24 : posTimeout = true;
457 24 : velTimeout = true;
458 24 : break;
459 :
460 1602 : case AID_ABSOLUTE:
461 1602 : if (readyToUseGPS()) {
462 : // We are commencing aiding using GPS - this is the preferred method
463 : posResetSource = resetDataSource::GPS;
464 : velResetSource = resetDataSource::GPS;
465 1591 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u is using GPS",(unsigned)imu_index);
466 : #if EK3_FEATURE_BEACON_FUSION
467 11 : } else if (readyToUseRangeBeacon()) {
468 : // We are commencing aiding using range beacons
469 : posResetSource = resetDataSource::RNGBCN;
470 6 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u is using range beacons",(unsigned)imu_index);
471 6 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u initial pos NE = %3.1f,%3.1f (m)",(unsigned)imu_index,(double)rngBcn.receiverPos.x,(double)rngBcn.receiverPos.y);
472 6 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u initial beacon pos D offset = %3.1f (m)",(unsigned)imu_index,(double)rngBcn.posOffsetNED.z);
473 : #endif // EK3_FEATURE_BEACON_FUSION
474 : #if EK3_FEATURE_EXTERNAL_NAV
475 5 : } else if (readyToUseExtNav()) {
476 : // we are commencing aiding using external nav
477 : posResetSource = resetDataSource::EXTNAV;
478 5 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u is using external nav data",(unsigned)imu_index);
479 5 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u initial pos NED = %3.1f,%3.1f,%3.1f (m)",(unsigned)imu_index,(double)extNavDataDelayed.pos.x,(double)extNavDataDelayed.pos.y,(double)extNavDataDelayed.pos.z);
480 5 : if (useExtNavVel) {
481 : velResetSource = resetDataSource::EXTNAV;
482 5 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u initial vel NED = %3.1f,%3.1f,%3.1f (m/s)",(unsigned)imu_index,(double)extNavVelDelayed.vel.x,(double)extNavVelDelayed.vel.y,(double)extNavVelDelayed.vel.z);
483 : }
484 : // handle height reset as special case
485 5 : if (frontend->sources.getPosZSource(core_index) == AP_NavEKF_Source::SourceZ::EXTNAV) {
486 3 : hgtMea = -extNavDataDelayed.pos.z;
487 3 : posDownObsNoise = sq(constrain_ftype(extNavDataDelayed.posErr, 0.1f, 10.0f));
488 3 : ResetHeight();
489 : }
490 : #endif // EK3_FEATURE_EXTERNAL_NAV
491 : }
492 :
493 : // clear timeout flags as a precaution to avoid triggering any additional transitions
494 1602 : posTimeout = false;
495 1602 : velTimeout = false;
496 :
497 : // reset the last fusion accepted times to prevent unwanted activation of timeout logic
498 1602 : lastGpsPosPassTime_ms = imuSampleTime_ms;
499 1602 : lastVelPassTime_ms = imuSampleTime_ms;
500 : #if EK3_FEATURE_BEACON_FUSION
501 1602 : rngBcn.lastPassTime_ms = imuSampleTime_ms;
502 : #endif
503 1602 : break;
504 : }
505 :
506 : // Always reset the position and velocity when changing mode
507 1686 : ResetVelocity(velResetSource);
508 1686 : ResetPosition(posResetSource);
509 : }
510 :
511 78317950 : }
512 :
513 : // Check the tilt and yaw alignmnent status
514 : // Used during initial bootstrap alignment of the filter
515 78317950 : void NavEKF3_core::checkAttitudeAlignmentStatus()
516 : {
517 : // Check for tilt convergence - used during initial alignment
518 : // Once the tilt variances have reduced, re-set the yaw and magnetic field states
519 : // and declare the tilt alignment complete
520 78317950 : if (!tiltAlignComplete) {
521 1164935 : if (tiltErrorVariance < sq(radians(5.0))) {
522 2722 : tiltAlignComplete = true;
523 2722 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u tilt alignment complete",(unsigned)imu_index);
524 : }
525 : }
526 :
527 : // submit yaw and magnetic field reset request
528 78317950 : if (!yawAlignComplete && tiltAlignComplete && use_compass()) {
529 74215 : magYawResetRequest = true;
530 : }
531 :
532 78317950 : }
533 :
534 : // return true if we should use the airspeed sensor
535 18761723 : bool NavEKF3_core::useAirspeed(void) const
536 : {
537 18761723 : return dal.airspeed_sensor_enabled();
538 : }
539 :
540 : // return true if we should use the range finder sensor
541 0 : bool NavEKF3_core::useRngFinder(void) const
542 : {
543 : // TO-DO add code to set this based in setting of optical flow use parameter and presence of sensor
544 0 : return true;
545 : }
546 :
547 : #if EK3_FEATURE_OPTFLOW_FUSION
548 : // return true if the filter is ready to start using optical flow measurements
549 16492294 : bool NavEKF3_core::readyToUseOptFlow(void) const
550 : {
551 : // ensure flow is used for navigation and not terrain alt estimation
552 16492294 : if (frontend->_flowUse != FLOW_USE_NAV) {
553 : return false;
554 : }
555 :
556 15075173 : if (!frontend->sources.useVelXYSource(AP_NavEKF_Source::SourceXY::OPTFLOW, core_index)) {
557 : return false;
558 : }
559 :
560 : // We need stable roll/pitch angles and gyro bias estimates but do not need the yaw angle aligned to use optical flow
561 10163 : return (imuSampleTime_ms - flowMeaTime_ms < 200) && tiltAlignComplete && delAngBiasLearned;
562 : }
563 : #endif // EK3_FEATURE_OPTFLOW_FUSION
564 :
565 : // return true if the filter is ready to start using body frame odometry measurements
566 16492258 : bool NavEKF3_core::readyToUseBodyOdm(void) const
567 : {
568 : #if EK3_FEATURE_BODY_ODOM
569 32969437 : if (!frontend->sources.useVelXYSource(AP_NavEKF_Source::SourceXY::EXTNAV, core_index) &&
570 16477179 : !frontend->sources.useVelXYSource(AP_NavEKF_Source::SourceXY::WHEEL_ENCODER, core_index)) {
571 : // exit immediately if sources not configured to fuse external nav or wheel encoders
572 : return false;
573 : }
574 :
575 : // Check for fresh visual odometry data that meets the accuracy required for alignment
576 15079 : bool visoDataGood = (imuSampleTime_ms - bodyOdmMeasTime_ms < 200) && (bodyOdmDataNew.velErr < 1.0f);
577 :
578 : // Check for fresh wheel encoder data
579 15079 : bool wencDataGood = (imuDataDelayed.time_ms - wheelOdmDataDelayed.time_ms < 200);
580 :
581 : // We require stable roll/pitch angles and gyro bias estimates but do not need the yaw angle aligned to use odometry measurements
582 : // because they are in a body frame of reference
583 15079 : return (visoDataGood || wencDataGood)
584 1588 : && tiltAlignComplete
585 15091 : && delAngBiasLearned;
586 : #else
587 : return false;
588 : #endif // EK3_FEATURE_BODY_ODOM
589 : }
590 :
591 : // return true if the filter to be ready to use gps
592 16871212 : bool NavEKF3_core::readyToUseGPS(void) const
593 : {
594 16871212 : if (frontend->sources.getPosXYSource(core_index) != AP_NavEKF_Source::SourceXY::GPS) {
595 : return false;
596 : }
597 :
598 16425447 : return validOrigin && tiltAlignComplete && yawAlignComplete && (delAngBiasLearned || assume_zero_sideslip()) && gpsGoodToAlign && gpsDataToFuse;
599 : }
600 :
601 : // return true if the filter to be ready to use the beacon range measurements
602 16868030 : bool NavEKF3_core::readyToUseRangeBeacon(void) const
603 : {
604 : #if EK3_FEATURE_BEACON_FUSION
605 16868030 : if (frontend->sources.getPosXYSource(core_index) != AP_NavEKF_Source::SourceXY::BEACON) {
606 : return false;
607 : }
608 :
609 62891 : return tiltAlignComplete && yawAlignComplete && delAngBiasLearned && rngBcn.alignmentCompleted && rngBcn.dataToFuse;
610 : #else
611 : return false;
612 : #endif // EK3_FEATURE_BEACON_FUSION
613 : }
614 :
615 : // return true if the filter is ready to use external nav data
616 16868018 : bool NavEKF3_core::readyToUseExtNav(void) const
617 : {
618 : #if EK3_FEATURE_EXTERNAL_NAV
619 16868018 : if (frontend->sources.getPosXYSource(core_index) != AP_NavEKF_Source::SourceXY::EXTNAV) {
620 : return false;
621 : }
622 :
623 29494 : return tiltAlignComplete && extNavDataToFuse;
624 : #else
625 : return false;
626 : #endif // EK3_FEATURE_EXTERNAL_NAV
627 : }
628 :
629 : // return true if we should use the compass
630 259350736 : bool NavEKF3_core::use_compass(void) const
631 : {
632 259350736 : if ((yaw_source_last != AP_NavEKF_Source::SourceYaw::COMPASS) &&
633 : (yaw_source_last != AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK)) {
634 : // not using compass as a yaw source
635 : return false;
636 : }
637 :
638 256219627 : const auto &compass = dal.compass();
639 256219627 : return compass.use_for_yaw(magSelectIndex) &&
640 256219602 : !allMagSensorsFailed;
641 : }
642 :
643 : // are we using (aka fusing) a non-compass yaw?
644 40189721 : bool NavEKF3_core::using_noncompass_for_yaw(void) const
645 : {
646 : #if EK3_FEATURE_EXTERNAL_NAV
647 40189721 : if (yaw_source_last == AP_NavEKF_Source::SourceYaw::EXTNAV) {
648 97068 : return ((imuSampleTime_ms - last_extnav_yaw_fusion_ms < 5000) || (imuSampleTime_ms - lastSynthYawTime_ms < 5000));
649 : }
650 : #endif
651 40092819 : if (yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS || yaw_source_last == AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK ||
652 40092819 : yaw_source_last == AP_NavEKF_Source::SourceYaw::GSF || !use_compass()) {
653 1201801 : return imuSampleTime_ms - last_gps_yaw_ms < 5000 || imuSampleTime_ms - lastSynthYawTime_ms < 5000;
654 : }
655 : return false;
656 : }
657 :
658 : // are we using (aka fusing) external nav for yaw?
659 40053019 : bool NavEKF3_core::using_extnav_for_yaw() const
660 : {
661 : #if EK3_FEATURE_EXTERNAL_NAV
662 40053019 : if (yaw_source_last == AP_NavEKF_Source::SourceYaw::EXTNAV) {
663 68739 : return ((imuSampleTime_ms - last_extnav_yaw_fusion_ms < 5000) || (imuSampleTime_ms - lastSynthYawTime_ms < 5000));
664 : }
665 : #endif
666 : return false;
667 : }
668 :
669 : // are we using a gps
670 40053019 : bool NavEKF3_core::using_gps() const
671 : {
672 40053019 : return frontend->sources.usingGPS(core_index);
673 : }
674 :
675 : /*
676 : should we assume zero sideslip?
677 : */
678 291001766 : bool NavEKF3_core::assume_zero_sideslip(void) const
679 : {
680 : // we don't assume zero sideslip for ground vehicles as EKF could
681 : // be quite sensitive to a rapid spin of the ground vehicle if
682 : // traction is lost
683 291001766 : return dal.get_fly_forward() && dal.get_vehicle_class() != AP_DAL::VehicleClass::GROUND;
684 : }
685 :
686 : // populates the Earth magnetic field table using the given location
687 1898 : void NavEKF3_core::setEarthFieldFromLocation(const Location &loc)
688 : {
689 1898 : const auto &compass = dal.compass();
690 1898 : if (compass.have_scale_factor(magSelectIndex) &&
691 1726 : compass.auto_declination_enabled()) {
692 1726 : getEarthFieldTable(loc);
693 1726 : if (frontend->_mag_ef_limit > 0) {
694 : // initialise earth field from tables
695 1726 : stateStruct.earth_magfield = table_earth_field_ga;
696 : }
697 : }
698 1898 : }
699 :
700 : // sets the local NED origin using a LLH location (latitude, longitude, height)
701 : // returns false is the origin has already been set
702 1734 : bool NavEKF3_core::setOriginLLH(const Location &loc)
703 : {
704 : // if the origin is valid reject setting a new origin
705 1734 : if (validOrigin) {
706 : return false;
707 : }
708 :
709 1734 : EKF_origin = loc;
710 1734 : ekfGpsRefHgt = (double)0.01 * (double)EKF_origin.alt;
711 : // define Earth rotation vector in the NED navigation frame at the origin
712 1734 : calcEarthRateNED(earthRateNED, EKF_origin.lat);
713 1734 : validOrigin = true;
714 :
715 : // but we do want to populate the WMM table even if we don't have a GPS at all
716 1734 : if (!stateStruct.quat.is_zero()) {
717 1728 : alignMagStateDeclination();
718 1728 : setEarthFieldFromLocation(EKF_origin);
719 : }
720 :
721 1734 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u origin set",(unsigned)imu_index);
722 :
723 1734 : if (!frontend->common_origin_valid) {
724 866 : frontend->common_origin_valid = true;
725 : // put origin in frontend as well to ensure it stays in sync between lanes
726 866 : public_origin = EKF_origin;
727 : }
728 :
729 :
730 : return true;
731 : }
732 :
733 : // record all requested yaw resets completed
734 4547 : void NavEKF3_core::recordYawResetsCompleted()
735 : {
736 4547 : gpsYawResetRequest = false;
737 4547 : magYawResetRequest = false;
738 4547 : yawAlignComplete = true;
739 4547 : if (inFlight) {
740 1381 : finalInflightYawInit = true;
741 : }
742 4547 : }
743 :
744 : // set the class variable true if the delta angle bias variances are sufficiently small
745 78317950 : void NavEKF3_core::checkGyroCalStatus(void)
746 : {
747 : // check delta angle bias variances
748 78317950 : const ftype delAngBiasVarMax = sq(radians(0.15 * dtEkfAvg));
749 78317950 : if (!use_compass() && (yaw_source_last != AP_NavEKF_Source::SourceYaw::GPS) && (yaw_source_last != AP_NavEKF_Source::SourceYaw::GPS_COMPASS_FALLBACK) &&
750 : (yaw_source_last != AP_NavEKF_Source::SourceYaw::EXTNAV)) {
751 : // rotate the variances into earth frame and evaluate horizontal terms only as yaw component is poorly observable without a yaw reference
752 : // which can make this check fail
753 197181 : const Vector3F delAngBiasVarVec { P[10][10], P[11][11], P[12][12] };
754 197181 : const Vector3F temp = prevTnb * delAngBiasVarVec;
755 394362 : delAngBiasLearned = (fabsF(temp.x) < delAngBiasVarMax) &&
756 170632 : (fabsF(temp.y) < delAngBiasVarMax);
757 : } else {
758 78120769 : delAngBiasLearned = (P[10][10] <= delAngBiasVarMax) &&
759 78120769 : (P[11][11] <= delAngBiasVarMax) &&
760 72504681 : (P[12][12] <= delAngBiasVarMax);
761 : }
762 78317950 : }
763 :
764 : // Update the filter status
765 19965274 : void NavEKF3_core::updateFilterStatus(void)
766 : {
767 : // init return value
768 : nav_filter_status status;
769 19965274 : status.value = 0;
770 19965274 : bool doingBodyVelNav = (PV_AidingMode != AID_NONE) && (imuSampleTime_ms - prevBodyVelFuseTime_ms < 5000);
771 19965274 : bool doingFlowNav = (PV_AidingMode != AID_NONE) && flowDataValid;
772 19965274 : bool doingWindRelNav = (!tasTimeout && assume_zero_sideslip()) || !dragTimeout;
773 19965274 : bool doingNormalGpsNav = !posTimeout && (PV_AidingMode == AID_ABSOLUTE);
774 19965274 : bool someVertRefData = (!velTimeout && (useGpsVertVel || useExtNavVel)) || !hgtTimeout;
775 19965274 : bool someHorizRefData = !(velTimeout && posTimeout && tasTimeout && dragTimeout) || doingFlowNav || doingBodyVelNav;
776 19965274 : bool filterHealthy = healthy() && tiltAlignComplete && (yawAlignComplete || (!use_compass() && (PV_AidingMode != AID_ABSOLUTE)));
777 :
778 : // If GPS height usage is specified, height is considered to be inaccurate until the GPS passes all checks
779 19965274 : bool hgtNotAccurate = (frontend->sources.getPosZSource(core_index) == AP_NavEKF_Source::SourceZ::GPS) && !validOrigin;
780 :
781 : // set individual flags
782 19965274 : status.flags.attitude = !stateStruct.quat.is_nan() && filterHealthy; // attitude valid (we need a better check)
783 19965274 : status.flags.horiz_vel = someHorizRefData && filterHealthy; // horizontal velocity estimate valid
784 19965274 : status.flags.vert_vel = someVertRefData && filterHealthy; // vertical velocity estimate valid
785 :
786 : #if EK3_FEATURE_OPTFLOW_SRTM
787 19965274 : const bool optflow_gnd_offset = gndOffsetValid || terrain_srtm_alt_valid;
788 : #else
789 : const bool optflow_gnd_offset = gndOffsetValid;
790 : #endif
791 19965274 : status.flags.horiz_pos_rel = ((doingFlowNav && optflow_gnd_offset) || doingWindRelNav || doingNormalGpsNav || doingBodyVelNav) && filterHealthy; // relative horizontal position estimate valid
792 :
793 19965274 : status.flags.horiz_pos_abs = doingNormalGpsNav && filterHealthy; // absolute horizontal position estimate valid
794 19965274 : status.flags.vert_pos = !hgtTimeout && filterHealthy && !hgtNotAccurate; // vertical position estimate valid
795 19965274 : status.flags.terrain_alt = gndOffsetValid && filterHealthy; // terrain height estimate valid
796 19965274 : status.flags.const_pos_mode = (PV_AidingMode == AID_NONE) && filterHealthy; // constant position mode
797 19965274 : status.flags.pred_horiz_pos_rel = status.flags.horiz_pos_rel; // EKF3 enters the required mode before flight
798 19965274 : status.flags.pred_horiz_pos_abs = status.flags.horiz_pos_abs; // EKF3 enters the required mode before flight
799 19965274 : status.flags.takeoff_detected = takeOffDetected; // takeoff for optical flow navigation has been detected
800 19965274 : status.flags.takeoff = dal.get_takeoff_expected(); // The EKF has been told to expect takeoff is in a ground effect mitigation mode and has started the EKF-GSF yaw estimator
801 19965274 : status.flags.touchdown = dal.get_touchdown_expected(); // The EKF has been told to detect touchdown and is in a ground effect mitigation mode
802 19965274 : status.flags.using_gps = ((imuSampleTime_ms - lastGpsPosPassTime_ms) < 4000) && (PV_AidingMode == AID_ABSOLUTE);
803 19965274 : status.flags.gps_glitching = !gpsAccuracyGood && (PV_AidingMode == AID_ABSOLUTE) && (frontend->sources.getPosXYSource(core_index) == AP_NavEKF_Source::SourceXY::GPS); // GPS glitching is affecting navigation accuracy
804 19965274 : status.flags.gps_quality_good = gpsGoodToAlign;
805 : // for reporting purposes we report rejecting airspeed after 3s of not fusing when we want to fuse the data
806 39976983 : status.flags.rejecting_airspeed = lastTasFailTime_ms != 0 &&
807 19965274 : (imuSampleTime_ms - lastTasFailTime_ms) < 1000 &&
808 9339 : (imuSampleTime_ms - lastTasPassTime_ms) > 3000;
809 19965274 : status.flags.initalized = status.flags.initalized || healthy();
810 19965274 : status.flags.dead_reckoning = (PV_AidingMode != AID_NONE) && doingWindRelNav && !((doingFlowNav && gndOffsetValid) || doingNormalGpsNav || doingBodyVelNav);
811 :
812 19965274 : filterStatus.value = status.value;
813 19965274 : }
814 :
815 19965274 : void NavEKF3_core::runYawEstimatorPrediction()
816 : {
817 : // exit immediately if no yaw estimator
818 19965274 : if (yawEstimator == nullptr) {
819 : return;
820 : }
821 :
822 : // ensure GPS is used for horizontal position and velocity
823 39737603 : if (frontend->sources.getPosXYSource(core_index) != AP_NavEKF_Source::SourceXY::GPS ||
824 19780419 : !frontend->sources.useVelXYSource(AP_NavEKF_Source::SourceXY::GPS, core_index)) {
825 199774 : return;
826 : }
827 :
828 : ftype trueAirspeed;
829 19757410 : if (tasDataDelayed.allowFusion && assume_zero_sideslip()) {
830 4420593 : trueAirspeed = MAX(tasDataDelayed.tas, 0.0f);
831 : } else {
832 : trueAirspeed = 0.0f;
833 : }
834 19757410 : yawEstimator->update(imuDataDelayed.delAng, imuDataDelayed.delVel, imuDataDelayed.delAngDT, imuDataDelayed.delVelDT, EKFGSF_run_filterbank, trueAirspeed);
835 : }
836 :
837 19965274 : void NavEKF3_core::runYawEstimatorCorrection()
838 : {
839 : // exit immediately if no yaw estimator
840 19965274 : if (yawEstimator == nullptr) {
841 : return;
842 : }
843 : // ensure GPS is used for horizontal position and velocity
844 39737603 : if (frontend->sources.getPosXYSource(core_index) != AP_NavEKF_Source::SourceXY::GPS ||
845 19780419 : !frontend->sources.useVelXYSource(AP_NavEKF_Source::SourceXY::GPS, core_index)) {
846 199774 : return;
847 : }
848 :
849 19757410 : if (EKFGSF_run_filterbank) {
850 13887176 : if (gpsDataToFuse) {
851 1007751 : Vector2F gpsVelNE = Vector2F(gpsDataDelayed.vel.x, gpsDataDelayed.vel.y);
852 1007751 : ftype gpsVelAcc = fmaxF(gpsSpdAccuracy, ftype(frontend->_gpsHorizVelNoise));
853 1007751 : yawEstimator->fuseVelData(gpsVelNE, gpsVelAcc);
854 :
855 : // after velocity data has been fused the yaw variance estimate will have been refreshed and
856 : // is used maintain a history of validity
857 : ftype gsfYaw, gsfYawVariance;
858 1007751 : if (EKFGSF_getYaw(gsfYaw, gsfYawVariance)) {
859 655893 : if (EKFGSF_yaw_valid_count < GSF_YAW_VALID_HISTORY_THRESHOLD) {
860 11795 : EKFGSF_yaw_valid_count++;
861 : }
862 : } else {
863 351858 : EKFGSF_yaw_valid_count = 0;
864 : }
865 : }
866 :
867 : // action an external reset request
868 13887176 : if (EKFGSF_yaw_reset_request_ms > 0 && imuSampleTime_ms - EKFGSF_yaw_reset_request_ms < YAW_RESET_TO_GSF_TIMEOUT_MS) {
869 1616 : EKFGSF_resetMainFilterYaw(true);
870 : }
871 : } else {
872 5870234 : EKFGSF_yaw_valid_count = 0;
873 : }
874 : }
875 :
876 : // request a reset the yaw to the GSF estimate
877 : // request times out after YAW_RESET_TO_GSF_TIMEOUT_MS if it cannot be actioned
878 8 : void NavEKF3_core::EKFGSF_requestYawReset()
879 : {
880 8 : EKFGSF_yaw_reset_request_ms = imuSampleTime_ms;
881 8 : }
|