2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
25 #include "build/build_config.h"
26 #include "build/debug.h"
28 #include "common/axis.h"
29 #include "common/log.h"
30 #include "common/maths.h"
32 #include "config/parameter_group.h"
33 #include "config/parameter_group_ids.h"
35 #include "drivers/time.h"
37 #include "fc/config.h"
38 #include "fc/settings.h"
40 #include "flight/imu.h"
41 #include "flight/secondary_imu.h"
45 #include "navigation/navigation.h"
46 #include "navigation/navigation_private.h"
47 #include "navigation/navigation_pos_estimator_private.h"
49 #include "sensors/acceleration.h"
50 #include "sensors/barometer.h"
51 #include "sensors/compass.h"
52 #include "sensors/pitotmeter.h"
53 #include "sensors/opflow.h"
55 navigationPosEstimator_t posEstimator
;
57 PG_REGISTER_WITH_RESET_TEMPLATE(positionEstimationConfig_t
, positionEstimationConfig
, PG_POSITION_ESTIMATION_CONFIG
, 5);
59 PG_RESET_TEMPLATE(positionEstimationConfig_t
, positionEstimationConfig
,
60 // Inertial position estimator parameters
61 .automatic_mag_declination
= SETTING_INAV_AUTO_MAG_DECL_DEFAULT
,
62 .reset_altitude_type
= SETTING_INAV_RESET_ALTITUDE_DEFAULT
,
63 .reset_home_type
= SETTING_INAV_RESET_HOME_DEFAULT
,
64 .gravity_calibration_tolerance
= SETTING_INAV_GRAVITY_CAL_TOLERANCE_DEFAULT
, // 5 cm/s/s calibration error accepted (0.5% of gravity)
65 .use_gps_velned
= SETTING_INAV_USE_GPS_VELNED_DEFAULT
, // "Disabled" is mandatory with gps_dyn_model = Pedestrian
66 .use_gps_no_baro
= SETTING_INAV_USE_GPS_NO_BARO_DEFAULT
, // Use GPS altitude if no baro is available on all aircrafts
67 .allow_dead_reckoning
= SETTING_INAV_ALLOW_DEAD_RECKONING_DEFAULT
,
69 .max_surface_altitude
= SETTING_INAV_MAX_SURFACE_ALTITUDE_DEFAULT
,
71 .w_xyz_acc_p
= SETTING_INAV_W_XYZ_ACC_P_DEFAULT
,
73 .w_z_baro_p
= SETTING_INAV_W_Z_BARO_P_DEFAULT
,
75 .w_z_surface_p
= SETTING_INAV_W_Z_SURFACE_P_DEFAULT
,
76 .w_z_surface_v
= SETTING_INAV_W_Z_SURFACE_V_DEFAULT
,
78 .w_z_gps_p
= SETTING_INAV_W_Z_GPS_P_DEFAULT
,
79 .w_z_gps_v
= SETTING_INAV_W_Z_GPS_V_DEFAULT
,
81 .w_xy_gps_p
= SETTING_INAV_W_XY_GPS_P_DEFAULT
,
82 .w_xy_gps_v
= SETTING_INAV_W_XY_GPS_V_DEFAULT
,
84 .w_xy_flow_p
= SETTING_INAV_W_XY_FLOW_P_DEFAULT
,
85 .w_xy_flow_v
= SETTING_INAV_W_XY_FLOW_V_DEFAULT
,
87 .w_z_res_v
= SETTING_INAV_W_Z_RES_V_DEFAULT
,
88 .w_xy_res_v
= SETTING_INAV_W_XY_RES_V_DEFAULT
,
90 .w_acc_bias
= SETTING_INAV_W_ACC_BIAS_DEFAULT
,
92 .max_eph_epv
= SETTING_INAV_MAX_EPH_EPV_DEFAULT
,
93 .baro_epv
= SETTING_INAV_BARO_EPV_DEFAULT
96 #define resetTimer(tim, currentTimeUs) { (tim)->deltaTime = 0; (tim)->lastTriggeredTime = currentTimeUs; }
97 #define getTimerDeltaMicros(tim) ((tim)->deltaTime)
98 static bool updateTimer(navigationTimer_t
* tim
, timeUs_t interval
, timeUs_t currentTimeUs
)
100 if ((currentTimeUs
- tim
->lastTriggeredTime
) >= interval
) {
101 tim
->deltaTime
= currentTimeUs
- tim
->lastTriggeredTime
;
102 tim
->lastTriggeredTime
= currentTimeUs
;
110 static bool shouldResetReferenceAltitude(void)
112 switch ((nav_reset_type_e
)positionEstimationConfig()->reset_altitude_type
) {
113 case NAV_RESET_NEVER
:
115 case NAV_RESET_ON_FIRST_ARM
:
116 return !ARMING_FLAG(ARMED
) && !ARMING_FLAG(WAS_EVER_ARMED
);
117 case NAV_RESET_ON_EACH_ARM
:
118 return !ARMING_FLAG(ARMED
);
125 /* Why is this here: Because GPS will be sending at quiet a nailed rate (if not overloaded by junk tasks at the brink of its specs)
126 * but we might read out with timejitter because Irq might be off by a few us so we do a +-10% margin around the time between GPS
127 * datasets representing the most common Hz-rates today. You might want to extend the list or find a smarter way.
128 * Don't overload your GPS in its config with trash, choose a Hz rate that it can deliver at a sustained rate.
131 static timeUs_t
getGPSDeltaTimeFilter(timeUs_t dTus
)
133 if (dTus
>= 225000 && dTus
<= 275000) return HZ2US(4); // 4Hz Data 250ms
134 if (dTus
>= 180000 && dTus
<= 220000) return HZ2US(5); // 5Hz Data 200ms
135 if (dTus
>= 90000 && dTus
<= 110000) return HZ2US(10); // 10Hz Data 100ms
136 if (dTus
>= 45000 && dTus
<= 55000) return HZ2US(20); // 20Hz Data 50ms
137 if (dTus
>= 30000 && dTus
<= 36000) return HZ2US(30); // 30Hz Data 33ms
138 if (dTus
>= 23000 && dTus
<= 27000) return HZ2US(40); // 40Hz Data 25ms
139 if (dTus
>= 18000 && dTus
<= 22000) return HZ2US(50); // 50Hz Data 20ms
140 return dTus
; // Filter failed. Set GPS Hz by measurement
143 #if defined(NAV_GPS_GLITCH_DETECTION)
144 static bool detectGPSGlitch(timeUs_t currentTimeUs
)
146 static timeUs_t previousTime
= 0;
147 static fpVector3_t lastKnownGoodPosition
;
148 static fpVector3_t lastKnownGoodVelocity
;
150 bool isGlitching
= false;
152 if (previousTime
== 0) {
156 fpVector3_t predictedGpsPosition
;
158 float dT
= US2S(currentTimeUs
- previousTime
);
160 /* We predict new position based on previous GPS velocity and position */
161 predictedGpsPosition
.x
= lastKnownGoodPosition
.x
+ lastKnownGoodVelocity
.x
* dT
;
162 predictedGpsPosition
.y
= lastKnownGoodPosition
.y
+ lastKnownGoodVelocity
.y
* dT
;
164 /* New pos is within predefined radius of predicted pos, radius is expanded exponentially */
165 gpsDistance
= fast_fsqrtf(sq(predictedGpsPosition
.x
- lastKnownGoodPosition
.x
) + sq(predictedGpsPosition
.y
- lastKnownGoodPosition
.y
));
166 if (gpsDistance
<= (INAV_GPS_GLITCH_RADIUS
+ 0.5f
* INAV_GPS_GLITCH_ACCEL
* dT
* dT
)) {
175 previousTime
= currentTimeUs
;
176 lastKnownGoodPosition
= posEstimator
.gps
.pos
;
177 lastKnownGoodVelocity
= posEstimator
.gps
.vel
;
186 * Function is called on each GPS update
188 void onNewGPSData(void)
190 static timeUs_t lastGPSNewDataTime
;
191 static int32_t previousLat
;
192 static int32_t previousLon
;
193 static int32_t previousAlt
;
194 static bool isFirstGPSUpdate
= true;
196 gpsLocation_t newLLH
;
197 const timeUs_t currentTimeUs
= micros();
199 newLLH
.lat
= gpsSol
.llh
.lat
;
200 newLLH
.lon
= gpsSol
.llh
.lon
;
201 newLLH
.alt
= gpsSol
.llh
.alt
;
203 if (sensors(SENSOR_GPS
)) {
204 if (!STATE(GPS_FIX
)) {
205 isFirstGPSUpdate
= true;
209 if ((currentTimeUs
- lastGPSNewDataTime
) > MS2US(INAV_GPS_TIMEOUT_MS
)) {
210 isFirstGPSUpdate
= true;
213 /* Automatic magnetic declination calculation - do this once */
214 if(STATE(GPS_FIX_HOME
)){
215 static bool magDeclinationSet
= false;
216 if (positionEstimationConfig()->automatic_mag_declination
&& !magDeclinationSet
) {
217 const float declination
= geoCalculateMagDeclination(&newLLH
);
218 imuSetMagneticDeclination(declination
);
219 #ifdef USE_SECONDARY_IMU
220 secondaryImuSetMagneticDeclination(declination
);
222 magDeclinationSet
= true;
225 /* Process position update if GPS origin is already set, or precision is good enough */
226 // FIXME: Add HDOP check for acquisition of GPS origin
227 /* Set GPS origin or reset the origin altitude - keep initial pre-arming altitude at zero */
228 if (!posControl
.gpsOrigin
.valid
) {
229 geoSetOrigin(&posControl
.gpsOrigin
, &newLLH
, GEO_ORIGIN_SET
);
231 else if (shouldResetReferenceAltitude()) {
232 /* If we were never armed - keep altitude at zero */
233 geoSetOrigin(&posControl
.gpsOrigin
, &newLLH
, GEO_ORIGIN_RESET_ALTITUDE
);
236 if (posControl
.gpsOrigin
.valid
) {
237 /* Convert LLH position to local coordinates */
238 geoConvertGeodeticToLocal(&posEstimator
.gps
.pos
, &posControl
.gpsOrigin
, &newLLH
, GEO_ALT_ABSOLUTE
);
240 /* If not the first update - calculate velocities */
241 if (!isFirstGPSUpdate
) {
242 float dT
= US2S(getGPSDeltaTimeFilter(currentTimeUs
- lastGPSNewDataTime
));
244 /* Use VELNED provided by GPS if available, calculate from coordinates otherwise */
245 float gpsScaleLonDown
= constrainf(cos_approx((ABS(gpsSol
.llh
.lat
) / 10000000.0f
) * 0.0174532925f
), 0.01f
, 1.0f
);
246 if (positionEstimationConfig()->use_gps_velned
&& gpsSol
.flags
.validVelNE
) {
247 posEstimator
.gps
.vel
.x
= gpsSol
.velNED
[X
];
248 posEstimator
.gps
.vel
.y
= gpsSol
.velNED
[Y
];
251 posEstimator
.gps
.vel
.x
= (posEstimator
.gps
.vel
.x
+ (DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR
* (gpsSol
.llh
.lat
- previousLat
) / dT
)) / 2.0f
;
252 posEstimator
.gps
.vel
.y
= (posEstimator
.gps
.vel
.y
+ (gpsScaleLonDown
* DISTANCE_BETWEEN_TWO_LONGITUDE_POINTS_AT_EQUATOR
* (gpsSol
.llh
.lon
- previousLon
) / dT
)) / 2.0f
;
255 if (positionEstimationConfig()->use_gps_velned
&& gpsSol
.flags
.validVelD
) {
256 posEstimator
.gps
.vel
.z
= -gpsSol
.velNED
[Z
]; // NEU
259 posEstimator
.gps
.vel
.z
= (posEstimator
.gps
.vel
.z
+ (gpsSol
.llh
.alt
- previousAlt
) / dT
) / 2.0f
;
262 #if defined(NAV_GPS_GLITCH_DETECTION)
263 /* GPS glitch protection. We have local coordinates and local velocity for current GPS update. Check if they are sane */
264 if (detectGPSGlitch(currentTimeUs
)) {
265 posEstimator
.gps
.glitchRecovery
= false;
266 posEstimator
.gps
.glitchDetected
= true;
269 /* Store previous glitch flag in glitchRecovery to indicate a valid reading after a glitch */
270 posEstimator
.gps
.glitchRecovery
= posEstimator
.gps
.glitchDetected
;
271 posEstimator
.gps
.glitchDetected
= false;
275 /* FIXME: use HDOP/VDOP */
276 if (gpsSol
.flags
.validEPE
) {
277 posEstimator
.gps
.eph
= gpsSol
.eph
;
278 posEstimator
.gps
.epv
= gpsSol
.epv
;
281 posEstimator
.gps
.eph
= INAV_GPS_DEFAULT_EPH
;
282 posEstimator
.gps
.epv
= INAV_GPS_DEFAULT_EPV
;
285 /* Indicate a last valid reading of Pos/Vel */
286 posEstimator
.gps
.lastUpdateTime
= currentTimeUs
;
289 previousLat
= gpsSol
.llh
.lat
;
290 previousLon
= gpsSol
.llh
.lon
;
291 previousAlt
= gpsSol
.llh
.alt
;
292 isFirstGPSUpdate
= false;
294 lastGPSNewDataTime
= currentTimeUs
;
298 posEstimator
.gps
.lastUpdateTime
= 0;
303 #if defined(USE_BARO)
305 * Read BARO and update alt/vel topic
306 * Function is called from TASK_BARO
308 void updatePositionEstimator_BaroTopic(timeUs_t currentTimeUs
)
310 static float initialBaroAltitudeOffset
= 0.0f
;
311 float newBaroAlt
= baroCalculateAltitude();
313 /* If we are required - keep altitude at zero */
314 if (shouldResetReferenceAltitude()) {
315 initialBaroAltitudeOffset
= newBaroAlt
;
318 if (sensors(SENSOR_BARO
) && baroIsCalibrationComplete()) {
319 const timeUs_t baroDtUs
= currentTimeUs
- posEstimator
.baro
.lastUpdateTime
;
321 posEstimator
.baro
.alt
= newBaroAlt
- initialBaroAltitudeOffset
;
322 posEstimator
.baro
.epv
= positionEstimationConfig()->baro_epv
;
323 posEstimator
.baro
.lastUpdateTime
= currentTimeUs
;
325 if (baroDtUs
<= MS2US(INAV_BARO_TIMEOUT_MS
)) {
326 pt1FilterApply3(&posEstimator
.baro
.avgFilter
, posEstimator
.baro
.alt
, US2S(baroDtUs
));
330 posEstimator
.baro
.alt
= 0;
331 posEstimator
.baro
.lastUpdateTime
= 0;
336 #if defined(USE_PITOT)
338 * Read Pitot and update airspeed topic
339 * Function is called at main loop rate, updates happen at reduced rate
341 void updatePositionEstimator_PitotTopic(timeUs_t currentTimeUs
)
343 posEstimator
.pitot
.airspeed
= pitot
.airSpeed
;
344 posEstimator
.pitot
.lastUpdateTime
= currentTimeUs
;
350 * Function is called at main loop rate
352 static void restartGravityCalibration(void)
354 zeroCalibrationStartS(&posEstimator
.imu
.gravityCalibration
, CALIBRATING_GRAVITY_TIME_MS
, positionEstimationConfig()->gravity_calibration_tolerance
, false);
357 static bool gravityCalibrationComplete(void)
359 return zeroCalibrationIsCompleteS(&posEstimator
.imu
.gravityCalibration
);
362 static void updateIMUEstimationWeight(const float dt
)
364 bool isAccClipped
= accIsClipped();
366 // If accelerometer measurement is clipped - drop the acc weight to zero
367 // and gradually restore weight back to 1.0 over time
369 posEstimator
.imu
.accWeightFactor
= 0.0f
;
372 const float relAlpha
= dt
/ (dt
+ INAV_ACC_CLIPPING_RC_CONSTANT
);
373 posEstimator
.imu
.accWeightFactor
= posEstimator
.imu
.accWeightFactor
* (1.0f
- relAlpha
) + 1.0f
* relAlpha
;
376 // DEBUG_VIBE[0-3] are used in IMU
377 DEBUG_SET(DEBUG_VIBE
, 4, posEstimator
.imu
.accWeightFactor
* 1000);
380 float navGetAccelerometerWeight(void)
382 const float accWeightScaled
= posEstimator
.imu
.accWeightFactor
* positionEstimationConfig()->w_xyz_acc_p
;
383 DEBUG_SET(DEBUG_VIBE
, 5, accWeightScaled
* 1000);
385 return accWeightScaled
;
388 static void updateIMUTopic(timeUs_t currentTimeUs
)
390 const float dt
= US2S(currentTimeUs
- posEstimator
.imu
.lastUpdateTime
);
391 posEstimator
.imu
.lastUpdateTime
= currentTimeUs
;
394 posEstimator
.imu
.accelNEU
.x
= 0;
395 posEstimator
.imu
.accelNEU
.y
= 0;
396 posEstimator
.imu
.accelNEU
.z
= 0;
398 restartGravityCalibration();
401 /* Update acceleration weight based on vibration levels and clipping */
402 updateIMUEstimationWeight(dt
);
406 /* Read acceleration data in body frame */
407 accelBF
.x
= imuMeasuredAccelBF
.x
;
408 accelBF
.y
= imuMeasuredAccelBF
.y
;
409 accelBF
.z
= imuMeasuredAccelBF
.z
;
411 /* Correct accelerometer bias */
412 accelBF
.x
-= posEstimator
.imu
.accelBias
.x
;
413 accelBF
.y
-= posEstimator
.imu
.accelBias
.y
;
414 accelBF
.z
-= posEstimator
.imu
.accelBias
.z
;
416 /* Rotate vector to Earth frame - from Forward-Right-Down to North-East-Up*/
417 imuTransformVectorBodyToEarth(&accelBF
);
419 /* Read acceleration data in NEU frame from IMU */
420 posEstimator
.imu
.accelNEU
.x
= accelBF
.x
;
421 posEstimator
.imu
.accelNEU
.y
= accelBF
.y
;
422 posEstimator
.imu
.accelNEU
.z
= accelBF
.z
;
424 /* When unarmed, assume that accelerometer should measure 1G. Use that to correct accelerometer gain */
425 if (!ARMING_FLAG(ARMED
) && !gravityCalibrationComplete()) {
426 zeroCalibrationAddValueS(&posEstimator
.imu
.gravityCalibration
, posEstimator
.imu
.accelNEU
.z
);
428 if (gravityCalibrationComplete()) {
429 zeroCalibrationGetZeroS(&posEstimator
.imu
.gravityCalibration
, &posEstimator
.imu
.calibratedGravityCMSS
);
430 LOG_D(POS_ESTIMATOR
, "Gravity calibration complete (%d)", (int)lrintf(posEstimator
.imu
.calibratedGravityCMSS
));
434 /* If calibration is incomplete - report zero acceleration */
435 if (gravityCalibrationComplete()) {
436 posEstimator
.imu
.accelNEU
.z
-= posEstimator
.imu
.calibratedGravityCMSS
;
439 posEstimator
.imu
.accelNEU
.x
= 0;
440 posEstimator
.imu
.accelNEU
.y
= 0;
441 posEstimator
.imu
.accelNEU
.z
= 0;
444 /* Update blackbox values */
445 navAccNEU
[X
] = posEstimator
.imu
.accelNEU
.x
;
446 navAccNEU
[Y
] = posEstimator
.imu
.accelNEU
.y
;
447 navAccNEU
[Z
] = posEstimator
.imu
.accelNEU
.z
;
451 float updateEPE(const float oldEPE
, const float dt
, const float newEPE
, const float w
)
453 return oldEPE
+ (newEPE
- oldEPE
) * w
* dt
;
456 static bool navIsAccelerationUsable(void)
461 static bool navIsHeadingUsable(void)
463 if (sensors(SENSOR_GPS
)) {
464 // If we have GPS - we need true IMU north (valid heading)
465 return isImuHeadingValid();
468 // If we don't have GPS - we may use whatever we have, other sensors are operating in body frame
469 return isImuHeadingValid() || positionEstimationConfig()->allow_dead_reckoning
;
473 static uint32_t calculateCurrentValidityFlags(timeUs_t currentTimeUs
)
475 /* Figure out if we have valid position data from our data sources */
476 uint32_t newFlags
= 0;
478 if (sensors(SENSOR_GPS
) && posControl
.gpsOrigin
.valid
&&
479 ((currentTimeUs
- posEstimator
.gps
.lastUpdateTime
) <= MS2US(INAV_GPS_TIMEOUT_MS
)) &&
480 (posEstimator
.gps
.eph
< positionEstimationConfig()->max_eph_epv
)) {
481 if (posEstimator
.gps
.epv
< positionEstimationConfig()->max_eph_epv
) {
482 newFlags
|= EST_GPS_XY_VALID
| EST_GPS_Z_VALID
;
485 newFlags
|= EST_GPS_XY_VALID
;
489 if (sensors(SENSOR_BARO
) && ((currentTimeUs
- posEstimator
.baro
.lastUpdateTime
) <= MS2US(INAV_BARO_TIMEOUT_MS
))) {
490 newFlags
|= EST_BARO_VALID
;
493 if (sensors(SENSOR_RANGEFINDER
) && ((currentTimeUs
- posEstimator
.surface
.lastUpdateTime
) <= MS2US(INAV_SURFACE_TIMEOUT_MS
))) {
494 newFlags
|= EST_SURFACE_VALID
;
497 if (sensors(SENSOR_OPFLOW
) && posEstimator
.flow
.isValid
&& ((currentTimeUs
- posEstimator
.flow
.lastUpdateTime
) <= MS2US(INAV_FLOW_TIMEOUT_MS
))) {
498 newFlags
|= EST_FLOW_VALID
;
501 if (posEstimator
.est
.eph
< positionEstimationConfig()->max_eph_epv
) {
502 newFlags
|= EST_XY_VALID
;
505 if (posEstimator
.est
.epv
< positionEstimationConfig()->max_eph_epv
) {
506 newFlags
|= EST_Z_VALID
;
512 static void estimationPredict(estimationContext_t
* ctx
)
514 const float accWeight
= navGetAccelerometerWeight();
516 /* Prediction step: Z-axis */
517 if ((ctx
->newFlags
& EST_Z_VALID
)) {
518 posEstimator
.est
.pos
.z
+= posEstimator
.est
.vel
.z
* ctx
->dt
;
519 posEstimator
.est
.pos
.z
+= posEstimator
.imu
.accelNEU
.z
* sq(ctx
->dt
) / 2.0f
* accWeight
;
520 posEstimator
.est
.vel
.z
+= posEstimator
.imu
.accelNEU
.z
* ctx
->dt
* sq(accWeight
);
523 /* Prediction step: XY-axis */
524 if ((ctx
->newFlags
& EST_XY_VALID
)) {
525 // Predict based on known velocity
526 posEstimator
.est
.pos
.x
+= posEstimator
.est
.vel
.x
* ctx
->dt
;
527 posEstimator
.est
.pos
.y
+= posEstimator
.est
.vel
.y
* ctx
->dt
;
529 // If heading is valid, accelNEU is valid as well. Account for acceleration
530 if (navIsHeadingUsable() && navIsAccelerationUsable()) {
531 posEstimator
.est
.pos
.x
+= posEstimator
.imu
.accelNEU
.x
* sq(ctx
->dt
) / 2.0f
* accWeight
;
532 posEstimator
.est
.pos
.y
+= posEstimator
.imu
.accelNEU
.y
* sq(ctx
->dt
) / 2.0f
* accWeight
;
533 posEstimator
.est
.vel
.x
+= posEstimator
.imu
.accelNEU
.x
* ctx
->dt
* sq(accWeight
);
534 posEstimator
.est
.vel
.y
+= posEstimator
.imu
.accelNEU
.y
* ctx
->dt
* sq(accWeight
);
539 static bool estimationCalculateCorrection_Z(estimationContext_t
* ctx
)
541 if (ctx
->newFlags
& EST_BARO_VALID
) {
542 timeUs_t currentTimeUs
= micros();
544 if (!ARMING_FLAG(ARMED
)) {
545 posEstimator
.state
.baroGroundAlt
= posEstimator
.est
.pos
.z
;
546 posEstimator
.state
.isBaroGroundValid
= true;
547 posEstimator
.state
.baroGroundTimeout
= currentTimeUs
+ 250000; // 0.25 sec
550 if (posEstimator
.est
.vel
.z
> 15) {
551 if (currentTimeUs
> posEstimator
.state
.baroGroundTimeout
) {
552 posEstimator
.state
.isBaroGroundValid
= false;
556 posEstimator
.state
.baroGroundTimeout
= currentTimeUs
+ 250000; // 0.25 sec
560 // We might be experiencing air cushion effect - use sonar or baro groung altitude to detect it
561 bool isAirCushionEffectDetected
= ARMING_FLAG(ARMED
) &&
562 (((ctx
->newFlags
& EST_SURFACE_VALID
) && posEstimator
.surface
.alt
< 20.0f
&& posEstimator
.state
.isBaroGroundValid
) ||
563 ((ctx
->newFlags
& EST_BARO_VALID
) && posEstimator
.state
.isBaroGroundValid
&& posEstimator
.baro
.alt
< posEstimator
.state
.baroGroundAlt
));
566 const float baroAltResidual
= (isAirCushionEffectDetected
? posEstimator
.state
.baroGroundAlt
: posEstimator
.baro
.alt
) - posEstimator
.est
.pos
.z
;
567 ctx
->estPosCorr
.z
+= baroAltResidual
* positionEstimationConfig()->w_z_baro_p
* ctx
->dt
;
568 ctx
->estVelCorr
.z
+= baroAltResidual
* sq(positionEstimationConfig()->w_z_baro_p
) * ctx
->dt
;
570 // If GPS is available - also use GPS climb rate
571 if (ctx
->newFlags
& EST_GPS_Z_VALID
) {
572 // Trust GPS velocity only if residual/error is less than 2.5 m/s, scale weight according to gaussian distribution
573 const float gpsRocResidual
= posEstimator
.gps
.vel
.z
- posEstimator
.est
.vel
.z
;
574 const float gpsRocScaler
= bellCurve(gpsRocResidual
, 250.0f
);
575 ctx
->estVelCorr
.z
+= gpsRocResidual
* positionEstimationConfig()->w_z_gps_v
* gpsRocScaler
* ctx
->dt
;
578 ctx
->newEPV
= updateEPE(posEstimator
.est
.epv
, ctx
->dt
, posEstimator
.baro
.epv
, positionEstimationConfig()->w_z_baro_p
);
580 // Accelerometer bias
581 if (!isAirCushionEffectDetected
) {
582 ctx
->accBiasCorr
.z
-= baroAltResidual
* sq(positionEstimationConfig()->w_z_baro_p
);
587 else if ((STATE(FIXED_WING_LEGACY
) || positionEstimationConfig()->use_gps_no_baro
) && (ctx
->newFlags
& EST_GPS_Z_VALID
)) {
588 // If baro is not available - use GPS Z for correction on a plane
589 // Reset current estimate to GPS altitude if estimate not valid
590 if (!(ctx
->newFlags
& EST_Z_VALID
)) {
591 ctx
->estPosCorr
.z
+= posEstimator
.gps
.pos
.z
- posEstimator
.est
.pos
.z
;
592 ctx
->estVelCorr
.z
+= posEstimator
.gps
.vel
.z
- posEstimator
.est
.vel
.z
;
593 ctx
->newEPV
= posEstimator
.gps
.epv
;
597 const float gpsAltResudual
= posEstimator
.gps
.pos
.z
- posEstimator
.est
.pos
.z
;
599 ctx
->estPosCorr
.z
+= gpsAltResudual
* positionEstimationConfig()->w_z_gps_p
* ctx
->dt
;
600 ctx
->estVelCorr
.z
+= gpsAltResudual
* sq(positionEstimationConfig()->w_z_gps_p
) * ctx
->dt
;
601 ctx
->estVelCorr
.z
+= (posEstimator
.gps
.vel
.z
- posEstimator
.est
.vel
.z
) * positionEstimationConfig()->w_z_gps_v
* ctx
->dt
;
602 ctx
->newEPV
= updateEPE(posEstimator
.est
.epv
, ctx
->dt
, MAX(posEstimator
.gps
.epv
, gpsAltResudual
), positionEstimationConfig()->w_z_gps_p
);
604 // Accelerometer bias
605 ctx
->accBiasCorr
.z
-= gpsAltResudual
* sq(positionEstimationConfig()->w_z_gps_p
);
611 DEBUG_SET(DEBUG_ALTITUDE
, 0, posEstimator
.est
.pos
.z
); // Position estimate
612 DEBUG_SET(DEBUG_ALTITUDE
, 2, posEstimator
.baro
.alt
); // Baro altitude
613 DEBUG_SET(DEBUG_ALTITUDE
, 4, posEstimator
.gps
.pos
.z
); // GPS altitude
614 DEBUG_SET(DEBUG_ALTITUDE
, 6, accGetVibrationLevel()); // Vibration level
615 DEBUG_SET(DEBUG_ALTITUDE
, 1, posEstimator
.est
.vel
.z
); // Vertical speed estimate
616 DEBUG_SET(DEBUG_ALTITUDE
, 3, posEstimator
.imu
.accelNEU
.z
); // Vertical acceleration on earth frame
617 DEBUG_SET(DEBUG_ALTITUDE
, 5, posEstimator
.gps
.vel
.z
); // GPS vertical speed
618 DEBUG_SET(DEBUG_ALTITUDE
, 7, accGetClipCount()); // Clip count
623 static bool estimationCalculateCorrection_XY_GPS(estimationContext_t
* ctx
)
625 if (ctx
->newFlags
& EST_GPS_XY_VALID
) {
626 /* If GPS is valid and our estimate is NOT valid - reset it to GPS coordinates and velocity */
627 if (!(ctx
->newFlags
& EST_XY_VALID
)) {
628 ctx
->estPosCorr
.x
+= posEstimator
.gps
.pos
.x
- posEstimator
.est
.pos
.x
;
629 ctx
->estPosCorr
.y
+= posEstimator
.gps
.pos
.y
- posEstimator
.est
.pos
.y
;
630 ctx
->estVelCorr
.x
+= posEstimator
.gps
.vel
.x
- posEstimator
.est
.vel
.x
;
631 ctx
->estVelCorr
.y
+= posEstimator
.gps
.vel
.y
- posEstimator
.est
.vel
.y
;
632 ctx
->newEPH
= posEstimator
.gps
.eph
;
635 const float gpsPosXResidual
= posEstimator
.gps
.pos
.x
- posEstimator
.est
.pos
.x
;
636 const float gpsPosYResidual
= posEstimator
.gps
.pos
.y
- posEstimator
.est
.pos
.y
;
637 const float gpsVelXResidual
= posEstimator
.gps
.vel
.x
- posEstimator
.est
.vel
.x
;
638 const float gpsVelYResidual
= posEstimator
.gps
.vel
.y
- posEstimator
.est
.vel
.y
;
639 const float gpsPosResidualMag
= fast_fsqrtf(sq(gpsPosXResidual
) + sq(gpsPosYResidual
));
641 //const float gpsWeightScaler = scaleRangef(bellCurve(gpsPosResidualMag, INAV_GPS_ACCEPTANCE_EPE), 0.0f, 1.0f, 0.1f, 1.0f);
642 const float gpsWeightScaler
= 1.0f
;
644 const float w_xy_gps_p
= positionEstimationConfig()->w_xy_gps_p
* gpsWeightScaler
;
645 const float w_xy_gps_v
= positionEstimationConfig()->w_xy_gps_v
* sq(gpsWeightScaler
);
648 ctx
->estPosCorr
.x
+= gpsPosXResidual
* w_xy_gps_p
* ctx
->dt
;
649 ctx
->estPosCorr
.y
+= gpsPosYResidual
* w_xy_gps_p
* ctx
->dt
;
651 // Velocity from coordinates
652 ctx
->estVelCorr
.x
+= gpsPosXResidual
* sq(w_xy_gps_p
) * ctx
->dt
;
653 ctx
->estVelCorr
.y
+= gpsPosYResidual
* sq(w_xy_gps_p
) * ctx
->dt
;
655 // Velocity from direct measurement
656 ctx
->estVelCorr
.x
+= gpsVelXResidual
* w_xy_gps_v
* ctx
->dt
;
657 ctx
->estVelCorr
.y
+= gpsVelYResidual
* w_xy_gps_v
* ctx
->dt
;
659 // Accelerometer bias
660 ctx
->accBiasCorr
.x
-= gpsPosXResidual
* sq(w_xy_gps_p
);
661 ctx
->accBiasCorr
.y
-= gpsPosYResidual
* sq(w_xy_gps_p
);
664 ctx
->newEPH
= updateEPE(posEstimator
.est
.eph
, ctx
->dt
, MAX(posEstimator
.gps
.eph
, gpsPosResidualMag
), w_xy_gps_p
);
674 * Calculate next estimate using IMU and apply corrections from reference sensors (GPS, BARO etc)
675 * Function is called at main loop rate
677 static void updateEstimatedTopic(timeUs_t currentTimeUs
)
679 estimationContext_t ctx
;
682 ctx
.dt
= US2S(currentTimeUs
- posEstimator
.est
.lastUpdateTime
);
683 posEstimator
.est
.lastUpdateTime
= currentTimeUs
;
685 /* If IMU is not ready we can't estimate anything */
687 posEstimator
.est
.eph
= positionEstimationConfig()->max_eph_epv
+ 0.001f
;
688 posEstimator
.est
.epv
= positionEstimationConfig()->max_eph_epv
+ 0.001f
;
689 posEstimator
.flags
= 0;
693 /* Calculate new EPH and EPV for the case we didn't update postion */
694 ctx
.newEPH
= posEstimator
.est
.eph
* ((posEstimator
.est
.eph
<= positionEstimationConfig()->max_eph_epv
) ? 1.0f
+ ctx
.dt
: 1.0f
);
695 ctx
.newEPV
= posEstimator
.est
.epv
* ((posEstimator
.est
.epv
<= positionEstimationConfig()->max_eph_epv
) ? 1.0f
+ ctx
.dt
: 1.0f
);
696 ctx
.newFlags
= calculateCurrentValidityFlags(currentTimeUs
);
697 vectorZero(&ctx
.estPosCorr
);
698 vectorZero(&ctx
.estVelCorr
);
699 vectorZero(&ctx
.accBiasCorr
);
701 /* AGL estimation - separate process, decouples from Z coordinate */
702 estimationCalculateAGL(&ctx
);
704 /* Prediction stage: X,Y,Z */
705 estimationPredict(&ctx
);
707 /* Correction stage: Z */
708 const bool estZCorrectOk
=
709 estimationCalculateCorrection_Z(&ctx
);
711 /* Correction stage: XY: GPS, FLOW */
712 // FIXME: Handle transition from FLOW to GPS and back - seamlessly fly indoor/outdoor
713 const bool estXYCorrectOk
=
714 estimationCalculateCorrection_XY_GPS(&ctx
) ||
715 estimationCalculateCorrection_XY_FLOW(&ctx
);
717 // If we can't apply correction or accuracy is off the charts - decay velocity to zero
718 if (!estXYCorrectOk
|| ctx
.newEPH
> positionEstimationConfig()->max_eph_epv
) {
719 ctx
.estVelCorr
.x
= (0.0f
- posEstimator
.est
.vel
.x
) * positionEstimationConfig()->w_xy_res_v
* ctx
.dt
;
720 ctx
.estVelCorr
.y
= (0.0f
- posEstimator
.est
.vel
.y
) * positionEstimationConfig()->w_xy_res_v
* ctx
.dt
;
723 if (!estZCorrectOk
|| ctx
.newEPV
> positionEstimationConfig()->max_eph_epv
) {
724 ctx
.estVelCorr
.z
= (0.0f
- posEstimator
.est
.vel
.z
) * positionEstimationConfig()->w_z_res_v
* ctx
.dt
;
728 vectorAdd(&posEstimator
.est
.pos
, &posEstimator
.est
.pos
, &ctx
.estPosCorr
);
729 vectorAdd(&posEstimator
.est
.vel
, &posEstimator
.est
.vel
, &ctx
.estVelCorr
);
731 /* Correct accelerometer bias */
732 if (positionEstimationConfig()->w_acc_bias
> 0.0f
) {
733 const float accelBiasCorrMagnitudeSq
= sq(ctx
.accBiasCorr
.x
) + sq(ctx
.accBiasCorr
.y
) + sq(ctx
.accBiasCorr
.z
);
734 if (accelBiasCorrMagnitudeSq
< sq(INAV_ACC_BIAS_ACCEPTANCE_VALUE
)) {
735 /* transform error vector from NEU frame to body frame */
736 imuTransformVectorEarthToBody(&ctx
.accBiasCorr
);
738 /* Correct accel bias */
739 posEstimator
.imu
.accelBias
.x
+= ctx
.accBiasCorr
.x
* positionEstimationConfig()->w_acc_bias
* ctx
.dt
;
740 posEstimator
.imu
.accelBias
.y
+= ctx
.accBiasCorr
.y
* positionEstimationConfig()->w_acc_bias
* ctx
.dt
;
741 posEstimator
.imu
.accelBias
.z
+= ctx
.accBiasCorr
.z
* positionEstimationConfig()->w_acc_bias
* ctx
.dt
;
745 /* Update uncertainty */
746 posEstimator
.est
.eph
= ctx
.newEPH
;
747 posEstimator
.est
.epv
= ctx
.newEPV
;
749 // Keep flags for further usage
750 posEstimator
.flags
= ctx
.newFlags
;
754 * Examine estimation error and update navigation system if estimate is good enough
755 * Function is called at main loop rate, but updates happen less frequently - at a fixed rate
757 static void publishEstimatedTopic(timeUs_t currentTimeUs
)
759 static navigationTimer_t posPublishTimer
;
761 /* IMU operates in decidegrees while INAV operates in deg*100 */
762 updateActualHeading(navIsHeadingUsable(), DECIDEGREES_TO_CENTIDEGREES(attitude
.values
.yaw
));
764 /* Position and velocity are published with INAV_POSITION_PUBLISH_RATE_HZ */
765 if (updateTimer(&posPublishTimer
, HZ2US(INAV_POSITION_PUBLISH_RATE_HZ
), currentTimeUs
)) {
766 /* Publish position update */
767 if (posEstimator
.est
.eph
< positionEstimationConfig()->max_eph_epv
) {
769 updateActualHorizontalPositionAndVelocity(true, true, posEstimator
.est
.pos
.x
, posEstimator
.est
.pos
.y
, posEstimator
.est
.vel
.x
, posEstimator
.est
.vel
.y
);
772 updateActualHorizontalPositionAndVelocity(false, false, posEstimator
.est
.pos
.x
, posEstimator
.est
.pos
.y
, 0, 0);
775 /* Publish altitude update and set altitude validity */
776 if (posEstimator
.est
.epv
< positionEstimationConfig()->max_eph_epv
) {
777 navigationEstimateStatus_e aglStatus
= (posEstimator
.est
.aglQual
== SURFACE_QUAL_LOW
) ? EST_USABLE
: EST_TRUSTED
;
778 updateActualAltitudeAndClimbRate(true, posEstimator
.est
.pos
.z
, posEstimator
.est
.vel
.z
, posEstimator
.est
.aglAlt
, posEstimator
.est
.aglVel
, aglStatus
);
781 updateActualAltitudeAndClimbRate(false, posEstimator
.est
.pos
.z
, 0, posEstimator
.est
.aglAlt
, 0, EST_NONE
);
784 //Update Blackbox states
785 navEPH
= posEstimator
.est
.eph
;
786 navEPV
= posEstimator
.est
.epv
;
790 #if defined(NAV_GPS_GLITCH_DETECTION)
791 bool isGPSGlitchDetected(void)
793 return posEstimator
.gps
.glitchDetected
;
798 * Initialize position estimator
799 * Should be called once before any update occurs
801 void initializePositionEstimator(void)
805 posEstimator
.est
.eph
= positionEstimationConfig()->max_eph_epv
+ 0.001f
;
806 posEstimator
.est
.epv
= positionEstimationConfig()->max_eph_epv
+ 0.001f
;
808 posEstimator
.imu
.lastUpdateTime
= 0;
809 posEstimator
.gps
.lastUpdateTime
= 0;
810 posEstimator
.baro
.lastUpdateTime
= 0;
811 posEstimator
.surface
.lastUpdateTime
= 0;
813 posEstimator
.est
.aglAlt
= 0;
814 posEstimator
.est
.aglVel
= 0;
816 posEstimator
.est
.flowCoordinates
[X
] = 0;
817 posEstimator
.est
.flowCoordinates
[Y
] = 0;
819 posEstimator
.imu
.accWeightFactor
= 0;
821 restartGravityCalibration();
823 for (axis
= 0; axis
< 3; axis
++) {
824 posEstimator
.imu
.accelBias
.v
[axis
] = 0;
825 posEstimator
.est
.pos
.v
[axis
] = 0;
826 posEstimator
.est
.vel
.v
[axis
] = 0;
829 pt1FilterInit(&posEstimator
.baro
.avgFilter
, INAV_BARO_AVERAGE_HZ
, 0.0f
);
830 pt1FilterInit(&posEstimator
.surface
.avgFilter
, INAV_SURFACE_AVERAGE_HZ
, 0.0f
);
835 * Update rate: loop rate (>100Hz)
837 void updatePositionEstimator(void)
839 static bool isInitialized
= false;
841 if (!isInitialized
) {
842 initializePositionEstimator();
843 isInitialized
= true;
846 const timeUs_t currentTimeUs
= micros();
848 /* Read updates from IMU, preprocess */
849 updateIMUTopic(currentTimeUs
);
851 /* Update estimate */
852 updateEstimatedTopic(currentTimeUs
);
854 /* Publish estimate */
855 publishEstimatedTopic(currentTimeUs
);
858 bool navIsCalibrationComplete(void)
860 return gravityCalibrationComplete();