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/>.
18 // Inertial Measurement Unit (IMU)
26 #include "blackbox/blackbox.h"
28 #include "build/build_config.h"
29 #include "build/debug.h"
31 #include "common/axis.h"
32 #include "common/filter.h"
33 #include "common/log.h"
34 #include "common/maths.h"
35 #include "common/vector.h"
36 #include "common/quaternion.h"
37 #include "common/time.h"
40 #include "config/feature.h"
41 #include "config/parameter_group.h"
42 #include "config/parameter_group_ids.h"
44 #include "drivers/time.h"
46 #include "fc/config.h"
47 #include "fc/runtime_config.h"
48 #include "fc/settings.h"
50 #include "flight/imu.h"
51 #include "flight/mixer.h"
52 #include "flight/mixer_profile.h"
53 #include "flight/pid.h"
54 #if defined(USE_WIND_ESTIMATOR)
55 #include "flight/wind_estimator.h"
60 #include "sensors/acceleration.h"
61 #include "sensors/barometer.h"
62 #include "sensors/pitotmeter.h"
63 #include "sensors/compass.h"
64 #include "sensors/gyro.h"
65 #include "sensors/sensors.h"
69 * X-axis = North/Forward
74 // the limit (in degrees/second) beyond which we stop integrating
75 // omega_I. At larger spin rates the DCM PI controller can get 'dizzy'
76 // which results in false gyro drift. See
77 // http://gentlenav.googlecode.com/files/fastRotations.pdf
79 #define SPIN_RATE_LIMIT 20
80 #define MAX_ACC_NEARNESS 0.2 // 20% or G error soft-accepted (0.8-1.2G)
81 #define MAX_MAG_NEARNESS 0.25 // 25% or magnetic field error soft-accepted (0.75-1.25)
82 #define COS10DEG 0.985f
83 #define COS20DEG 0.940f
84 #define IMU_ROTATION_LPF 3 // Hz
85 FASTRAM fpVector3_t imuMeasuredAccelBF
;
86 FASTRAM fpVector3_t imuMeasuredRotationBF
;
87 //centrifugal force compensated using gps
88 FASTRAM fpVector3_t compansatedGravityBF
;// cm/s/s
90 STATIC_FASTRAM
float smallAngleCosZ
;
92 STATIC_FASTRAM
bool isAccelUpdatedAtLeastOnce
;
93 STATIC_FASTRAM fpVector3_t vCorrectedMagNorth
; // Magnetic North vector in EF (true North rotated by declination)
95 FASTRAM fpQuaternion_t orientation
;
96 FASTRAM attitudeEulerAngles_t attitude
; // absolute angle inclination in multiple of 0.1 degree 180 deg = 1800
97 FASTRAM
float rMat
[3][3];
99 STATIC_FASTRAM imuRuntimeConfig_t imuRuntimeConfig
;
101 STATIC_FASTRAM pt1Filter_t rotRateFilterX
;
102 STATIC_FASTRAM pt1Filter_t rotRateFilterY
;
103 STATIC_FASTRAM pt1Filter_t rotRateFilterZ
;
104 FASTRAM fpVector3_t imuMeasuredRotationBFFiltered
= {.v
= {0.0f
, 0.0f
, 0.0f
}};
106 STATIC_FASTRAM pt1Filter_t HeadVecEFFilterX
;
107 STATIC_FASTRAM pt1Filter_t HeadVecEFFilterY
;
108 STATIC_FASTRAM pt1Filter_t HeadVecEFFilterZ
;
109 FASTRAM fpVector3_t HeadVecEFFiltered
= {.v
= {0.0f
, 0.0f
, 0.0f
}};
111 STATIC_FASTRAM
float GPS3DspeedFiltered
=0.0f
;
112 STATIC_FASTRAM pt1Filter_t GPS3DspeedFilter
;
114 FASTRAM
bool gpsHeadingInitialized
;
116 FASTRAM
bool imuUpdated
= false;
118 static float imuCalculateAccelerometerWeightNearness(fpVector3_t
* accBF
);
120 PG_REGISTER_WITH_RESET_TEMPLATE(imuConfig_t
, imuConfig
, PG_IMU_CONFIG
, 2);
122 PG_RESET_TEMPLATE(imuConfig_t
, imuConfig
,
123 .dcm_kp_acc
= SETTING_AHRS_DCM_KP_DEFAULT
, // 0.20 * 10000
124 .dcm_ki_acc
= SETTING_AHRS_DCM_KI_DEFAULT
, // 0.005 * 10000
125 .dcm_kp_mag
= SETTING_AHRS_DCM_KP_MAG_DEFAULT
, // 0.20 * 10000
126 .dcm_ki_mag
= SETTING_AHRS_DCM_KI_MAG_DEFAULT
, // 0.005 * 10000
127 .small_angle
= SETTING_SMALL_ANGLE_DEFAULT
,
128 .acc_ignore_rate
= SETTING_AHRS_ACC_IGNORE_RATE_DEFAULT
,
129 .acc_ignore_slope
= SETTING_AHRS_ACC_IGNORE_SLOPE_DEFAULT
,
130 .gps_yaw_windcomp
= SETTING_AHRS_GPS_YAW_WINDCOMP_DEFAULT
,
131 .inertia_comp_method
= SETTING_AHRS_INERTIA_COMP_METHOD_DEFAULT
,
132 .gps_yaw_weight
= SETTING_AHRS_GPS_YAW_WEIGHT_DEFAULT
135 STATIC_UNIT_TESTED
void imuComputeRotationMatrix(void)
137 float q1q1
= orientation
.q1
* orientation
.q1
;
138 float q2q2
= orientation
.q2
* orientation
.q2
;
139 float q3q3
= orientation
.q3
* orientation
.q3
;
141 float q0q1
= orientation
.q0
* orientation
.q1
;
142 float q0q2
= orientation
.q0
* orientation
.q2
;
143 float q0q3
= orientation
.q0
* orientation
.q3
;
144 float q1q2
= orientation
.q1
* orientation
.q2
;
145 float q1q3
= orientation
.q1
* orientation
.q3
;
146 float q2q3
= orientation
.q2
* orientation
.q3
;
148 rMat
[0][0] = 1.0f
- 2.0f
* q2q2
- 2.0f
* q3q3
;
149 rMat
[0][1] = 2.0f
* (q1q2
+ -q0q3
);
150 rMat
[0][2] = 2.0f
* (q1q3
- -q0q2
);
152 rMat
[1][0] = 2.0f
* (q1q2
- -q0q3
);
153 rMat
[1][1] = 1.0f
- 2.0f
* q1q1
- 2.0f
* q3q3
;
154 rMat
[1][2] = 2.0f
* (q2q3
+ -q0q1
);
156 rMat
[2][0] = 2.0f
* (q1q3
+ -q0q2
);
157 rMat
[2][1] = 2.0f
* (q2q3
- -q0q1
);
158 rMat
[2][2] = 1.0f
- 2.0f
* q1q1
- 2.0f
* q2q2
;
161 void imuConfigure(void)
163 imuRuntimeConfig
.dcm_kp_acc
= imuConfig()->dcm_kp_acc
/ 10000.0f
;
164 imuRuntimeConfig
.dcm_ki_acc
= imuConfig()->dcm_ki_acc
/ 10000.0f
;
165 imuRuntimeConfig
.dcm_kp_mag
= imuConfig()->dcm_kp_mag
/ 10000.0f
;
166 imuRuntimeConfig
.dcm_ki_mag
= imuConfig()->dcm_ki_mag
/ 10000.0f
;
167 imuRuntimeConfig
.small_angle
= imuConfig()->small_angle
;
172 smallAngleCosZ
= cos_approx(degreesToRadians(imuRuntimeConfig
.small_angle
));
174 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
175 imuMeasuredAccelBF
.v
[axis
] = 0;
178 // Explicitly initialize FASTRAM statics
179 isAccelUpdatedAtLeastOnce
= false;
180 gpsHeadingInitialized
= false;
182 // Create magnetic declination matrix
184 const int deg
= compassConfig()->mag_declination
/ 100;
185 const int min
= compassConfig()->mag_declination
% 100;
190 imuSetMagneticDeclination(deg
+ min
/ 60.0f
);
192 quaternionInitUnit(&orientation
);
193 imuComputeRotationMatrix();
195 // Initialize rotation rate filter
196 pt1FilterReset(&rotRateFilterX
, 0);
197 pt1FilterReset(&rotRateFilterY
, 0);
198 pt1FilterReset(&rotRateFilterZ
, 0);
199 // Initialize Heading vector filter
200 pt1FilterReset(&HeadVecEFFilterX
, 0);
201 pt1FilterReset(&HeadVecEFFilterY
, 0);
202 pt1FilterReset(&HeadVecEFFilterZ
, 0);
203 // Initialize 3d speed filter
204 pt1FilterReset(&GPS3DspeedFilter
, 0);
207 void imuSetMagneticDeclination(float declinationDeg
)
209 const float declinationRad
= -DEGREES_TO_RADIANS(declinationDeg
);
210 vCorrectedMagNorth
.x
= cos_approx(declinationRad
);
211 vCorrectedMagNorth
.y
= sin_approx(declinationRad
);
212 vCorrectedMagNorth
.z
= 0;
215 void imuTransformVectorBodyToEarth(fpVector3_t
* v
)
217 // From body frame to earth frame
218 quaternionRotateVectorInv(v
, v
, &orientation
);
220 // HACK: This is needed to correctly transform from NED (sensor frame) to NEU (navigation)
224 void imuTransformVectorEarthToBody(fpVector3_t
* v
)
226 // HACK: This is needed to correctly transform from NED (sensor frame) to NEU (navigation)
229 // From earth frame to body frame
230 quaternionRotateVector(v
, v
, &orientation
);
234 STATIC_UNIT_TESTED
void imuComputeQuaternionFromRPY(int16_t initialRoll
, int16_t initialPitch
, int16_t initialYaw
)
236 if (initialRoll
> 1800) initialRoll
-= 3600;
237 if (initialPitch
> 1800) initialPitch
-= 3600;
238 if (initialYaw
> 1800) initialYaw
-= 3600;
240 const float cosRoll
= cos_approx(DECIDEGREES_TO_RADIANS(initialRoll
) * 0.5f
);
241 const float sinRoll
= sin_approx(DECIDEGREES_TO_RADIANS(initialRoll
) * 0.5f
);
243 const float cosPitch
= cos_approx(DECIDEGREES_TO_RADIANS(initialPitch
) * 0.5f
);
244 const float sinPitch
= sin_approx(DECIDEGREES_TO_RADIANS(initialPitch
) * 0.5f
);
246 const float cosYaw
= cos_approx(DECIDEGREES_TO_RADIANS(-initialYaw
) * 0.5f
);
247 const float sinYaw
= sin_approx(DECIDEGREES_TO_RADIANS(-initialYaw
) * 0.5f
);
249 orientation
.q0
= cosRoll
* cosPitch
* cosYaw
+ sinRoll
* sinPitch
* sinYaw
;
250 orientation
.q1
= sinRoll
* cosPitch
* cosYaw
- cosRoll
* sinPitch
* sinYaw
;
251 orientation
.q2
= cosRoll
* sinPitch
* cosYaw
+ sinRoll
* cosPitch
* sinYaw
;
252 orientation
.q3
= cosRoll
* cosPitch
* sinYaw
- sinRoll
* sinPitch
* cosYaw
;
254 imuComputeRotationMatrix();
258 static bool imuUseFastGains(void)
260 return !ARMING_FLAG(ARMED
) && millis() < 20000;
263 static float imuGetPGainScaleFactor(void)
265 if (imuUseFastGains()) {
273 static void imuResetOrientationQuaternion(const fpVector3_t
* accBF
)
275 const float accNorm
= fast_fsqrtf(vectorNormSquared(accBF
));
277 orientation
.q0
= accBF
->z
+ accNorm
;
278 orientation
.q1
= accBF
->y
;
279 orientation
.q2
= -accBF
->x
;
280 orientation
.q3
= 0.0f
;
282 quaternionNormalize(&orientation
, &orientation
);
285 static bool imuValidateQuaternion(const fpQuaternion_t
* quat
)
287 const float check
= fabsf(quat
->q0
) + fabsf(quat
->q1
) + fabsf(quat
->q2
) + fabsf(quat
->q3
);
289 if (!isnan(check
) && !isinf(check
)) {
293 const float normSq
= quaternionNormSqared(&orientation
);
294 if (normSq
> (1.0f
- 1e-6f
) && normSq
< (1.0f
+ 1e-6f
)) {
301 static void imuCheckAndResetOrientationQuaternion(const fpQuaternion_t
* quat
, const fpVector3_t
* accBF
)
303 // Check if some calculation in IMU update yield NAN or zero quaternion
304 if (imuValidateQuaternion(&orientation
)) {
308 flightLogEvent_IMUError_t imuErrorEvent
;
310 // Orientation is invalid. We need to reset it
311 if (imuValidateQuaternion(quat
)) {
312 // Previous quaternion valid. Reset to it
314 imuErrorEvent
.errorCode
= 1;
315 LOG_ERROR(IMU
, "AHRS orientation quaternion error. Reset to last known good value");
318 // No valid reference. Best guess from accelerometer
319 imuResetOrientationQuaternion(accBF
);
320 imuErrorEvent
.errorCode
= 2;
321 LOG_ERROR(IMU
, "AHRS orientation quaternion error. Best guess from ACC");
325 if (feature(FEATURE_BLACKBOX
)) {
326 blackboxLogEvent(FLIGHT_LOG_EVENT_IMU_FAILURE
, (flightLogEventData_t
*)&imuErrorEvent
);
331 bool isGPSTrustworthy(void)
333 return (sensors(SENSOR_GPS
) && STATE(GPS_FIX
) && gpsSol
.numSat
>= 6)
334 #ifdef USE_GPS_FIX_ESTIMATION
335 || STATE(GPS_ESTIMATED_FIX
)
340 static float imuCalculateMcCogWeight(void)
342 float wCoG
= imuCalculateAccelerometerWeightNearness(&imuMeasuredAccelBF
);
343 float rotRateMagnitude
= fast_fsqrtf(vectorNormSquared(&imuMeasuredRotationBFFiltered
));
344 const float rateSlopeMax
= DEGREES_TO_RADIANS((imuConfig()->acc_ignore_rate
)) * 4.0f
;
345 wCoG
*= scaleRangef(constrainf(rotRateMagnitude
, 0.0f
, rateSlopeMax
), 0.0f
, rateSlopeMax
, 1.0f
, 0.0f
);
349 static void imuMahonyAHRSupdate(float dt
, const fpVector3_t
* gyroBF
, const fpVector3_t
* accBF
, const fpVector3_t
* magBF
, bool useCOG
, float courseOverGround
, float accWScaler
, float magWScaler
)
351 STATIC_FASTRAM fpVector3_t vGyroDriftEstimate
= { 0 };
353 fpQuaternion_t prevOrientation
= orientation
;
354 fpVector3_t vRotation
= *gyroBF
;
356 /* Calculate general spin rate (rad/s) */
357 const float spin_rate_sq
= vectorNormSquared(&vRotation
);
359 /* Step 1: Yaw correction */
360 // Use measured magnetic field vector
361 if (magBF
|| useCOG
) {
364 if(magBF
){wCoG
*= imuConfig()->gps_yaw_weight
/ 100.0f
;}
366 fpVector3_t vMagErr
= { .v
= { 0.0f
, 0.0f
, 0.0f
} };
367 fpVector3_t vCoGErr
= { .v
= { 0.0f
, 0.0f
, 0.0f
} };
369 if (magBF
&& vectorNormSquared(magBF
) > 0.01f
) {
370 wMag
*= bellCurve((fast_fsqrtf(vectorNormSquared(magBF
)) - 1024.0f
) / 1024.0f
, MAX_MAG_NEARNESS
);
373 // For magnetometer correction we make an assumption that magnetic field is perpendicular to gravity (ignore Z-component in EF).
374 // This way magnetic field will only affect heading and wont mess roll/pitch angles
376 // (hx; hy; 0) - measured mag field vector in EF (assuming Z-component is zero)
377 // This should yield direction to magnetic North (1; 0; 0)
378 quaternionRotateVectorInv(&vMag
, magBF
, &orientation
); // BF -> EF
380 // Ignore magnetic inclination
383 // We zeroed out vMag.z - make sure the whole vector didn't go to zero
384 if (vectorNormSquared(&vMag
) > 0.01f
) {
385 // Normalize to unit vector
386 vectorNormalize(&vMag
, &vMag
);
389 if (ARMING_FLAG(SIMULATOR_MODE_HITL
) || ARMING_FLAG(SIMULATOR_MODE_SITL
)) {
390 imuSetMagneticDeclination(0);
394 // Reference mag field vector heading is Magnetic North in EF. We compute that by rotating True North vector by declination and assuming Z-component is zero
395 // magnetometer error is cross product between estimated magnetic north and measured magnetic north (calculated in EF)
396 vectorCrossProduct(&vMagErr
, &vMag
, &vCorrectedMagNorth
);
398 // Rotate error back into body frame
399 quaternionRotateVector(&vMagErr
, &vMagErr
, &orientation
);
403 fpVector3_t vForward
= { .v
= { 0.0f
, 0.0f
, 0.0f
} };
404 //vForward as trust vector
405 if (STATE(MULTIROTOR
) && (!isMixerTransitionMixing
)){
410 fpVector3_t vHeadingEF
;
412 // Use raw heading error (from GPS or whatever else)
413 while (courseOverGround
> M_PIf
) courseOverGround
-= (2.0f
* M_PIf
);
414 while (courseOverGround
< -M_PIf
) courseOverGround
+= (2.0f
* M_PIf
);
416 // William Premerlani and Paul Bizard, Direction Cosine Matrix IMU - Eqn. 22-23
417 // (Rxx; Ryx) - measured (estimated) heading vector (EF)
418 // (-cos(COG), sin(COG)) - reference heading vector (EF)
420 float airSpeed
= gpsSol
.groundSpeed
;
421 // Compute heading vector in EF from scalar CoG,x axis of accelerometer is pointing backwards.
422 fpVector3_t vCoG
= { .v
= { -cos_approx(courseOverGround
), sin_approx(courseOverGround
), 0.0f
} };
423 #if defined(USE_WIND_ESTIMATOR)
424 // remove wind elements in vCoG for better heading estimation
425 if (isEstimatedWindSpeedValid() && imuConfig()->gps_yaw_windcomp
)
427 vectorScale(&vCoG
, &vCoG
, gpsSol
.groundSpeed
);
428 vCoG
.x
+= getEstimatedWindSpeed(X
);
429 vCoG
.y
-= getEstimatedWindSpeed(Y
);
430 airSpeed
= fast_fsqrtf(vectorNormSquared(&vCoG
));
431 vectorNormalize(&vCoG
, &vCoG
);
434 wCoG
*= scaleRangef(constrainf((airSpeed
+gpsSol
.groundSpeed
)/2, 400, 1000), 400, 1000, 0.0f
, 1.0f
);
435 // Rotate Forward vector from BF to EF - will yield Heading vector in Earth frame
436 quaternionRotateVectorInv(&vHeadingEF
, &vForward
, &orientation
);
438 if (STATE(MULTIROTOR
)){
439 //when multicopter`s orientation or speed is changing rapidly. less weight on gps heading
440 wCoG
*= imuCalculateMcCogWeight();
441 //scale accroading to multirotor`s tilt angle
442 wCoG
*= scaleRangef(constrainf(vHeadingEF
.z
, COS20DEG
, COS10DEG
), COS20DEG
, COS10DEG
, 1.0f
, 0.0f
);
443 //for inverted flying, wCoG is lowered by imuCalculateMcCogWeight no additional processing needed
447 // We zeroed out vHeadingEF.z - make sure the whole vector didn't go to zero
448 if (vectorNormSquared(&vHeadingEF
) > 0.01f
) {
449 // Normalize to unit vector
450 vectorNormalize(&vHeadingEF
, &vHeadingEF
);
452 // error is cross product between reference heading and estimated heading (calculated in EF)
453 vectorCrossProduct(&vCoGErr
, &vCoG
, &vHeadingEF
);
455 // Rotate error back into body frame
456 quaternionRotateVector(&vCoGErr
, &vCoGErr
, &orientation
);
459 fpVector3_t vErr
= { .v
= { 0.0f
, 0.0f
, 0.0f
} };
460 vectorScale(&vMagErr
, &vMagErr
, wMag
);
461 vectorScale(&vCoGErr
, &vCoGErr
, wCoG
);
462 vectorAdd(&vErr
, &vMagErr
, &vCoGErr
);
463 // Compute and apply integral feedback if enabled
464 if (imuRuntimeConfig
.dcm_ki_mag
> 0.0f
) {
465 // Stop integrating if spinning beyond the certain limit
466 if (spin_rate_sq
< sq(DEGREES_TO_RADIANS(SPIN_RATE_LIMIT
))) {
469 // integral error scaled by Ki
470 vectorScale(&vTmp
, &vErr
, imuRuntimeConfig
.dcm_ki_mag
* magWScaler
* dt
);
471 vectorAdd(&vGyroDriftEstimate
, &vGyroDriftEstimate
, &vTmp
);
475 // Calculate kP gain and apply proportional feedback
476 vectorScale(&vErr
, &vErr
, imuRuntimeConfig
.dcm_kp_mag
* magWScaler
);
477 vectorAdd(&vRotation
, &vRotation
, &vErr
);
481 /* Step 2: Roll and pitch correction - use measured acceleration vector */
483 static const fpVector3_t vGravity
= { .v
= { 0.0f
, 0.0f
, 1.0f
} };
484 fpVector3_t vEstGravity
, vAcc
, vErr
;
486 // Calculate estimated gravity vector in body frame
487 quaternionRotateVector(&vEstGravity
, &vGravity
, &orientation
); // EF -> BF
489 // Error is sum of cross product between estimated direction and measured direction of gravity
490 vectorNormalize(&vAcc
, accBF
);
491 vectorCrossProduct(&vErr
, &vAcc
, &vEstGravity
);
493 // Compute and apply integral feedback if enabled
494 if (imuRuntimeConfig
.dcm_ki_acc
> 0.0f
) {
495 // Stop integrating if spinning beyond the certain limit
496 if (spin_rate_sq
< sq(DEGREES_TO_RADIANS(SPIN_RATE_LIMIT
))) {
499 // integral error scaled by Ki
500 vectorScale(&vTmp
, &vErr
, imuRuntimeConfig
.dcm_ki_acc
* accWScaler
* dt
);
501 vectorAdd(&vGyroDriftEstimate
, &vGyroDriftEstimate
, &vTmp
);
505 // Calculate kP gain and apply proportional feedback
506 vectorScale(&vErr
, &vErr
, imuRuntimeConfig
.dcm_kp_acc
* accWScaler
);
507 vectorAdd(&vRotation
, &vRotation
, &vErr
);
510 float i_limit
= DEGREES_TO_RADIANS(2.0f
) * (imuRuntimeConfig
.dcm_kp_acc
+ imuRuntimeConfig
.dcm_kp_mag
) / 2.0f
;
511 vGyroDriftEstimate
.x
= constrainf(vGyroDriftEstimate
.x
, -i_limit
, i_limit
);
512 vGyroDriftEstimate
.y
= constrainf(vGyroDriftEstimate
.y
, -i_limit
, i_limit
);
513 vGyroDriftEstimate
.z
= constrainf(vGyroDriftEstimate
.z
, -i_limit
, i_limit
);
515 // Apply gyro drift correction
516 vectorAdd(&vRotation
, &vRotation
, &vGyroDriftEstimate
);
518 // Integrate rate of change of quaternion
520 fpQuaternion_t deltaQ
;
522 vectorScale(&vTheta
, &vRotation
, 0.5f
* dt
);
523 quaternionInitFromVector(&deltaQ
, &vTheta
);
524 const float thetaMagnitudeSq
= vectorNormSquared(&vTheta
);
526 // If calculated rotation is zero - don't update quaternion
527 if (thetaMagnitudeSq
>= 1e-20f
) {
528 // Calculate quaternion delta:
529 // Theta is a axis/angle rotation. Direction of a vector is axis, magnitude is angle/2.
530 // Proper quaternion from axis/angle involves computing sin/cos, but the formula becomes numerically unstable as Theta approaches zero.
531 // For near-zero cases we use the first 3 terms of the Taylor series expansion for sin/cos. We check if fourth term is less than machine precision -
532 // then we can safely use the "low angle" approximated version without loss of accuracy.
533 if (thetaMagnitudeSq
< fast_fsqrtf(24.0f
* 1e-6f
)) {
534 quaternionScale(&deltaQ
, &deltaQ
, 1.0f
- thetaMagnitudeSq
/ 6.0f
);
535 deltaQ
.q0
= 1.0f
- thetaMagnitudeSq
/ 2.0f
;
538 const float thetaMagnitude
= fast_fsqrtf(thetaMagnitudeSq
);
539 quaternionScale(&deltaQ
, &deltaQ
, sin_approx(thetaMagnitude
) / thetaMagnitude
);
540 deltaQ
.q0
= cos_approx(thetaMagnitude
);
543 // Calculate final orientation and renormalize
544 quaternionMultiply(&orientation
, &orientation
, &deltaQ
);
545 quaternionNormalize(&orientation
, &orientation
);
548 // Check for invalid quaternion and reset to previous known good one
549 imuCheckAndResetOrientationQuaternion(&prevOrientation
, accBF
);
551 // Pre-compute rotation matrix from quaternion
552 imuComputeRotationMatrix();
555 STATIC_UNIT_TESTED
void imuUpdateEulerAngles(void)
558 if ((ARMING_FLAG(SIMULATOR_MODE_HITL
) && !SIMULATOR_HAS_OPTION(HITL_USE_IMU
)) || (ARMING_FLAG(SIMULATOR_MODE_SITL
) && imuUpdated
)) {
559 imuComputeQuaternionFromRPY(attitude
.values
.roll
, attitude
.values
.pitch
, attitude
.values
.yaw
);
560 imuComputeRotationMatrix();
565 /* Compute pitch/roll angles */
566 attitude
.values
.roll
= RADIANS_TO_DECIDEGREES(atan2_approx(rMat
[2][1], rMat
[2][2]));
567 attitude
.values
.pitch
= RADIANS_TO_DECIDEGREES((0.5f
* M_PIf
) - acos_approx(-rMat
[2][0]));
568 attitude
.values
.yaw
= RADIANS_TO_DECIDEGREES(-atan2_approx(rMat
[1][0], rMat
[0][0]));
571 if (attitude
.values
.yaw
< 0)
572 attitude
.values
.yaw
+= 3600;
574 /* Update small angle state */
575 if (calculateCosTiltAngle() > smallAngleCosZ
) {
576 ENABLE_STATE(SMALL_ANGLE
);
578 DISABLE_STATE(SMALL_ANGLE
);
582 static float imuCalculateAccelerometerWeightNearness(fpVector3_t
* accBF
)
584 fpVector3_t accBFNorm
;
585 vectorScale(&accBFNorm
, accBF
, 1.0f
/ GRAVITY_CMSS
);
586 const float accMagnitudeSq
= vectorNormSquared(&accBFNorm
);
587 const float accWeight_Nearness
= bellCurve(fast_fsqrtf(accMagnitudeSq
) - 1.0f
, MAX_ACC_NEARNESS
);
588 return accWeight_Nearness
;
591 static float imuCalculateAccelerometerWeightRateIgnore(const float acc_ignore_slope_multipiler
)
593 // Experiment: if rotation rate on a FIXED_WING_LEGACY is higher than a threshold - centrifugal force messes up too much and we
594 // should not use measured accel for AHRS comp
595 // Centrifugal acceleration AccelC = Omega^2 * R = Speed^2 / R
597 // For a banked turn R = Speed^2 / (G * tan(Roll))
598 // Omega = G * tan(Roll) / Speed
599 // Knowing the typical airspeed is around ~20 m/s we can calculate roll angles that yield certain angular rate
600 // 1 deg => 0.49 deg/s
601 // 2 deg => 0.98 deg/s
602 // 5 deg => 2.45 deg/s
603 // 10 deg => 4.96 deg/s
604 // Therefore for a typical plane a sustained angular rate of ~2.45 deg/s will yield a banking error of ~5 deg
605 // Since we can't do proper centrifugal compensation at the moment we pass the magnitude of angular rate through an
606 // LPF with a low cutoff and if it's larger than our threshold - invalidate accelerometer
608 // Default - don't apply rate/ignore scaling
609 float accWeight_RateIgnore
= 1.0f
;
611 if (ARMING_FLAG(ARMED
) && imuConfig()->acc_ignore_rate
)
613 float rotRateMagnitude
= fast_fsqrtf(vectorNormSquared(&imuMeasuredRotationBFFiltered
));
614 rotRateMagnitude
= rotRateMagnitude
/ (acc_ignore_slope_multipiler
+ 0.001f
);
615 if (imuConfig()->acc_ignore_slope
)
617 const float rateSlopeMin
= DEGREES_TO_RADIANS((imuConfig()->acc_ignore_rate
- imuConfig()->acc_ignore_slope
));
618 const float rateSlopeMax
= DEGREES_TO_RADIANS((imuConfig()->acc_ignore_rate
+ imuConfig()->acc_ignore_slope
));
620 accWeight_RateIgnore
= scaleRangef(constrainf(rotRateMagnitude
, rateSlopeMin
, rateSlopeMax
), rateSlopeMin
, rateSlopeMax
, 1.0f
, 0.0f
);
624 if (rotRateMagnitude
> DEGREES_TO_RADIANS(imuConfig()->acc_ignore_rate
))
626 accWeight_RateIgnore
= 0.0f
;
631 return accWeight_RateIgnore
;
634 static void imuCalculateFilters(float dT
)
637 imuMeasuredRotationBFFiltered
.x
= pt1FilterApply4(&rotRateFilterX
, imuMeasuredRotationBF
.x
, IMU_ROTATION_LPF
, dT
);
638 imuMeasuredRotationBFFiltered
.y
= pt1FilterApply4(&rotRateFilterY
, imuMeasuredRotationBF
.y
, IMU_ROTATION_LPF
, dT
);
639 imuMeasuredRotationBFFiltered
.z
= pt1FilterApply4(&rotRateFilterZ
, imuMeasuredRotationBF
.z
, IMU_ROTATION_LPF
, dT
);
640 HeadVecEFFiltered
.x
= pt1FilterApply4(&HeadVecEFFilterX
, rMat
[0][0], IMU_ROTATION_LPF
, dT
);
641 HeadVecEFFiltered
.y
= pt1FilterApply4(&HeadVecEFFilterY
, rMat
[1][0], IMU_ROTATION_LPF
, dT
);
642 HeadVecEFFiltered
.z
= pt1FilterApply4(&HeadVecEFFilterZ
, rMat
[2][0], IMU_ROTATION_LPF
, dT
);
645 float GPS3Dspeed
= calc_length_pythagorean_3D(gpsSol
.velNED
[X
],gpsSol
.velNED
[Y
],gpsSol
.velNED
[Z
]);
646 GPS3DspeedFiltered
= pt1FilterApply4(&GPS3DspeedFilter
, GPS3Dspeed
, IMU_ROTATION_LPF
, dT
);
649 static void imuCalculateGPSacceleration(fpVector3_t
*vEstcentrifugalAccelBF
, float *acc_ignore_slope_multipiler
)
651 static rtcTime_t lastGPSNewDataTime
= 0;
652 static bool lastGPSHeartbeat
;
653 static fpVector3_t lastGPSvel
;
655 const fpVector3_t currentGPSvel
= {.v
= {gpsSol
.velNED
[X
], gpsSol
.velNED
[Y
], gpsSol
.velNED
[Z
]}}; // cm/s gps speed
656 const rtcTime_t currenttime
= millis();
658 // on first gps data acquired, time_delta_ms will be large, vEstcentrifugalAccelBF will be minimal to disable the compensation
659 rtcTime_t time_delta_ms
= currenttime
- lastGPSNewDataTime
;
660 if (lastGPSHeartbeat
!= gpsSol
.flags
.gpsHeartbeat
&& time_delta_ms
> 0)
662 // on new gps frame, update accEF and estimate centrifugal accleration
663 fpVector3_t vGPSacc
= {.v
= {0.0f
, 0.0f
, 0.0f
}};
664 vGPSacc
.x
= -(currentGPSvel
.x
- lastGPSvel
.x
) / (MS2S(time_delta_ms
)); // the x axis of accerometer is pointing backward
665 vGPSacc
.y
= (currentGPSvel
.y
- lastGPSvel
.y
) / (MS2S(time_delta_ms
));
666 vGPSacc
.z
= (currentGPSvel
.z
- lastGPSvel
.z
) / (MS2S(time_delta_ms
));
667 // Calculate estimated centrifugal accleration vector in body frame
668 quaternionRotateVector(vEstcentrifugalAccelBF
, &vGPSacc
, &orientation
); // EF -> BF
669 lastGPSNewDataTime
= currenttime
;
670 lastGPSvel
= currentGPSvel
;
672 lastGPSHeartbeat
= gpsSol
.flags
.gpsHeartbeat
;
673 *acc_ignore_slope_multipiler
= 4;
676 static void imuCalculateTurnRateacceleration(fpVector3_t
*vEstcentrifugalAccelBF
, float dT
, float *acc_ignore_slope_multipiler
)
679 static float lastspeed
= -1.0f
;
680 float currentspeed
= 0;
681 if (isGPSTrustworthy()){
682 //first speed choice is gps
683 currentspeed
= GPS3DspeedFiltered
;
684 *acc_ignore_slope_multipiler
= 4.0f
;
687 if (sensors(SENSOR_PITOT
) && pitotIsHealthy() && currentspeed
< 0)
689 // second choice is pitot
690 currentspeed
= getAirspeedEstimate();
691 *acc_ignore_slope_multipiler
= 2.0f
;
694 if (currentspeed
< 0)
696 //third choice is fixedWingReferenceAirspeed
697 currentspeed
= pidProfile()->fixedWingReferenceAirspeed
;
698 *acc_ignore_slope_multipiler
= 1.0f
;
700 float speed_change
= lastspeed
> 0 ? currentspeed
- lastspeed
: 0;
701 vEstcentrifugalAccelBF
->x
= -speed_change
/dT
;
702 vEstcentrifugalAccelBF
->y
= -currentspeed
*imuMeasuredRotationBFFiltered
.z
;
703 vEstcentrifugalAccelBF
->z
= currentspeed
*imuMeasuredRotationBFFiltered
.y
;
704 lastspeed
= currentspeed
;
707 fpQuaternion_t
* getTailSitterQuaternion(bool normal2tail
){
708 static bool firstRun
= true;
709 static fpQuaternion_t qNormal2Tail
;
710 static fpQuaternion_t qTail2Normal
;
712 fpAxisAngle_t axisAngle
;
713 axisAngle
.axis
.x
= 0;
714 axisAngle
.axis
.y
= 1;
715 axisAngle
.axis
.z
= 0;
716 axisAngle
.angle
= DEGREES_TO_RADIANS(-90);
717 axisAngleToQuaternion(&qNormal2Tail
, &axisAngle
);
718 quaternionConjugate(&qTail2Normal
, &qNormal2Tail
);
721 return normal2tail
? &qNormal2Tail
: &qTail2Normal
;
724 void imuUpdateTailSitter(void)
726 static bool lastTailSitter
=false;
727 if (((bool)STATE(TAILSITTER
)) != lastTailSitter
){
728 fpQuaternion_t
* rotation_for_tailsitter
= getTailSitterQuaternion(STATE(TAILSITTER
));
729 quaternionMultiply(&orientation
, &orientation
, rotation_for_tailsitter
);
731 lastTailSitter
= STATE(TAILSITTER
);
734 static void imuCalculateEstimatedAttitude(float dT
)
737 const bool canUseMAG
= sensors(SENSOR_MAG
) && compassIsHealthy();
739 const bool canUseMAG
= false;
742 float courseOverGround
= 0;
746 bool canUseCOG
= isGPSHeadingValid();
748 // Use compass (if available)
751 gpsHeadingInitialized
= true; // GPS heading initialised from MAG, continue on GPS if compass fails
753 // Use GPS (if available)
755 if (gpsHeadingInitialized
) {
756 courseOverGround
= DECIDEGREES_TO_RADIANS(gpsSol
.groundCourse
);
759 else if (!canUseMAG
) {
760 // Re-initialize quaternion from known Roll, Pitch and GPS heading
761 imuComputeQuaternionFromRPY(attitude
.values
.roll
, attitude
.values
.pitch
, gpsSol
.groundCourse
);
762 gpsHeadingInitialized
= true;
764 // Force reset of heading hold target
765 resetHeadingHoldTarget(DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
767 } else if (!ARMING_FLAG(ARMED
)) {
768 gpsHeadingInitialized
= false;
771 imuCalculateFilters(dT
);
772 // centrifugal force compensation
773 static fpVector3_t vEstcentrifugalAccelBF_velned
;
774 static fpVector3_t vEstcentrifugalAccelBF_turnrate
;
775 float acc_ignore_slope_multipiler
= 1.0f
; // when using gps centrifugal_force_compensation, AccelerometerWeightRateIgnore slope will be multiplied by this value
776 if (isGPSTrustworthy())
778 imuCalculateGPSacceleration(&vEstcentrifugalAccelBF_velned
, &acc_ignore_slope_multipiler
);
782 imuCalculateTurnRateacceleration(&vEstcentrifugalAccelBF_turnrate
, dT
, &acc_ignore_slope_multipiler
);
784 if (imuConfig()->inertia_comp_method
== COMPMETHOD_ADAPTIVE
&& isGPSTrustworthy() && STATE(AIRPLANE
))
786 //pick the best centrifugal acceleration between velned and turnrate
787 fpVector3_t compansatedGravityBF_velned
;
788 vectorAdd(&compansatedGravityBF_velned
, &imuMeasuredAccelBF
, &vEstcentrifugalAccelBF_velned
);
789 float velned_error
= fabsf(fast_fsqrtf(vectorNormSquared(&compansatedGravityBF_velned
)) - GRAVITY_CMSS
);
791 fpVector3_t compansatedGravityBF_turnrate
;
792 vectorAdd(&compansatedGravityBF_turnrate
, &imuMeasuredAccelBF
, &vEstcentrifugalAccelBF_turnrate
);
793 float turnrate_error
= fabsf(fast_fsqrtf(vectorNormSquared(&compansatedGravityBF_turnrate
)) - GRAVITY_CMSS
);
795 compansatedGravityBF
= velned_error
> turnrate_error
? compansatedGravityBF_turnrate
:compansatedGravityBF_velned
;
797 else if (((imuConfig()->inertia_comp_method
== COMPMETHOD_VELNED
) || (imuConfig()->inertia_comp_method
== COMPMETHOD_ADAPTIVE
)) && isGPSTrustworthy())
799 //velned centrifugal force compensation, quad will use this method
800 vectorAdd(&compansatedGravityBF
, &imuMeasuredAccelBF
, &vEstcentrifugalAccelBF_velned
);
802 else if (STATE(AIRPLANE
))
804 //turnrate centrifugal force compensation
805 vectorAdd(&compansatedGravityBF
, &imuMeasuredAccelBF
, &vEstcentrifugalAccelBF_turnrate
);
809 compansatedGravityBF
= imuMeasuredAccelBF
;
812 // In absence of GPS MAG is the only option
816 compansatedGravityBF
= imuMeasuredAccelBF
818 float accWeight
= imuGetPGainScaleFactor() * imuCalculateAccelerometerWeightNearness(&compansatedGravityBF
);
819 accWeight
= accWeight
* imuCalculateAccelerometerWeightRateIgnore(acc_ignore_slope_multipiler
);
820 const bool useAcc
= (accWeight
> 0.001f
);
822 const float magWeight
= imuGetPGainScaleFactor() * 1.0f
;
823 fpVector3_t measuredMagBF
= {.v
= {mag
.magADC
[X
], mag
.magADC
[Y
], mag
.magADC
[Z
]}};
824 imuMahonyAHRSupdate(dT
, &imuMeasuredRotationBF
,
825 useAcc
? &compansatedGravityBF
: NULL
,
826 useMag
? &measuredMagBF
: NULL
,
827 useCOG
, courseOverGround
,
830 imuUpdateTailSitter();
831 imuUpdateEulerAngles();
834 void imuUpdateAccelerometer(void)
836 if (sensors(SENSOR_ACC
)) {
838 isAccelUpdatedAtLeastOnce
= true;
842 void imuCheckVibrationLevels(void)
844 fpVector3_t accVibeLevels
;
846 accGetVibrationLevels(&accVibeLevels
);
847 const uint32_t accClipCount
= accGetClipCount();
849 DEBUG_SET(DEBUG_VIBE
, 0, accVibeLevels
.x
* 100);
850 DEBUG_SET(DEBUG_VIBE
, 1, accVibeLevels
.y
* 100);
851 DEBUG_SET(DEBUG_VIBE
, 2, accVibeLevels
.z
* 100);
852 DEBUG_SET(DEBUG_VIBE
, 3, accClipCount
);
853 // DEBUG_VIBE values 4-7 are used by NAV estimator
856 void imuUpdateAttitude(timeUs_t currentTimeUs
)
859 static timeUs_t previousIMUUpdateTimeUs
;
860 const float dT
= (currentTimeUs
- previousIMUUpdateTimeUs
) * 1e-6;
861 previousIMUUpdateTimeUs
= currentTimeUs
;
863 if (sensors(SENSOR_ACC
) && isAccelUpdatedAtLeastOnce
) {
864 gyroGetMeasuredRotationRate(&imuMeasuredRotationBF
); // Calculate gyro rate in body frame in rad/s
865 accGetMeasuredAcceleration(&imuMeasuredAccelBF
); // Calculate accel in body frame in cm/s/s
866 imuCheckVibrationLevels();
867 imuCalculateEstimatedAttitude(dT
); // Update attitude estimate
869 acc
.accADCf
[X
] = 0.0f
;
870 acc
.accADCf
[Y
] = 0.0f
;
871 acc
.accADCf
[Z
] = 0.0f
;
876 bool isImuReady(void)
878 return sensors(SENSOR_ACC
) && STATE(ACCELEROMETER_CALIBRATED
) && gyroIsCalibrationComplete();
881 bool isImuHeadingValid(void)
883 return (sensors(SENSOR_MAG
) && STATE(COMPASS_CALIBRATED
)) || gpsHeadingInitialized
;
886 float calculateCosTiltAngle(void)
888 return 1.0f
- 2.0f
* sq(orientation
.q1
) - 2.0f
* sq(orientation
.q2
);
891 #if defined(SITL_BUILD) || defined (USE_SIMULATOR)
893 void imuSetAttitudeRPY(int16_t roll
, int16_t pitch
, int16_t yaw
)
895 attitude
.values
.roll
= roll
;
896 attitude
.values
.pitch
= pitch
;
897 attitude
.values
.yaw
= yaw
;