2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 // Inertial Measurement Unit (IMU)
29 #include "build/build_config.h"
30 #include "build/debug.h"
32 #include "common/axis.h"
35 #include "pg/pg_ids.h"
37 #include "drivers/time.h"
39 #include "fc/runtime_config.h"
41 #include "flight/gps_rescue.h"
42 #include "flight/imu.h"
43 #include "flight/mixer.h"
44 #include "flight/pid.h"
48 #include "scheduler/scheduler.h"
50 #include "sensors/acceleration.h"
51 #include "sensors/barometer.h"
52 #include "sensors/compass.h"
53 #include "sensors/gyro.h"
54 #include "sensors/sensors.h"
56 #if defined(SIMULATOR_BUILD) && defined(SIMULATOR_MULTITHREAD)
60 static pthread_mutex_t imuUpdateLock
;
62 #if defined(SIMULATOR_IMU_SYNC)
63 static uint32_t imuDeltaT
= 0;
64 static bool imuUpdated
= false;
67 #define IMU_LOCK pthread_mutex_lock(&imuUpdateLock)
68 #define IMU_UNLOCK pthread_mutex_unlock(&imuUpdateLock)
77 // the limit (in degrees/second) beyond which we stop integrating
78 // omega_I. At larger spin rates the DCM PI controller can get 'dizzy'
79 // which results in false gyro drift. See
80 // http://gentlenav.googlecode.com/files/fastRotations.pdf
82 #define SPIN_RATE_LIMIT 20
84 #define ATTITUDE_RESET_QUIET_TIME 250000 // 250ms - gyro quiet period after disarm before attitude reset
85 #define ATTITUDE_RESET_GYRO_LIMIT 15 // 15 deg/sec - gyro limit for quiet period
86 #define ATTITUDE_RESET_KP_GAIN 25.0 // dcmKpGain value to use during attitude reset
87 #define ATTITUDE_RESET_ACTIVE_TIME 500000 // 500ms - Time to wait for attitude to converge at high gain
88 #define GPS_COG_MIN_GROUNDSPEED 500 // 500cm/s minimum groundspeed for a gps heading to be considered valid
90 float accAverage
[XYZ_AXIS_COUNT
];
92 bool canUseGPSHeading
= true;
94 static float throttleAngleScale
;
95 static int throttleAngleValue
;
97 static float smallAngleCosZ
= 0;
99 static imuRuntimeConfig_t imuRuntimeConfig
;
103 STATIC_UNIT_TESTED
bool attitudeIsEstablished
= false;
105 // quaternion of sensor frame relative to earth frame
106 STATIC_UNIT_TESTED quaternion q
= QUATERNION_INITIALIZE
;
107 STATIC_UNIT_TESTED quaternionProducts qP
= QUATERNION_PRODUCTS_INITIALIZE
;
108 // headfree quaternions
109 quaternion headfree
= QUATERNION_INITIALIZE
;
110 quaternion offset
= QUATERNION_INITIALIZE
;
112 // absolute angle inclination in multiple of 0.1 degree 180 deg = 1800
113 attitudeEulerAngles_t attitude
= EULER_INITIALIZE
;
115 PG_REGISTER_WITH_RESET_TEMPLATE(imuConfig_t
, imuConfig
, PG_IMU_CONFIG
, 1);
117 PG_RESET_TEMPLATE(imuConfig_t
, imuConfig
,
118 .dcm_kp
= 2500, // 1.0 * 10000
119 .dcm_ki
= 0, // 0.003 * 10000
123 static void imuQuaternionComputeProducts(quaternion
*quat
, quaternionProducts
*quatProd
)
125 quatProd
->ww
= quat
->w
* quat
->w
;
126 quatProd
->wx
= quat
->w
* quat
->x
;
127 quatProd
->wy
= quat
->w
* quat
->y
;
128 quatProd
->wz
= quat
->w
* quat
->z
;
129 quatProd
->xx
= quat
->x
* quat
->x
;
130 quatProd
->xy
= quat
->x
* quat
->y
;
131 quatProd
->xz
= quat
->x
* quat
->z
;
132 quatProd
->yy
= quat
->y
* quat
->y
;
133 quatProd
->yz
= quat
->y
* quat
->z
;
134 quatProd
->zz
= quat
->z
* quat
->z
;
137 STATIC_UNIT_TESTED
void imuComputeRotationMatrix(void){
138 imuQuaternionComputeProducts(&q
, &qP
);
140 rMat
[0][0] = 1.0f
- 2.0f
* qP
.yy
- 2.0f
* qP
.zz
;
141 rMat
[0][1] = 2.0f
* (qP
.xy
+ -qP
.wz
);
142 rMat
[0][2] = 2.0f
* (qP
.xz
- -qP
.wy
);
144 rMat
[1][0] = 2.0f
* (qP
.xy
- -qP
.wz
);
145 rMat
[1][1] = 1.0f
- 2.0f
* qP
.xx
- 2.0f
* qP
.zz
;
146 rMat
[1][2] = 2.0f
* (qP
.yz
+ -qP
.wx
);
148 rMat
[2][0] = 2.0f
* (qP
.xz
+ -qP
.wy
);
149 rMat
[2][1] = 2.0f
* (qP
.yz
- -qP
.wx
);
150 rMat
[2][2] = 1.0f
- 2.0f
* qP
.xx
- 2.0f
* qP
.yy
;
152 #if defined(SIMULATOR_BUILD) && !defined(USE_IMU_CALC) && !defined(SET_IMU_FROM_EULER)
153 rMat
[1][0] = -2.0f
* (qP
.xy
- -qP
.wz
);
154 rMat
[2][0] = -2.0f
* (qP
.xz
+ -qP
.wy
);
159 * Calculate RC time constant used in the accZ lpf.
161 static float calculateAccZLowPassFilterRCTimeConstant(float accz_lpf_cutoff
)
163 return 0.5f
/ (M_PIf
* accz_lpf_cutoff
);
166 static float calculateThrottleAngleScale(uint16_t throttle_correction_angle
)
168 return (1800.0f
/ M_PIf
) * (900.0f
/ throttle_correction_angle
);
171 void imuConfigure(uint16_t throttle_correction_angle
, uint8_t throttle_correction_value
)
173 imuRuntimeConfig
.dcm_kp
= imuConfig()->dcm_kp
/ 10000.0f
;
174 imuRuntimeConfig
.dcm_ki
= imuConfig()->dcm_ki
/ 10000.0f
;
176 smallAngleCosZ
= cos_approx(degreesToRadians(imuConfig()->small_angle
));
178 fc_acc
= calculateAccZLowPassFilterRCTimeConstant(5.0f
); // Set to fix value
179 throttleAngleScale
= calculateThrottleAngleScale(throttle_correction_angle
);
181 throttleAngleValue
= throttle_correction_value
;
187 canUseGPSHeading
= true;
189 canUseGPSHeading
= false;
192 imuComputeRotationMatrix();
194 #if defined(SIMULATOR_BUILD) && defined(SIMULATOR_MULTITHREAD)
195 if (pthread_mutex_init(&imuUpdateLock
, NULL
) != 0) {
196 printf("Create imuUpdateLock error!\n");
202 static float invSqrt(float x
)
204 return 1.0f
/ sqrtf(x
);
207 static void imuMahonyAHRSupdate(float dt
, float gx
, float gy
, float gz
,
208 bool useAcc
, float ax
, float ay
, float az
,
210 bool useCOG
, float courseOverGround
, const float dcmKpGain
)
212 static float integralFBx
= 0.0f
, integralFBy
= 0.0f
, integralFBz
= 0.0f
; // integral error terms scaled by Ki
214 // Calculate general spin rate (rad/s)
215 const float spin_rate
= sqrtf(sq(gx
) + sq(gy
) + sq(gz
));
217 // Use raw heading error (from GPS or whatever else)
218 float ex
= 0, ey
= 0, ez
= 0;
220 while (courseOverGround
> M_PIf
) {
221 courseOverGround
-= (2.0f
* M_PIf
);
224 while (courseOverGround
< -M_PIf
) {
225 courseOverGround
+= (2.0f
* M_PIf
);
228 const float ez_ef
= (- sin_approx(courseOverGround
) * rMat
[0][0] - cos_approx(courseOverGround
) * rMat
[1][0]);
230 ex
= rMat
[2][0] * ez_ef
;
231 ey
= rMat
[2][1] * ez_ef
;
232 ez
= rMat
[2][2] * ez_ef
;
236 // Use measured magnetic field vector
237 float mx
= mag
.magADC
[X
];
238 float my
= mag
.magADC
[Y
];
239 float mz
= mag
.magADC
[Z
];
240 float recipMagNorm
= sq(mx
) + sq(my
) + sq(mz
);
241 if (useMag
&& recipMagNorm
> 0.01f
) {
242 // Normalise magnetometer measurement
243 recipMagNorm
= invSqrt(recipMagNorm
);
248 // For magnetometer correction we make an assumption that magnetic field is perpendicular to gravity (ignore Z-component in EF).
249 // This way magnetic field will only affect heading and wont mess roll/pitch angles
251 // (hx; hy; 0) - measured mag field vector in EF (assuming Z-component is zero)
252 // (bx; 0; 0) - reference mag field vector heading due North in EF (assuming Z-component is zero)
253 const float hx
= rMat
[0][0] * mx
+ rMat
[0][1] * my
+ rMat
[0][2] * mz
;
254 const float hy
= rMat
[1][0] * mx
+ rMat
[1][1] * my
+ rMat
[1][2] * mz
;
255 const float bx
= sqrtf(hx
* hx
+ hy
* hy
);
257 // magnetometer error is cross product between estimated magnetic north and measured magnetic north (calculated in EF)
258 const float ez_ef
= -(hy
* bx
);
260 // Rotate mag error vector back to BF and accumulate
261 ex
+= rMat
[2][0] * ez_ef
;
262 ey
+= rMat
[2][1] * ez_ef
;
263 ez
+= rMat
[2][2] * ez_ef
;
269 // Use measured acceleration vector
270 float recipAccNorm
= sq(ax
) + sq(ay
) + sq(az
);
271 if (useAcc
&& recipAccNorm
> 0.01f
) {
272 // Normalise accelerometer measurement
273 recipAccNorm
= invSqrt(recipAccNorm
);
278 // Error is sum of cross product between estimated direction and measured direction of gravity
279 ex
+= (ay
* rMat
[2][2] - az
* rMat
[2][1]);
280 ey
+= (az
* rMat
[2][0] - ax
* rMat
[2][2]);
281 ez
+= (ax
* rMat
[2][1] - ay
* rMat
[2][0]);
284 // Compute and apply integral feedback if enabled
285 if (imuRuntimeConfig
.dcm_ki
> 0.0f
) {
286 // Stop integrating if spinning beyond the certain limit
287 if (spin_rate
< DEGREES_TO_RADIANS(SPIN_RATE_LIMIT
)) {
288 const float dcmKiGain
= imuRuntimeConfig
.dcm_ki
;
289 integralFBx
+= dcmKiGain
* ex
* dt
; // integral error scaled by Ki
290 integralFBy
+= dcmKiGain
* ey
* dt
;
291 integralFBz
+= dcmKiGain
* ez
* dt
;
294 integralFBx
= 0.0f
; // prevent integral windup
299 // Apply proportional and integral feedback
300 gx
+= dcmKpGain
* ex
+ integralFBx
;
301 gy
+= dcmKpGain
* ey
+ integralFBy
;
302 gz
+= dcmKpGain
* ez
+ integralFBz
;
304 // Integrate rate of change of quaternion
315 q
.w
+= (-buffer
.x
* gx
- buffer
.y
* gy
- buffer
.z
* gz
);
316 q
.x
+= (+buffer
.w
* gx
+ buffer
.y
* gz
- buffer
.z
* gy
);
317 q
.y
+= (+buffer
.w
* gy
- buffer
.x
* gz
+ buffer
.z
* gx
);
318 q
.z
+= (+buffer
.w
* gz
+ buffer
.x
* gy
- buffer
.y
* gx
);
320 // Normalise quaternion
321 float recipNorm
= invSqrt(sq(q
.w
) + sq(q
.x
) + sq(q
.y
) + sq(q
.z
));
327 // Pre-compute rotation matrix from quaternion
328 imuComputeRotationMatrix();
330 attitudeIsEstablished
= true;
333 STATIC_UNIT_TESTED
void imuUpdateEulerAngles(void)
335 quaternionProducts buffer
;
337 if (FLIGHT_MODE(HEADFREE_MODE
)) {
338 imuQuaternionComputeProducts(&headfree
, &buffer
);
340 attitude
.values
.roll
= lrintf(atan2_approx((+2.0f
* (buffer
.wx
+ buffer
.yz
)), (+1.0f
- 2.0f
* (buffer
.xx
+ buffer
.yy
))) * (1800.0f
/ M_PIf
));
341 attitude
.values
.pitch
= lrintf(((0.5f
* M_PIf
) - acos_approx(+2.0f
* (buffer
.wy
- buffer
.xz
))) * (1800.0f
/ M_PIf
));
342 attitude
.values
.yaw
= lrintf((-atan2_approx((+2.0f
* (buffer
.wz
+ buffer
.xy
)), (+1.0f
- 2.0f
* (buffer
.yy
+ buffer
.zz
))) * (1800.0f
/ M_PIf
)));
344 attitude
.values
.roll
= lrintf(atan2_approx(rMat
[2][1], rMat
[2][2]) * (1800.0f
/ M_PIf
));
345 attitude
.values
.pitch
= lrintf(((0.5f
* M_PIf
) - acos_approx(-rMat
[2][0])) * (1800.0f
/ M_PIf
));
346 attitude
.values
.yaw
= lrintf((-atan2_approx(rMat
[1][0], rMat
[0][0]) * (1800.0f
/ M_PIf
)));
349 if (attitude
.values
.yaw
< 0) {
350 attitude
.values
.yaw
+= 3600;
354 static bool imuIsAccelerometerHealthy(float *accAverage
)
356 float accMagnitudeSq
= 0;
357 for (int axis
= 0; axis
< 3; axis
++) {
358 const float a
= accAverage
[axis
];
359 accMagnitudeSq
+= a
* a
;
362 accMagnitudeSq
= accMagnitudeSq
* sq(acc
.dev
.acc_1G_rec
);
364 // Accept accel readings only in range 0.9g - 1.1g
365 return (0.81f
< accMagnitudeSq
) && (accMagnitudeSq
< 1.21f
);
368 // Calculate the dcmKpGain to use. When armed, the gain is imuRuntimeConfig.dcm_kp * 1.0 scaling.
369 // When disarmed after initial boot, the scaling is set to 10.0 for the first 20 seconds to speed up initial convergence.
370 // After disarming we want to quickly reestablish convergence to deal with the attitude estimation being incorrect due to a crash.
371 // - wait for a 250ms period of low gyro activity to ensure the craft is not moving
372 // - use a large dcmKpGain value for 500ms to allow the attitude estimate to quickly converge
373 // - reset the gain back to the standard setting
374 static float imuCalcKpGain(timeUs_t currentTimeUs
, bool useAcc
, float *gyroAverage
)
376 static bool lastArmState
= false;
377 static timeUs_t gyroQuietPeriodTimeEnd
= 0;
378 static timeUs_t attitudeResetTimeEnd
= 0;
379 static bool attitudeResetCompleted
= false;
381 bool attitudeResetActive
= false;
383 const bool armState
= ARMING_FLAG(ARMED
);
386 if (lastArmState
) { // Just disarmed; start the gyro quiet period
387 gyroQuietPeriodTimeEnd
= currentTimeUs
+ ATTITUDE_RESET_QUIET_TIME
;
388 attitudeResetTimeEnd
= 0;
389 attitudeResetCompleted
= false;
392 // If gyro activity exceeds the threshold then restart the quiet period.
393 // Also, if the attitude reset has been complete and there is subsequent gyro activity then
394 // start the reset cycle again. This addresses the case where the pilot rights the craft after a crash.
395 if ((attitudeResetTimeEnd
> 0) || (gyroQuietPeriodTimeEnd
> 0) || attitudeResetCompleted
) {
396 if ((fabsf(gyroAverage
[X
]) > ATTITUDE_RESET_GYRO_LIMIT
)
397 || (fabsf(gyroAverage
[Y
]) > ATTITUDE_RESET_GYRO_LIMIT
)
398 || (fabsf(gyroAverage
[Z
]) > ATTITUDE_RESET_GYRO_LIMIT
)
401 gyroQuietPeriodTimeEnd
= currentTimeUs
+ ATTITUDE_RESET_QUIET_TIME
;
402 attitudeResetTimeEnd
= 0;
405 if (attitudeResetTimeEnd
> 0) { // Resetting the attitude estimation
406 if (currentTimeUs
>= attitudeResetTimeEnd
) {
407 gyroQuietPeriodTimeEnd
= 0;
408 attitudeResetTimeEnd
= 0;
409 attitudeResetCompleted
= true;
411 attitudeResetActive
= true;
413 } else if ((gyroQuietPeriodTimeEnd
> 0) && (currentTimeUs
>= gyroQuietPeriodTimeEnd
)) {
414 // Start the high gain period to bring the estimation into convergence
415 attitudeResetTimeEnd
= currentTimeUs
+ ATTITUDE_RESET_ACTIVE_TIME
;
416 gyroQuietPeriodTimeEnd
= 0;
419 lastArmState
= armState
;
421 if (attitudeResetActive
) {
422 ret
= ATTITUDE_RESET_KP_GAIN
;
424 ret
= imuRuntimeConfig
.dcm_kp
;
426 ret
= ret
* 10.0f
; // Scale the kP to generally converge faster when disarmed.
434 static void imuComputeQuaternionFromRPY(quaternionProducts
*quatProd
, int16_t initialRoll
, int16_t initialPitch
, int16_t initialYaw
)
436 if (initialRoll
> 1800) {
440 if (initialPitch
> 1800) {
441 initialPitch
-= 3600;
444 if (initialYaw
> 1800) {
448 const float cosRoll
= cos_approx(DECIDEGREES_TO_RADIANS(initialRoll
) * 0.5f
);
449 const float sinRoll
= sin_approx(DECIDEGREES_TO_RADIANS(initialRoll
) * 0.5f
);
451 const float cosPitch
= cos_approx(DECIDEGREES_TO_RADIANS(initialPitch
) * 0.5f
);
452 const float sinPitch
= sin_approx(DECIDEGREES_TO_RADIANS(initialPitch
) * 0.5f
);
454 const float cosYaw
= cos_approx(DECIDEGREES_TO_RADIANS(-initialYaw
) * 0.5f
);
455 const float sinYaw
= sin_approx(DECIDEGREES_TO_RADIANS(-initialYaw
) * 0.5f
);
457 const float q0
= cosRoll
* cosPitch
* cosYaw
+ sinRoll
* sinPitch
* sinYaw
;
458 const float q1
= sinRoll
* cosPitch
* cosYaw
- cosRoll
* sinPitch
* sinYaw
;
459 const float q2
= cosRoll
* sinPitch
* cosYaw
+ sinRoll
* cosPitch
* sinYaw
;
460 const float q3
= cosRoll
* cosPitch
* sinYaw
- sinRoll
* sinPitch
* cosYaw
;
462 quatProd
->xx
= sq(q1
);
463 quatProd
->yy
= sq(q2
);
464 quatProd
->zz
= sq(q3
);
466 quatProd
->xy
= q1
* q2
;
467 quatProd
->xz
= q1
* q3
;
468 quatProd
->yz
= q2
* q3
;
470 quatProd
->wx
= q0
* q1
;
471 quatProd
->wy
= q0
* q2
;
472 quatProd
->wz
= q0
* q3
;
474 imuComputeRotationMatrix();
476 attitudeIsEstablished
= true;
480 static void imuCalculateEstimatedAttitude(timeUs_t currentTimeUs
)
482 static timeUs_t previousIMUUpdateTime
;
485 bool useCOG
= false; // Whether or not correct yaw via imuMahonyAHRSupdate from our ground course
486 float courseOverGround
= 0; // To be used when useCOG is true. Stored in Radians
488 const timeDelta_t deltaT
= currentTimeUs
- previousIMUUpdateTime
;
489 previousIMUUpdateTime
= currentTimeUs
;
492 if (sensors(SENSOR_MAG
) && compassIsHealthy()
493 #ifdef USE_GPS_RESCUE
494 && !gpsRescueDisableMag()
501 if (!useMag
&& sensors(SENSOR_GPS
) && STATE(GPS_FIX
) && gpsSol
.numSat
>= 5 && gpsSol
.groundSpeed
>= GPS_COG_MIN_GROUNDSPEED
) {
502 // Use GPS course over ground to correct attitude.values.yaw
504 courseOverGround
= DECIDEGREES_TO_RADIANS(gpsSol
.groundCourse
);
507 courseOverGround
= DECIDEGREES_TO_RADIANS(gpsSol
.groundCourse
);
512 if (useCOG
&& shouldInitializeGPSHeading()) {
513 // Reset our reference and reinitialize quaternion. This will likely ideally happen more than once per flight, but for now,
514 // shouldInitializeGPSHeading() returns true only once.
515 imuComputeQuaternionFromRPY(&qP
, attitude
.values
.roll
, attitude
.values
.pitch
, gpsSol
.groundCourse
);
517 useCOG
= false; // Don't use the COG when we first reinitialize. Next time around though, yes.
522 #if defined(SIMULATOR_BUILD) && !defined(USE_IMU_CALC)
523 UNUSED(imuMahonyAHRSupdate
);
524 UNUSED(imuIsAccelerometerHealthy
);
528 UNUSED(canUseGPSHeading
);
529 UNUSED(courseOverGround
);
531 UNUSED(imuCalcKpGain
);
534 #if defined(SIMULATOR_BUILD) && defined(SIMULATOR_IMU_SYNC)
535 // printf("[imu]deltaT = %u, imuDeltaT = %u, currentTimeUs = %u, micros64_real = %lu\n", deltaT, imuDeltaT, currentTimeUs, micros64_real());
538 float gyroAverage
[XYZ_AXIS_COUNT
];
539 gyroGetAccumulationAverage(gyroAverage
);
541 if (accGetAccumulationAverage(accAverage
)) {
542 useAcc
= imuIsAccelerometerHealthy(accAverage
);
545 imuMahonyAHRSupdate(deltaT
* 1e-6f
,
546 DEGREES_TO_RADIANS(gyroAverage
[X
]), DEGREES_TO_RADIANS(gyroAverage
[Y
]), DEGREES_TO_RADIANS(gyroAverage
[Z
]),
547 useAcc
, accAverage
[X
], accAverage
[Y
], accAverage
[Z
],
549 useCOG
, courseOverGround
, imuCalcKpGain(currentTimeUs
, useAcc
, gyroAverage
));
551 imuUpdateEulerAngles();
555 static int calculateThrottleAngleCorrection(void)
558 * Use 0 as the throttle angle correction if we are inverted, vertical or with a
559 * small angle < 0.86 deg
560 * TODO: Define this small angle in config.
562 if (getCosTiltAngle() <= 0.015f
) {
565 int angle
= lrintf(acos_approx(getCosTiltAngle()) * throttleAngleScale
);
568 return lrintf(throttleAngleValue
* sin_approx(angle
/ (900.0f
* M_PIf
/ 2.0f
)));
571 void imuUpdateAttitude(timeUs_t currentTimeUs
)
573 if (sensors(SENSOR_ACC
) && acc
.isAccelUpdatedAtLeastOnce
) {
575 #if defined(SIMULATOR_BUILD) && defined(SIMULATOR_IMU_SYNC)
576 if (imuUpdated
== false) {
582 imuCalculateEstimatedAttitude(currentTimeUs
);
585 // Update the throttle correction for angle and supply it to the mixer
586 int throttleAngleCorrection
= 0;
587 if (throttleAngleValue
&& (FLIGHT_MODE(ANGLE_MODE
) || FLIGHT_MODE(HORIZON_MODE
)) && ARMING_FLAG(ARMED
)) {
588 throttleAngleCorrection
= calculateThrottleAngleCorrection();
590 mixerSetThrottleAngleCorrection(throttleAngleCorrection
);
596 schedulerIgnoreTaskStateTime();
601 bool shouldInitializeGPSHeading()
603 static bool initialized
= false;
614 float getCosTiltAngle(void)
619 void getQuaternion(quaternion
*quat
)
627 #ifdef SIMULATOR_BUILD
628 void imuSetAttitudeRPY(float roll
, float pitch
, float yaw
)
632 attitude
.values
.roll
= roll
* 10;
633 attitude
.values
.pitch
= pitch
* 10;
634 attitude
.values
.yaw
= yaw
* 10;
639 void imuSetAttitudeQuat(float w
, float x
, float y
, float z
)
648 imuComputeRotationMatrix();
650 attitudeIsEstablished
= true;
652 imuUpdateEulerAngles();
657 #if defined(SIMULATOR_BUILD) && defined(SIMULATOR_IMU_SYNC)
658 void imuSetHasNewData(uint32_t dt
)
669 bool imuQuaternionHeadfreeOffsetSet(void)
671 if ((ABS(attitude
.values
.roll
) < 450) && (ABS(attitude
.values
.pitch
) < 450)) {
672 const float yaw
= -atan2_approx((+2.0f
* (qP
.wz
+ qP
.xy
)), (+1.0f
- 2.0f
* (qP
.yy
+ qP
.zz
)));
674 offset
.w
= cos_approx(yaw
/2);
677 offset
.z
= sin_approx(yaw
/2);
685 void imuQuaternionMultiplication(quaternion
*q1
, quaternion
*q2
, quaternion
*result
)
687 const float A
= (q1
->w
+ q1
->x
) * (q2
->w
+ q2
->x
);
688 const float B
= (q1
->z
- q1
->y
) * (q2
->y
- q2
->z
);
689 const float C
= (q1
->w
- q1
->x
) * (q2
->y
+ q2
->z
);
690 const float D
= (q1
->y
+ q1
->z
) * (q2
->w
- q2
->x
);
691 const float E
= (q1
->x
+ q1
->z
) * (q2
->x
+ q2
->y
);
692 const float F
= (q1
->x
- q1
->z
) * (q2
->x
- q2
->y
);
693 const float G
= (q1
->w
+ q1
->y
) * (q2
->w
- q2
->z
);
694 const float H
= (q1
->w
- q1
->y
) * (q2
->w
+ q2
->z
);
696 result
->w
= B
+ (- E
- F
+ G
+ H
) / 2.0f
;
697 result
->x
= A
- (+ E
+ F
+ G
+ H
) / 2.0f
;
698 result
->y
= C
+ (+ E
- F
+ G
- H
) / 2.0f
;
699 result
->z
= D
+ (+ E
- F
- G
+ H
) / 2.0f
;
702 void imuQuaternionHeadfreeTransformVectorEarthToBody(t_fp_vector_def
*v
)
704 quaternionProducts buffer
;
706 imuQuaternionMultiplication(&offset
, &q
, &headfree
);
707 imuQuaternionComputeProducts(&headfree
, &buffer
);
709 const float x
= (buffer
.ww
+ buffer
.xx
- buffer
.yy
- buffer
.zz
) * v
->X
+ 2.0f
* (buffer
.xy
+ buffer
.wz
) * v
->Y
+ 2.0f
* (buffer
.xz
- buffer
.wy
) * v
->Z
;
710 const float y
= 2.0f
* (buffer
.xy
- buffer
.wz
) * v
->X
+ (buffer
.ww
- buffer
.xx
+ buffer
.yy
- buffer
.zz
) * v
->Y
+ 2.0f
* (buffer
.yz
+ buffer
.wx
) * v
->Z
;
711 const float z
= 2.0f
* (buffer
.xz
+ buffer
.wy
) * v
->X
+ 2.0f
* (buffer
.yz
- buffer
.wx
) * v
->Y
+ (buffer
.ww
- buffer
.xx
- buffer
.yy
+ buffer
.zz
) * v
->Z
;
721 return !sensors(SENSOR_ACC
) || (attitudeIsEstablished
&& getCosTiltAngle() > smallAngleCosZ
);