forked from multiwii/multiwii-firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
IMU.cpp
355 lines (310 loc) · 11.7 KB
/
IMU.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#include "Arduino.h"
#include "config.h"
#include "def.h"
#include "types.h"
#include "MultiWii.h"
#include "IMU.h"
#include "Sensors.h"
void getEstimatedAttitude();
void computeIMU () {
uint8_t axis;
static int16_t gyroADCprevious[3] = {0,0,0};
static int16_t gyroADCinter[3];
uint16_t timeInterleave = 0;
#if ACC
ACC_getADC();
getEstimatedAttitude();
#endif
#if GYRO
Gyro_getADC();
#endif
for (axis = 0; axis < 3; axis++)
gyroADCinter[axis] = imu.gyroADC[axis];
timeInterleave=micros();
annexCode();
uint8_t t=0;
while((int16_t)(micros()-timeInterleave)<650) t=1; //empirical, interleaving delay between 2 consecutive reads
#ifdef LCD_TELEMETRY
if (!t) annex650_overrun_count++;
#endif
#if GYRO
Gyro_getADC();
#endif
for (axis = 0; axis < 3; axis++) {
gyroADCinter[axis] = imu.gyroADC[axis]+gyroADCinter[axis];
// empirical, we take a weighted value of the current and the previous values
imu.gyroData[axis] = (gyroADCinter[axis]+gyroADCprevious[axis])/3;
gyroADCprevious[axis] = gyroADCinter[axis]>>1;
if (!ACC) imu.accADC[axis]=0;
}
#if defined(GYRO_SMOOTHING)
static int16_t gyroSmooth[3] = {0,0,0};
for (axis = 0; axis < 3; axis++) {
imu.gyroData[axis] = (int16_t) ( ( (int32_t)((int32_t)gyroSmooth[axis] * (conf.Smoothing[axis]-1) )+imu.gyroData[axis]+1 ) / conf.Smoothing[axis]);
gyroSmooth[axis] = imu.gyroData[axis];
}
#elif defined(TRI)
static int16_t gyroYawSmooth = 0;
imu.gyroData[YAW] = (gyroYawSmooth*2+imu.gyroData[YAW])/3;
gyroYawSmooth = imu.gyroData[YAW];
#endif
}
// **************************************************
// Simplified IMU based on "Complementary Filter"
// Inspired by http://starlino.com/imu_guide.html
//
// adapted by ziss_dm : http://www.multiwii.com/forum/viewtopic.php?f=8&t=198
//
// The following ideas was used in this project:
// 1) Rotation matrix: http://en.wikipedia.org/wiki/Rotation_matrix
// 2) Small-angle approximation: http://en.wikipedia.org/wiki/Small-angle_approximation
// 3) C. Hastings approximation for atan2()
// 4) Optimization tricks: http://www.hackersdelight.org/
//
// Currently Magnetometer uses separate CF which is used only
// for heading approximation.
//
// **************************************************
//****** advanced users settings *******************
/* Set the Low Pass Filter factor for ACC
Increasing this value would reduce ACC noise (visible in GUI), but would increase ACC lag time
Comment this if you do not want filter at all.
unit = n power of 2 */
// this one is also used for ALT HOLD calculation, should not be changed
#ifndef ACC_LPF_FACTOR
#define ACC_LPF_FACTOR 4 // that means a LPF of 16
#endif
/* Set the Gyro Weight for Gyro/Acc complementary filter
Increasing this value would reduce and delay Acc influence on the output of the filter*/
#ifndef GYR_CMPF_FACTOR
#define GYR_CMPF_FACTOR 10 // that means a CMP_FACTOR of 1024 (2^10)
#endif
/* Set the Gyro Weight for Gyro/Magnetometer complementary filter
Increasing this value would reduce and delay Magnetometer influence on the output of the filter*/
#define GYR_CMPFM_FACTOR 8 // that means a CMP_FACTOR of 256 (2^8)
typedef struct {
int32_t X,Y,Z;
} t_int32_t_vector_def;
typedef struct {
uint16_t XL; int16_t X;
uint16_t YL; int16_t Y;
uint16_t ZL; int16_t Z;
} t_int16_t_vector_def;
// note: we use implicit first 16 MSB bits 32 -> 16 cast. ie V32.X>>16 = V16.X
typedef union {
int32_t A32[3];
t_int32_t_vector_def V32;
int16_t A16[6];
t_int16_t_vector_def V16;
} t_int32_t_vector;
//return angle , unit: 1/10 degree
int16_t _atan2(int32_t y, int32_t x){
float z = y;
int16_t a;
uint8_t c;
c = abs(y) < abs(x);
if ( c ) {z = z / x;} else {z = x / z;}
a = 2046.43 * (z / (3.5714 + z * z));
if ( c ){
if (x<0) {
if (y<0) a -= 1800;
else a += 1800;
}
} else {
a = 900 - a;
if (y<0) a -= 1800;
}
return a;
}
float InvSqrt (float x){
union{
int32_t i;
float f;
} conv;
conv.f = x;
conv.i = 0x5f1ffff9 - (conv.i >> 1);
return conv.f * (1.68191409f - 0.703952253f * x * conv.f * conv.f);
}
// signed16 * signed16
// 22 cycles
// http://mekonik.wordpress.com/2009/03/18/arduino-avr-gcc-multiplication/
#define MultiS16X16to32(longRes, intIn1, intIn2) \
asm volatile ( \
"clr r26 \n\t" \
"mul %A1, %A2 \n\t" \
"movw %A0, r0 \n\t" \
"muls %B1, %B2 \n\t" \
"movw %C0, r0 \n\t" \
"mulsu %B2, %A1 \n\t" \
"sbc %D0, r26 \n\t" \
"add %B0, r0 \n\t" \
"adc %C0, r1 \n\t" \
"adc %D0, r26 \n\t" \
"mulsu %B1, %A2 \n\t" \
"sbc %D0, r26 \n\t" \
"add %B0, r0 \n\t" \
"adc %C0, r1 \n\t" \
"adc %D0, r26 \n\t" \
"clr r1 \n\t" \
: \
"=&r" (longRes) \
: \
"a" (intIn1), \
"a" (intIn2) \
: \
"r26" \
)
int32_t __attribute__ ((noinline)) mul(int16_t a, int16_t b) {
int32_t r;
MultiS16X16to32(r, a, b);
//r = (int32_t)a*b; without asm requirement
return r;
}
// Rotate Estimated vector(s) with small angle approximation, according to the gyro data
void rotateV32( t_int32_t_vector *v,int16_t* delta) {
int16_t X = v->V16.X;
int16_t Y = v->V16.Y;
int16_t Z = v->V16.Z;
v->V32.Z -= mul(delta[ROLL] , X) + mul(delta[PITCH] , Y);
v->V32.X += mul(delta[ROLL] , Z) - mul(delta[YAW] , Y);
v->V32.Y += mul(delta[PITCH] , Z) + mul(delta[YAW] , X);
}
static int16_t accZ=0;
void getEstimatedAttitude(){
uint8_t axis;
int32_t accMag = 0;
float scale;
int16_t deltaGyroAngle16[3];
static t_int32_t_vector EstG = {0,0,(int32_t)ACC_1G<<16};
#if MAG
static t_int32_t_vector EstM;
#else
static t_int32_t_vector EstM = {0,(int32_t)1<<24,0};
#endif
static uint32_t LPFAcc[3];
float invG; // 1/|G|
static int16_t accZoffset = 0;
int32_t accZ_tmp=0;
static uint16_t previousT;
uint16_t currentT = micros();
// unit: radian per bit, scaled by 2^16 for further multiplication
// with a delta time of 3000 us, and GYRO scale of most gyros, scale = a little bit less than 1
scale = (currentT - previousT) * (GYRO_SCALE * 65536);
previousT = currentT;
// Initialization
for (axis = 0; axis < 3; axis++) {
// valid as long as LPF_FACTOR is less than 15
imu.accSmooth[axis] = LPFAcc[axis]>>ACC_LPF_FACTOR;
LPFAcc[axis] += imu.accADC[axis] - imu.accSmooth[axis];
// used to calculate later the magnitude of acc vector
accMag += mul(imu.accSmooth[axis] , imu.accSmooth[axis]);
// unit: radian scaled by 2^16
// imu.gyroADC[axis] is 14 bit long, the scale factor ensure deltaGyroAngle16[axis] is still 14 bit long
deltaGyroAngle16[axis] = imu.gyroADC[axis] * scale;
}
// we rotate the intermediate 32 bit vector with the radian vector (deltaGyroAngle16), scaled by 2^16
// however, only the first 16 MSB of the 32 bit vector is used to compute the result
// it is ok to use this approximation as the 16 LSB are used only for the complementary filter part
rotateV32(&EstG,deltaGyroAngle16);
rotateV32(&EstM,deltaGyroAngle16);
// Apply complimentary filter (Gyro drift correction)
// If accel magnitude >1.15G or <0.85G and ACC vector outside of the limit range => we neutralize the effect of accelerometers in the angle estimation.
// To do that, we just skip filter, as EstV already rotated by Gyro
for (axis = 0; axis < 3; axis++) {
if ( (int16_t)(0.85*ACC_1G*ACC_1G/256) < (int16_t)(accMag>>8) && (int16_t)(accMag>>8) < (int16_t)(1.15*ACC_1G*ACC_1G/256) )
EstG.A32[axis] += (int32_t)(imu.accSmooth[axis] - EstG.A16[2*axis+1])<<(16-GYR_CMPF_FACTOR);
accZ_tmp += mul(imu.accSmooth[axis] , EstG.A16[2*axis+1]);
#if MAG
EstM.A32[axis] += (int32_t)(imu.magADC[axis] - EstM.A16[2*axis+1])<<(16-GYR_CMPFM_FACTOR);
#endif
}
if (EstG.V16.Z > ACCZ_25deg)
f.SMALL_ANGLES_25 = 1;
else
f.SMALL_ANGLES_25 = 0;
// Attitude of the estimated vector
int32_t sqGX_sqGZ = mul(EstG.V16.X,EstG.V16.X) + mul(EstG.V16.Z,EstG.V16.Z);
invG = InvSqrt(sqGX_sqGZ + mul(EstG.V16.Y,EstG.V16.Y));
att.angle[ROLL] = _atan2(EstG.V16.X , EstG.V16.Z);
att.angle[PITCH] = _atan2(EstG.V16.Y , InvSqrt(sqGX_sqGZ)*sqGX_sqGZ);
//note on the second term: mathematically there is a risk of overflow (16*16*16=48 bits). assumed to be null with real values
att.heading = _atan2(
mul(EstM.V16.Z , EstG.V16.X) - mul(EstM.V16.X , EstG.V16.Z),
(EstM.V16.Y * sqGX_sqGZ - (mul(EstM.V16.X , EstG.V16.X) + mul(EstM.V16.Z , EstG.V16.Z)) * EstG.V16.Y)*invG );
#if MAG
att.heading += conf.mag_declination; // Set from GUI
#endif
att.heading /= 10;
#if defined(THROTTLE_ANGLE_CORRECTION)
cosZ = mul(EstG.V16.Z , 100) / ACC_1G ; // cos(angleZ) * 100
throttleAngleCorrection = THROTTLE_ANGLE_CORRECTION * constrain(100 - cosZ, 0, 100) >>3; // 16 bit ok: 200*150 = 30000
#endif
// projection of ACC vector to global Z, with 1G subtructed
// Math: accZ = A * G / |G| - 1G
accZ = accZ_tmp * invG;
if (!f.ARMED) {
accZoffset -= accZoffset>>3;
accZoffset += accZ;
}
accZ -= accZoffset>>3;
}
#define UPDATE_INTERVAL 25000 // 40hz update rate (20hz LPF on acc)
#define BARO_TAB_SIZE 21
#define ACC_Z_DEADBAND (ACC_1G>>5) // was 40 instead of 32 now
#define applyDeadband(value, deadband) \
if(abs(value) < deadband) { \
value = 0; \
} else if(value > 0){ \
value -= deadband; \
} else if(value < 0){ \
value += deadband; \
}
#if BARO
uint8_t getEstimatedAltitude(){
int32_t BaroAlt;
static float baroGroundTemperatureScale,logBaroGroundPressureSum;
static float vel = 0.0f;
static uint16_t previousT;
uint16_t currentT = micros();
uint16_t dTime;
dTime = currentT - previousT;
if (dTime < UPDATE_INTERVAL) return 0;
previousT = currentT;
if(calibratingB > 0) {
logBaroGroundPressureSum = log(baroPressureSum);
baroGroundTemperatureScale = ((int32_t)baroTemperature + 27315) * (2 * 29.271267f); // 2 * is included here => no need for * 2 on BaroAlt in additional LPF
calibratingB--;
}
// baroGroundPressureSum is not supposed to be 0 here
// see: https://code.google.com/p/ardupilot-mega/source/browse/libraries/AP_Baro/AP_Baro.cpp
BaroAlt = ( logBaroGroundPressureSum - log(baroPressureSum) ) * baroGroundTemperatureScale;
alt.EstAlt = (alt.EstAlt * 6 + BaroAlt ) >> 3; // additional LPF to reduce baro noise (faster by 30 µs)
#if (defined(VARIOMETER) && (VARIOMETER != 2)) || !defined(SUPPRESS_BARO_ALTHOLD)
//P
int16_t error16 = constrain(AltHold - alt.EstAlt, -300, 300);
applyDeadband(error16, 10); //remove small P parametr to reduce noise near zero position
BaroPID = constrain((conf.pid[PIDALT].P8 * error16 >>7), -150, +150);
//I
errorAltitudeI += conf.pid[PIDALT].I8 * error16 >>6;
errorAltitudeI = constrain(errorAltitudeI,-30000,30000);
BaroPID += errorAltitudeI>>9; //I in range +/-60
applyDeadband(accZ, ACC_Z_DEADBAND);
static int32_t lastBaroAlt;
// could only overflow with a difference of 320m, which is highly improbable here
int16_t baroVel = mul((alt.EstAlt - lastBaroAlt) , (1000000 / UPDATE_INTERVAL));
lastBaroAlt = alt.EstAlt;
baroVel = constrain(baroVel, -300, 300); // constrain baro velocity +/- 300cm/s
applyDeadband(baroVel, 10); // to reduce noise near zero
// Integrator - velocity, cm/sec
vel += accZ * ACC_VelScale * dTime;
// apply Complimentary Filter to keep the calculated velocity based on baro velocity (i.e. near real velocity).
// By using CF it's possible to correct the drift of integrated accZ (velocity) without loosing the phase, i.e without delay
vel = vel * 0.985f + baroVel * 0.015f;
//D
alt.vario = vel;
applyDeadband(alt.vario, 5);
BaroPID -= constrain(conf.pid[PIDALT].D8 * alt.vario >>4, -150, 150);
#endif
return 1;
}
#endif //BARO