Adding more servos
[inav.git] / src / main / flight / imu.c
blobd3f9d90d7dbc25058e198269fc13b15a051e8e5f
1 /*
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)
20 #include <stdbool.h>
21 #include <stdint.h>
22 #include <math.h>
24 #include "platform.h"
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"
56 #endif
58 #include "io/gps.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
70 * Y-axis = East/Right
71 * Z-axis = Up
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;
170 void imuInit(void)
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
183 #ifdef USE_MAG
184 const int deg = compassConfig()->mag_declination / 100;
185 const int min = compassConfig()->mag_declination % 100;
186 #else
187 const int deg = 0;
188 const int min = 0;
189 #endif
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)
221 v->y = -v->y;
224 void imuTransformVectorEarthToBody(fpVector3_t * v)
226 // HACK: This is needed to correctly transform from NED (sensor frame) to NEU (navigation)
227 v->y = -v->y;
229 // From earth frame to body frame
230 quaternionRotateVector(v, v, &orientation);
233 #if defined(USE_GPS)
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();
256 #endif
258 static bool imuUseFastGains(void)
260 return !ARMING_FLAG(ARMED) && millis() < 20000;
263 static float imuGetPGainScaleFactor(void)
265 if (imuUseFastGains()) {
266 return 10.0f;
268 else {
269 return 1.0f;
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)) {
290 return true;
293 const float normSq = quaternionNormSqared(&orientation);
294 if (normSq > (1.0f - 1e-6f) && normSq < (1.0f + 1e-6f)) {
295 return true;
298 return false;
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)) {
305 return;
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
313 orientation = *quat;
314 imuErrorEvent.errorCode = 1;
315 LOG_ERROR(IMU, "AHRS orientation quaternion error. Reset to last known good value");
317 else {
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");
324 #ifdef USE_BLACKBOX
325 if (feature(FEATURE_BLACKBOX)) {
326 blackboxLogEvent(FLIGHT_LOG_EVENT_IMU_FAILURE, (flightLogEventData_t*)&imuErrorEvent);
328 #endif
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)
336 #endif
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);
346 return wCoG;
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) {
362 float wMag = 1.0f;
363 float wCoG = 1.0f;
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);
371 fpVector3_t vMag;
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
381 vMag.z = 0.0f;
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);
388 #ifdef USE_SIMULATOR
389 if (ARMING_FLAG(SIMULATOR_MODE_HITL) || ARMING_FLAG(SIMULATOR_MODE_SITL)) {
390 imuSetMagneticDeclination(0);
392 #endif
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);
402 if (useCOG) {
403 fpVector3_t vForward = { .v = { 0.0f, 0.0f, 0.0f } };
404 //vForward as trust vector
405 if (STATE(MULTIROTOR) && (!isMixerTransitionMixing)){
406 vForward.z = 1.0f;
407 }else{
408 vForward.x = 1.0f;
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);
433 #endif
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
445 vHeadingEF.z = 0.0f;
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))) {
467 fpVector3_t vTmp;
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 */
482 if (accBF) {
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))) {
497 fpVector3_t vTmp;
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);
509 // Anti wind-up
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
519 fpVector3_t vTheta;
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;
537 else {
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)
557 #ifdef USE_SIMULATOR
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();
562 else
563 #endif
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);
577 } else {
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
596 // Omega = Speed / 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);
622 else
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)
636 //flitering
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);
644 //anti aliasing
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)
678 //fixed wing only
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;
686 #ifdef USE_PITOT
687 if (sensors(SENSOR_PITOT) && pitotIsHealthy() && currentspeed < 0)
689 // second choice is pitot
690 currentspeed = getAirspeedEstimate();
691 *acc_ignore_slope_multipiler = 2.0f;
693 #endif
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;
711 if(firstRun){
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);
719 firstRun = false;
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)
736 #if defined(USE_MAG)
737 const bool canUseMAG = sensors(SENSOR_MAG) && compassIsHealthy();
738 #else
739 const bool canUseMAG = false;
740 #endif
742 float courseOverGround = 0;
743 bool useMag = false;
744 bool useCOG = false;
745 #if defined(USE_GPS)
746 bool canUseCOG = isGPSHeadingValid();
748 // Use compass (if available)
749 if (canUseMAG) {
750 useMag = true;
751 gpsHeadingInitialized = true; // GPS heading initialised from MAG, continue on GPS if compass fails
753 // Use GPS (if available)
754 if (canUseCOG) {
755 if (gpsHeadingInitialized) {
756 courseOverGround = DECIDEGREES_TO_RADIANS(gpsSol.groundCourse);
757 useCOG = true;
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);
780 if (STATE(AIRPLANE))
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);
807 else
809 compansatedGravityBF = imuMeasuredAccelBF;
811 #else
812 // In absence of GPS MAG is the only option
813 if (canUseMAG) {
814 useMag = true;
816 compansatedGravityBF = imuMeasuredAccelBF
817 #endif
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,
828 accWeight,
829 magWeight);
830 imuUpdateTailSitter();
831 imuUpdateEulerAngles();
834 void imuUpdateAccelerometer(void)
836 if (sensors(SENSOR_ACC)) {
837 accUpdate();
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)
858 /* Calculate dT */
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
868 } else {
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;
898 imuUpdated = true;
900 #endif