2 ******************************************************************************
3 * @addtogroup OpenPilotModules OpenPilot Modules
5 * @addtogroup State Estimation
6 * @brief Acquires sensor data and computes state estimate
10 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
11 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2013.
12 * @brief Complementary filter to calculate Attitude from Accels and Gyros
13 * and optionally magnetometers:
14 * WARNING: Will drift if the mean acceleration force doesn't point
15 * to ground, unsafe on fixed wing, since position hold is
16 * implemented as continuous circular flying!
18 * @see The GNU Public License (GPL) Version 3
20 ******************************************************************************/
22 * This program is free software; you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation; either version 3 of the License, or
25 * (at your option) any later version.
27 * This program is distributed in the hope that it will be useful, but
28 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
29 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
32 * You should have received a copy of the GNU General Public License along
33 * with this program; if not, write to the Free Software Foundation, Inc.,
34 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
37 #include "inc/stateestimation.h"
39 #include <attitudesettings.h>
40 #include <attitudestate.h>
41 #include <flightstatus.h>
42 #include <homelocation.h>
43 #include <revocalibration.h>
45 #include <CoordinateConversions.h>
46 #include <pios_notify.h>
49 #define STACK_REQUIRED 512
51 #define CALIBRATION_DELAY_MS 4000
52 #define CALIBRATION_DURATION_MS 6000
53 #define RELOADSETTINGS_DELAY_MS 1000
54 #define CONVERGENCE_MAGKP 20.0f
55 #define VARIANCE_WINDOW_SIZE 40
63 AttitudeSettingsData attitudeSettings
;
64 HomeLocationData homeLocation
;
67 float currentAccel
[3];
69 float accels_filtered
[3];
70 float grot_filtered
[3];
75 bool accel_filter_enabled
;
76 float rollPitchBiasRate
;
82 pw_variance_t gyro_var
[3];
87 static FlightStatusData flightStatus
;
91 static int32_t initwithmag(stateFilter
*self
);
92 static int32_t initwithoutmag(stateFilter
*self
);
93 static int32_t maininit(stateFilter
*self
);
94 static filterResult
filter(stateFilter
*self
, stateEstimation
*state
);
95 static filterResult
complementaryFilter(struct data
*this, float gyro
[3], float accel
[3], float mag
[3], float attitude
[4]);
97 static void flightStatusUpdatedCb(UAVObjEvent
*ev
);
99 static void globalInit(void);
102 static void globalInit(void)
106 FlightStatusInitialize();
107 FlightStatusConnectCallback(&flightStatusUpdatedCb
);
108 flightStatusUpdatedCb(NULL
);
112 int32_t filterCFInitialize(stateFilter
*handle
)
115 handle
->init
= &initwithoutmag
;
116 handle
->filter
= &filter
;
117 handle
->localdata
= pios_malloc(sizeof(struct data
));
118 return STACK_REQUIRED
;
121 int32_t filterCFMInitialize(stateFilter
*handle
)
124 handle
->init
= &initwithmag
;
125 handle
->filter
= &filter
;
126 handle
->localdata
= pios_malloc(sizeof(struct data
));
127 return STACK_REQUIRED
;
130 static int32_t initwithmag(stateFilter
*self
)
132 struct data
*this = (struct data
*)self
->localdata
;
135 return maininit(self
);
138 static int32_t initwithoutmag(stateFilter
*self
)
140 struct data
*this = (struct data
*)self
->localdata
;
143 return maininit(self
);
146 static int32_t maininit(stateFilter
*self
)
148 struct data
*this = (struct data
*)self
->localdata
;
151 this->accelUpdated
= 0;
152 this->magCalibrated
= true;
153 AttitudeSettingsGet(&this->attitudeSettings
);
154 HomeLocationGet(&this->homeLocation
);
156 const float fakeDt
= 0.0025f
;
157 if (this->attitudeSettings
.AccelTau
< 0.0001f
) {
158 this->accel_alpha
= 0; // not trusting this to resolve to 0
159 this->accel_filter_enabled
= false;
161 this->accel_alpha
= expf(-fakeDt
/ this->attitudeSettings
.AccelTau
);
162 this->accel_filter_enabled
= true;
166 this->gyroBias
[0] = 0.0f
;
167 this->gyroBias
[1] = 0.0f
;
168 this->gyroBias
[2] = 0.0f
;
173 * Collect all required state variables, then run complementary filter
175 static filterResult
filter(stateFilter
*self
, stateEstimation
*state
)
177 struct data
*this = (struct data
*)self
->localdata
;
179 filterResult result
= FILTERRESULT_OK
;
181 if (IS_SET(state
->updated
, SENSORUPDATES_mag
)) {
182 this->magUpdated
= 1;
183 this->currentMag
[0] = state
->mag
[0];
184 this->currentMag
[1] = state
->mag
[1];
185 this->currentMag
[2] = state
->mag
[2];
187 if (IS_SET(state
->updated
, SENSORUPDATES_accel
)) {
188 this->accelUpdated
= 1;
189 this->currentAccel
[0] = state
->accel
[0];
190 this->currentAccel
[1] = state
->accel
[1];
191 this->currentAccel
[2] = state
->accel
[2];
193 if (IS_SET(state
->updated
, SENSORUPDATES_gyro
)) {
194 if (this->accelUpdated
) {
196 result
= complementaryFilter(this, state
->gyro
, this->currentAccel
, this->currentMag
, attitude
);
197 if (result
== FILTERRESULT_OK
) {
198 state
->attitude
[0] = attitude
[0];
199 state
->attitude
[1] = attitude
[1];
200 state
->attitude
[2] = attitude
[2];
201 state
->attitude
[3] = attitude
[3];
202 state
->updated
|= SENSORUPDATES_attitude
;
204 this->accelUpdated
= 0;
205 this->magUpdated
= 0;
212 static inline void apply_accel_filter(const struct data
*this, const float *raw
, float *filtered
)
214 if (this->accel_filter_enabled
) {
215 filtered
[0] = filtered
[0] * this->accel_alpha
+ raw
[0] * (1 - this->accel_alpha
);
216 filtered
[1] = filtered
[1] * this->accel_alpha
+ raw
[1] * (1 - this->accel_alpha
);
217 filtered
[2] = filtered
[2] * this->accel_alpha
+ raw
[2] * (1 - this->accel_alpha
);
219 filtered
[0] = raw
[0];
220 filtered
[1] = raw
[1];
221 filtered
[2] = raw
[2];
225 static filterResult
complementaryFilter(struct data
*this, float gyro
[3], float accel
[3], float mag
[3], float attitude
[4])
229 // During initialization and
230 if (this->first_run
) {
231 #if defined(PIOS_INCLUDE_HMC5X83)
232 // wait until mags have been updated
233 if (!this->magUpdated
&& this->useMag
) {
234 return FILTERRESULT_ERROR
;
242 pseudo_windowed_variance_init(&this->gyro_var
[0], VARIANCE_WINDOW_SIZE
);
243 pseudo_windowed_variance_init(&this->gyro_var
[1], VARIANCE_WINDOW_SIZE
);
244 pseudo_windowed_variance_init(&this->gyro_var
[2], VARIANCE_WINDOW_SIZE
);
247 RevoCalibrationmag_biasArrayGet(magBias
);
248 // don't trust Mag for initial orientation if it has not been calibrated
249 if (fabsf(magBias
[0]) < 1e-6f
&& fabsf(magBias
[1]) < 1e-6f
&& fabsf(magBias
[2]) < 1e-6f
) {
250 this->magCalibrated
= false;
256 AttitudeStateData attitudeState
; // base on previous state
257 AttitudeStateGet(&attitudeState
);
260 // Set initial attitude. Use accels to determine roll and pitch, rotate magnetic measurement accordingly,
261 // so pseudo "north" vector can be estimated even if the board is not level
262 attitudeState
.Roll
= atan2f(-accel
[1], -accel
[2]);
263 float zn
= cosf(attitudeState
.Roll
) * mag
[2] + sinf(attitudeState
.Roll
) * mag
[1];
264 float yn
= cosf(attitudeState
.Roll
) * mag
[1] - sinf(attitudeState
.Roll
) * mag
[2];
266 // rotate accels z vector according to roll
267 float azn
= cosf(attitudeState
.Roll
) * accel
[2] + sinf(attitudeState
.Roll
) * accel
[1];
268 attitudeState
.Pitch
= atan2f(accel
[0], -azn
);
270 float xn
= cosf(attitudeState
.Pitch
) * mag
[0] + sinf(attitudeState
.Pitch
) * zn
;
272 attitudeState
.Yaw
= atan2f(-yn
, xn
);
273 // TODO: This is still a hack
274 // Put this in a proper generic function in CoordinateConversion.c
275 // should take 4 vectors: g (0,0,-9.81), accels, Be (or 1,0,0 if no home loc) and magnetometers (or 1,0,0 if no mags)
276 // should calculate the rotation in 3d space using proper cross product math
277 // SUBTODO: formulate the math required
279 attitudeState
.Roll
= RAD2DEG(attitudeState
.Roll
);
280 attitudeState
.Pitch
= RAD2DEG(attitudeState
.Pitch
);
281 attitudeState
.Yaw
= RAD2DEG(attitudeState
.Yaw
);
283 RPY2Quaternion(&attitudeState
.Roll
, attitude
);
286 this->accels_filtered
[0] = 0.0f
;
287 this->accels_filtered
[1] = 0.0f
;
288 this->accels_filtered
[2] = 0.0f
;
289 this->grot_filtered
[0] = 0.0f
;
290 this->grot_filtered
[1] = 0.0f
;
291 this->grot_filtered
[2] = 0.0f
;
292 this->timeval
= PIOS_DELAY_GetRaw(); // Cycle counter used for precise timing
293 this->starttime
= xTaskGetTickCount(); // Tick counter used for long time intervals
295 return FILTERRESULT_OK
; // must return OK on initial initialization, so attitude will init with a valid quaternion
297 // check whether copter is steady
298 if (this->init
== UNDONE
&& this->attitudeSettings
.InitialZeroWhenBoardSteady
== ATTITUDESETTINGS_INITIALZEROWHENBOARDSTEADY_TRUE
) {
299 pseudo_windowed_variance_push_sample(&this->gyro_var
[0], gyro
[0]);
300 pseudo_windowed_variance_push_sample(&this->gyro_var
[1], gyro
[1]);
301 pseudo_windowed_variance_push_sample(&this->gyro_var
[2], gyro
[2]);
302 float const gyrovarx
= pseudo_windowed_variance_get(&this->gyro_var
[0]);
303 float const gyrovary
= pseudo_windowed_variance_get(&this->gyro_var
[1]);
304 float const gyrovarz
= pseudo_windowed_variance_get(&this->gyro_var
[2]);
306 if ((fabsf(gyrovarx
) + fabsf(gyrovary
) + fabsf(gyrovarz
)) > this->attitudeSettings
.BoardSteadyMaxVariance
) {
307 this->starttime
= xTaskGetTickCount();
309 return FILTERRESULT_WARNING
;
314 if (this->init
== UNDONE
&& xTaskGetTickCount() - this->starttime
< CALIBRATION_DELAY_MS
/ portTICK_RATE_MS
) {
315 // wait 4 seconds for the user to get his hands off in case the board was just powered
316 this->timeval
= PIOS_DELAY_GetRaw();
317 return FILTERRESULT_ERROR
;
318 } else if (this->init
== UNDONE
&& xTaskGetTickCount() - this->starttime
< (CALIBRATION_DELAY_MS
+ CALIBRATION_DURATION_MS
) / portTICK_RATE_MS
) {
319 // For first 6 seconds use accels to get gyro bias
320 this->attitudeSettings
.AccelKp
= 1.0f
;
321 this->attitudeSettings
.AccelKi
= 0.0f
;
322 this->attitudeSettings
.YawBiasRate
= 0.23f
;
323 this->accel_filter_enabled
= false;
324 this->rollPitchBiasRate
= 0.01f
;
325 this->attitudeSettings
.MagKp
= this->magCalibrated
? CONVERGENCE_MAGKP
: 0.0f
;
326 PIOS_NOTIFY_StartNotification(NOTIFY_DRAW_ATTENTION
, NOTIFY_PRIORITY_REGULAR
);
327 } else if ((this->attitudeSettings
.ZeroDuringArming
== ATTITUDESETTINGS_ZERODURINGARMING_TRUE
) && (flightStatus
.Armed
== FLIGHTSTATUS_ARMED_ARMING
)) {
328 this->attitudeSettings
.AccelKp
= 1.0f
;
329 this->attitudeSettings
.AccelKi
= 0.0f
;
330 this->attitudeSettings
.YawBiasRate
= 0.23f
;
331 this->accel_filter_enabled
= false;
332 this->rollPitchBiasRate
= 0.01f
;
333 this->attitudeSettings
.MagKp
= this->magCalibrated
? CONVERGENCE_MAGKP
: 0.0f
;
335 PIOS_NOTIFY_StartNotification(NOTIFY_DRAW_ATTENTION
, NOTIFY_PRIORITY_REGULAR
);
336 } else if (this->init
== UNDONE
) {
337 this->rollPitchBiasRate
= 0.0f
;
338 if (this->accel_alpha
> 0.0f
) {
339 this->accel_filter_enabled
= true;
341 this->inittime
= xTaskGetTickCount();
343 // Allow running filter with custom MagKp for some time before reload settings
344 } else if (this->init
== DONE
&& (!this->magCalibrated
|| (xTaskGetTickCount() - this->inittime
> RELOADSETTINGS_DELAY_MS
/ portTICK_RATE_MS
))) {
345 // Reload settings (all the rates)
346 AttitudeSettingsGet(&this->attitudeSettings
);
350 // Compute the dT using the cpu clock
351 dT
= PIOS_DELAY_DiffuS(this->timeval
) / 1000000.0f
;
352 this->timeval
= PIOS_DELAY_GetRaw();
353 if (dT
< 0.001f
) { // safe bounds
357 AttitudeStateData attitudeState
; // base on previous state
358 AttitudeStateGet(&attitudeState
);
360 // Get the current attitude estimate
361 quat_copy(&attitudeState
.q1
, attitude
);
363 // Apply smoothing to accel values, to reduce vibration noise before main calculations.
364 apply_accel_filter(this, accel
, this->accels_filtered
);
366 // Rotate gravity to body frame and cross with accels
368 grot
[0] = -(2.0f
* (attitude
[1] * attitude
[3] - attitude
[0] * attitude
[2]));
369 grot
[1] = -(2.0f
* (attitude
[2] * attitude
[3] + attitude
[0] * attitude
[1]));
370 grot
[2] = -(attitude
[0] * attitude
[0] - attitude
[1] * attitude
[1] - attitude
[2] * attitude
[2] + attitude
[3] * attitude
[3]);
373 apply_accel_filter(this, grot
, this->grot_filtered
);
375 CrossProduct((const float *)this->accels_filtered
, (const float *)this->grot_filtered
, accel_err
);
377 // Account for accel magnitude
378 float accel_mag
= sqrtf(this->accels_filtered
[0] * this->accels_filtered
[0] + this->accels_filtered
[1] * this->accels_filtered
[1] + this->accels_filtered
[2] * this->accels_filtered
[2]);
379 if (accel_mag
< 1.0e-3f
) {
380 return FILTERRESULT_CRITICAL
; // safety feature copied from CC
383 // Account for filtered gravity vector magnitude
385 if (this->accel_filter_enabled
) {
386 grot_mag
= sqrtf(this->grot_filtered
[0] * this->grot_filtered
[0] + this->grot_filtered
[1] * this->grot_filtered
[1] + this->grot_filtered
[2] * this->grot_filtered
[2]);
390 if (grot_mag
< 1.0e-3f
) {
391 return FILTERRESULT_CRITICAL
;
394 accel_err
[0] /= (accel_mag
* grot_mag
);
395 accel_err
[1] /= (accel_mag
* grot_mag
);
396 accel_err
[2] /= (accel_mag
* grot_mag
);
398 float mag_err
[3] = { 0.0f
};
399 if (this->magUpdated
&& this->useMag
) {
400 // Rotate gravity to body frame and cross with accels
404 Quaternion2R(attitude
, Rbe
);
406 rot_mult(Rbe
, this->homeLocation
.Be
, brot
);
408 float mag_len
= sqrtf(mag
[0] * mag
[0] + mag
[1] * mag
[1] + mag
[2] * mag
[2]);
413 float bmag
= sqrtf(brot
[0] * brot
[0] + brot
[1] * brot
[1] + brot
[2] * brot
[2]);
418 // Only compute if neither vector is null
419 if (bmag
< 1.0f
|| mag_len
< 1.0f
) {
420 mag_err
[0] = mag_err
[1] = mag_err
[2] = 0.0f
;
422 CrossProduct((const float *)mag
, (const float *)brot
, mag_err
);
425 mag_err
[0] = mag_err
[1] = mag_err
[2] = 0.0f
;
428 // Correct rates based on integral coefficient
429 gyro
[0] -= this->gyroBias
[0];
430 gyro
[1] -= this->gyroBias
[1];
431 gyro
[2] -= this->gyroBias
[2];
433 // Accumulate integral of error. Scale here so that units are (deg/s) but Ki has units of s
434 this->gyroBias
[0] -= accel_err
[0] * this->attitudeSettings
.AccelKi
- gyro
[0] * this->rollPitchBiasRate
;
435 this->gyroBias
[1] -= accel_err
[1] * this->attitudeSettings
.AccelKi
- gyro
[1] * this->rollPitchBiasRate
;
437 this->gyroBias
[2] -= mag_err
[2] * this->attitudeSettings
.MagKi
- gyro
[2] * this->rollPitchBiasRate
;
439 this->gyroBias
[2] -= -gyro
[2] * this->rollPitchBiasRate
;
442 float gyrotmp
[3] = { gyro
[0], gyro
[1], gyro
[2] };
443 // Correct rates based on proportional coefficient
444 gyrotmp
[0] += accel_err
[0] * this->attitudeSettings
.AccelKp
/ dT
;
445 gyrotmp
[1] += accel_err
[1] * this->attitudeSettings
.AccelKp
/ dT
;
447 gyrotmp
[2] += accel_err
[2] * this->attitudeSettings
.AccelKp
/ dT
+ mag_err
[2] * this->attitudeSettings
.MagKp
/ dT
;
449 gyrotmp
[2] += accel_err
[2] * this->attitudeSettings
.AccelKp
/ dT
;
452 // Work out time derivative from INSAlgo writeup
453 // Also accounts for the fact that gyros are in deg/s
455 qdot
[0] = DEG2RAD(-attitude
[1] * gyrotmp
[0] - attitude
[2] * gyrotmp
[1] - attitude
[3] * gyrotmp
[2]) * dT
/ 2;
456 qdot
[1] = DEG2RAD(attitude
[0] * gyrotmp
[0] - attitude
[3] * gyrotmp
[1] + attitude
[2] * gyrotmp
[2]) * dT
/ 2;
457 qdot
[2] = DEG2RAD(attitude
[3] * gyrotmp
[0] + attitude
[0] * gyrotmp
[1] - attitude
[1] * gyrotmp
[2]) * dT
/ 2;
458 qdot
[3] = DEG2RAD(-attitude
[2] * gyrotmp
[0] + attitude
[1] * gyrotmp
[1] + attitude
[0] * gyrotmp
[2]) * dT
/ 2;
461 attitude
[0] = attitude
[0] + qdot
[0];
462 attitude
[1] = attitude
[1] + qdot
[1];
463 attitude
[2] = attitude
[2] + qdot
[2];
464 attitude
[3] = attitude
[3] + qdot
[3];
466 if (attitude
[0] < 0.0f
) {
467 attitude
[0] = -attitude
[0];
468 attitude
[1] = -attitude
[1];
469 attitude
[2] = -attitude
[2];
470 attitude
[3] = -attitude
[3];
474 float inv_qmag
= invsqrtf(attitude
[0] * attitude
[0] + attitude
[1] * attitude
[1] + attitude
[2] * attitude
[2] + attitude
[3] * attitude
[3]);
475 attitude
[0] = attitude
[0] * inv_qmag
;
476 attitude
[1] = attitude
[1] * inv_qmag
;
477 attitude
[2] = attitude
[2] * inv_qmag
;
478 attitude
[3] = attitude
[3] * inv_qmag
;
480 // If quaternion has become inappropriately short or is nan reinit.
481 // THIS SHOULD NEVER ACTUALLY HAPPEN
482 if ((fabsf(inv_qmag
) > 1e3f
) || isnan(inv_qmag
)) {
484 return FILTERRESULT_WARNING
;
487 if (this->init
!= UNDONE
) {
488 return FILTERRESULT_OK
;
490 return FILTERRESULT_CRITICAL
; // return "critical" for now, so users can see the zeroing period, switch to more graceful notification later
494 static void flightStatusUpdatedCb(__attribute__((unused
)) UAVObjEvent
*ev
)
496 FlightStatusGet(&flightStatus
);