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/>.
23 #include "build/debug.h"
25 #include "common/axis.h"
26 #include "common/maths.h"
27 #include "common/utils.h"
29 #include "config/parameter_group.h"
30 #include "config/parameter_group_ids.h"
32 #include "drivers/compass/compass.h"
33 #include "drivers/compass/compass_ak8963.h"
34 #include "drivers/compass/compass_ak8975.h"
35 #include "drivers/compass/compass_fake.h"
36 #include "drivers/compass/compass_hmc5883l.h"
37 #include "drivers/compass/compass_mag3110.h"
38 #include "drivers/compass/compass_ist8310.h"
39 #include "drivers/compass/compass_ist8308.h"
40 #include "drivers/compass/compass_qmc5883l.h"
41 #include "drivers/compass/compass_mpu9250.h"
42 #include "drivers/compass/compass_lis3mdl.h"
43 #include "drivers/compass/compass_rm3100.h"
44 #include "drivers/compass/compass_vcm5883.h"
45 #include "drivers/compass/compass_mlx90393.h"
46 #include "drivers/compass/compass_msp.h"
47 #include "drivers/io.h"
48 #include "drivers/light_led.h"
49 #include "drivers/time.h"
51 #include "fc/config.h"
52 #include "fc/runtime_config.h"
53 #include "fc/settings.h"
56 #include "io/beeper.h"
58 #include "sensors/boardalignment.h"
59 #include "sensors/compass.h"
60 #include "sensors/gyro.h"
61 #include "sensors/sensors.h"
63 mag_t mag
; // mag access functions
67 PG_REGISTER_WITH_RESET_TEMPLATE(compassConfig_t
, compassConfig
, PG_COMPASS_CONFIG
, 6);
69 PG_RESET_TEMPLATE(compassConfig_t
, compassConfig
,
70 .mag_align
= SETTING_ALIGN_MAG_DEFAULT
,
71 .mag_hardware
= SETTING_MAG_HARDWARE_DEFAULT
,
72 .mag_declination
= SETTING_MAG_DECLINATION_DEFAULT
,
74 .mag_to_use
= SETTING_MAG_TO_USE_DEFAULT
,
76 .magCalibrationTimeLimit
= SETTING_MAG_CALIBRATION_TIME_DEFAULT
,
77 .rollDeciDegrees
= SETTING_ALIGN_MAG_ROLL_DEFAULT
,
78 .pitchDeciDegrees
= SETTING_ALIGN_MAG_PITCH_DEFAULT
,
79 .yawDeciDegrees
= SETTING_ALIGN_MAG_YAW_DEFAULT
,
80 .magGain
= {SETTING_MAGGAIN_X_DEFAULT
, SETTING_MAGGAIN_Y_DEFAULT
, SETTING_MAGGAIN_Z_DEFAULT
},
83 static bool magUpdatedAtLeastOnce
= false;
85 bool compassDetect(magDev_t
*dev
, magSensor_e magHardwareToUse
)
87 magSensor_e magHardware
= MAG_NONE
;
88 requestedSensors
[SENSOR_INDEX_MAG
] = magHardwareToUse
;
90 dev
->magAlign
.useExternal
= false;
91 dev
->magAlign
.onBoard
= ALIGN_DEFAULT
;
93 switch (magHardwareToUse
) {
98 #ifdef USE_MAG_QMC5883
99 if (qmc5883Detect(dev
)) {
100 magHardware
= MAG_QMC5883
;
104 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
105 if (magHardwareToUse
!= MAG_AUTODETECT
) {
111 #ifdef USE_MAG_HMC5883
112 if (hmc5883lDetect(dev
)) {
113 magHardware
= MAG_HMC5883
;
117 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
118 if (magHardwareToUse
!= MAG_AUTODETECT
) {
124 #ifdef USE_MAG_AK8975
125 if (ak8975Detect(dev
)) {
126 magHardware
= MAG_AK8975
;
130 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
131 if (magHardwareToUse
!= MAG_AUTODETECT
) {
137 #ifdef USE_MAG_AK8963
138 if (ak8963Detect(dev
)) {
139 magHardware
= MAG_AK8963
;
143 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
144 if (magHardwareToUse
!= MAG_AUTODETECT
) {
150 #ifdef USE_MAG_MAG3110
151 if (mag3110detect(dev
)) {
152 magHardware
= MAG_MAG3110
;
156 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
157 if (magHardwareToUse
!= MAG_AUTODETECT
) {
163 #ifdef USE_MAG_IST8310
164 if (ist8310Detect(dev
)) {
165 magHardware
= MAG_IST8310
;
169 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
170 if (magHardwareToUse
!= MAG_AUTODETECT
) {
176 #ifdef USE_MAG_IST8308
177 if (ist8308Detect(dev
)) {
178 magHardware
= MAG_IST8308
;
182 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
183 if (magHardwareToUse
!= MAG_AUTODETECT
) {
189 #ifdef USE_MAG_MPU9250
190 if (mpu9250CompassDetect(dev
)) {
191 magHardware
= MAG_MPU9250
;
198 #ifdef USE_MAG_LIS3MDL
199 if (lis3mdlDetect(dev
)) {
200 magHardware
= MAG_LIS3MDL
;
205 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
206 if (magHardwareToUse
!= MAG_AUTODETECT
) {
213 // Skip autodetection for MSP mag
214 if (magHardwareToUse
!= MAG_AUTODETECT
&& mspMagDetect(dev
)) {
215 magHardware
= MAG_MSP
;
219 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
220 if (magHardwareToUse
!= MAG_AUTODETECT
) {
226 #ifdef USE_MAG_RM3100
227 if (rm3100MagDetect(dev
)) {
228 magHardware
= MAG_RM3100
;
232 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
233 if (magHardwareToUse
!= MAG_AUTODETECT
) {
239 #ifdef USE_MAG_VCM5883
240 if (vcm5883Detect(dev
)) {
241 magHardware
= MAG_VCM5883
;
245 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
246 if (magHardwareToUse
!= MAG_AUTODETECT
) {
252 #ifdef USE_MAG_MLX90393
253 if (mlx90393Detect(dev
)) {
254 magHardware
= MAG_MLX90393
;
258 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
259 if (magHardwareToUse
!= MAG_AUTODETECT
) {
266 if (fakeMagDetect(dev
)) {
267 magHardware
= MAG_FAKE
;
271 /* If we are asked for a specific sensor - break out, otherwise - fall through and continue */
272 if (magHardwareToUse
!= MAG_AUTODETECT
) {
278 magHardware
= MAG_NONE
;
282 if (magHardware
== MAG_NONE
) {
283 sensorsClear(SENSOR_MAG
);
287 detectedSensors
[SENSOR_INDEX_MAG
] = magHardware
;
288 sensorsSet(SENSOR_MAG
);
292 bool compassInit(void)
295 mag
.dev
.magSensorToUse
= compassConfig()->mag_to_use
;
297 mag
.dev
.magSensorToUse
= 0;
300 if (!compassDetect(&mag
.dev
, compassConfig()->mag_hardware
)) {
303 // initialize and calibration. turn on led during mag calibration (calibration routine blinks it)
305 const bool ret
= mag
.dev
.init(&mag
.dev
);
309 sensorsClear(SENSOR_MAG
);
312 if (compassConfig()->rollDeciDegrees
!= 0 ||
313 compassConfig()->pitchDeciDegrees
!= 0 ||
314 compassConfig()->yawDeciDegrees
!= 0) {
316 // Externally aligned compass
317 mag
.dev
.magAlign
.useExternal
= true;
319 fp_angles_t compassAngles
= {
320 .angles
.roll
= DECIDEGREES_TO_RADIANS(compassConfig()->rollDeciDegrees
),
321 .angles
.pitch
= DECIDEGREES_TO_RADIANS(compassConfig()->pitchDeciDegrees
),
322 .angles
.yaw
= DECIDEGREES_TO_RADIANS(compassConfig()->yawDeciDegrees
),
324 rotationMatrixFromAngles(&mag
.dev
.magAlign
.externalRotation
, &compassAngles
);
326 mag
.dev
.magAlign
.useExternal
= false;
327 if (compassConfig()->mag_align
!= ALIGN_DEFAULT
) {
328 mag
.dev
.magAlign
.onBoard
= compassConfig()->mag_align
;
330 mag
.dev
.magAlign
.onBoard
= CW270_DEG_FLIP
; // The most popular default is 270FLIP for external mags
337 bool compassIsHealthy(void)
339 return (mag
.magADC
[X
] != 0) || (mag
.magADC
[Y
] != 0) || (mag
.magADC
[Z
] != 0);
342 bool compassIsReady(void)
344 return magUpdatedAtLeastOnce
;
347 bool compassIsCalibrationComplete(void)
349 if (STATE(COMPASS_CALIBRATED
)) {
357 void compassUpdate(timeUs_t currentTimeUs
)
360 if (ARMING_FLAG(SIMULATOR_MODE_HITL
)) {
361 magUpdatedAtLeastOnce
= true;
365 static sensorCalibrationState_t calState
;
366 static timeUs_t calStartedAt
= 0;
367 static int16_t magPrev
[XYZ_AXIS_COUNT
];
368 static int magAxisDeviation
[XYZ_AXIS_COUNT
];
370 #if defined(SITL_BUILD)
371 ENABLE_STATE(COMPASS_CALIBRATED
);
375 compassConfig()->magZero
.raw
[X
] == 0 && compassConfig()->magZero
.raw
[Y
] == 0 && compassConfig()->magZero
.raw
[Z
] == 0 &&
376 compassConfig()->magGain
[X
] == 1024 && compassConfig()->magGain
[Y
] == 1024 && compassConfig()->magGain
[Z
] == 1024
378 DISABLE_STATE(COMPASS_CALIBRATED
);
381 ENABLE_STATE(COMPASS_CALIBRATED
);
385 if (!mag
.dev
.read(&mag
.dev
)) {
392 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
393 mag
.magADC
[axis
] = mag
.dev
.magADCRaw
[axis
]; // int32_t copy to work with
396 if (STATE(CALIBRATE_MAG
)) {
397 calStartedAt
= currentTimeUs
;
399 for (int axis
= 0; axis
< 3; axis
++) {
400 compassConfigMutable()->magZero
.raw
[axis
] = 0;
401 compassConfigMutable()->magGain
[axis
] = 1024;
403 magAxisDeviation
[axis
] = 0; // Gain is based on the biggest absolute deviation from the mag zero point. Gain computation starts at 0
406 beeper(BEEPER_ACTION_SUCCESS
);
408 sensorCalibrationResetState(&calState
);
409 DISABLE_STATE(CALIBRATE_MAG
);
412 if (calStartedAt
!= 0) {
413 if ((currentTimeUs
- calStartedAt
) < (compassConfig()->magCalibrationTimeLimit
* 1000000)) {
419 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
420 diffMag
+= (mag
.magADC
[axis
] - magPrev
[axis
]) * (mag
.magADC
[axis
] - magPrev
[axis
]);
421 avgMag
+= (mag
.magADC
[axis
] + magPrev
[axis
]) * (mag
.magADC
[axis
] + magPrev
[axis
]) / 4.0f
;
423 // Find the biggest sample deviation together with sample' sign
424 if (ABS(mag
.magADC
[axis
]) > ABS(magAxisDeviation
[axis
])) {
425 magAxisDeviation
[axis
] = mag
.magADC
[axis
];
430 // sqrtf(diffMag / avgMag) is a rough approximation of tangent of angle between magADC and magPrev. tan(8 deg) = 0.14
431 if ((avgMag
> 0.01f
) && ((diffMag
/ avgMag
) > (0.14f
* 0.14f
))) {
432 sensorCalibrationPushSampleForOffsetCalculation(&calState
, mag
.magADC
);
434 for (int axis
= 0; axis
< 3; axis
++) {
435 magPrev
[axis
] = mag
.magADC
[axis
];
440 sensorCalibrationSolveForOffset(&calState
, magZerof
);
442 for (int axis
= 0; axis
< 3; axis
++) {
443 compassConfigMutable()->magZero
.raw
[axis
] = lrintf(magZerof
[axis
]);
448 * We use max absolute value of each axis as scale calibration with constant 1024 as base
449 * It is dirty, but worth checking if this will solve the problem of changing mag vector when UAV is tilted
451 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
452 compassConfigMutable()->magGain
[axis
] = ABS(magAxisDeviation
[axis
] - compassConfig()->magZero
.raw
[axis
]);
456 saveConfigAndNotify();
460 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
461 mag
.magADC
[axis
] = (mag
.magADC
[axis
] - compassConfig()->magZero
.raw
[axis
]) * 1024 / compassConfig()->magGain
[axis
];
465 if (mag
.dev
.magAlign
.useExternal
) {
466 const fpVector3_t v
= {
474 rotationMatrixRotateVector(&rotated
, &v
, &mag
.dev
.magAlign
.externalRotation
);
475 applyTailSitterAlignment(&rotated
);
476 mag
.magADC
[X
] = rotated
.x
;
477 mag
.magADC
[Y
] = rotated
.y
;
478 mag
.magADC
[Z
] = rotated
.z
;
482 applySensorAlignment(mag
.magADC
, mag
.magADC
, mag
.dev
.magAlign
.onBoard
);
483 applyBoardAlignment(mag
.magADC
);
486 magUpdatedAtLeastOnce
= true;