-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathuNavINS.cpp
431 lines (386 loc) · 15.4 KB
/
uNavINS.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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
/*
uNavINS.cpp
Original Author:
Adhika Lie
2012-10-08
University of Minnesota
Aerospace Engineering and Mechanics
Copyright 2011 Regents of the University of Minnesota. All rights reserved.
Updated to be a class, use Eigen, and compile as an Arduino library.
Added methods to get gyro and accel bias. Added initialization to
estimated angles rather than assuming IMU is level. Added method to get psi,
rather than just heading, and ground track.
Brian R Taylor
2017-12-20
Bolder Flight Systems
Copyright 2017 Bolder Flight Systems
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "uNavINS.h"
void uNavINS::update(unsigned long TOW,double vn,double ve,double vd,double lat,double lon,double alt,float p,float q,float r,float ax,float ay,float az,float hx,float hy, float hz) {
if (!initialized) {
// initial attitude and heading
theta = asinf(ax/G);
phi = asinf(-ay/(G*cosf(theta)));
// magnetic heading correction due to roll and pitch angle
Bxc = hx*cosf(theta) + (hy*sinf(phi) + hz*cosf(phi))*sinf(theta);
Byc = hy*cosf(phi) - hz*sinf(phi);
// finding initial heading
if (-Byc > 0) {
psi = M_PI/2.0f - atanf(Bxc/-Byc);
} else {
psi= 3.0f*M_PI/2.0f - atanf(Bxc/-Byc);
}
psi = constrainAngle180(psi);
psi_initial = psi;
// euler to quaternion
quat(0) = cosf(psi/2.0f)*cosf(theta/2.0f)*cosf(phi/2.0f) + sinf(psi/2.0f)*sinf(theta/2.0f)*sinf(phi/2.0f);
quat(1) = cosf(psi/2.0f)*cosf(theta/2.0f)*sinf(phi/2.0f) - sinf(psi/2.0f)*sinf(theta/2.0f)*cosf(phi/2.0f);
quat(2) = cosf(psi/2.0f)*sinf(theta/2.0f)*cosf(phi/2.0f) + sinf(psi/2.0f)*cosf(theta/2.0f)*sinf(phi/2.0f);
quat(3) = sinf(psi/2.0f)*cosf(theta/2.0f)*cosf(phi/2.0f) - cosf(psi/2.0f)*sinf(theta/2.0f)*sinf(phi/2.0f);
// Assemble the matrices
// ... gravity
grav(2,0) = G;
// ... H
H.block(0,0,5,5) = Eigen::Matrix<float,5,5>::Identity();
// // ... Rw
// Rw.block(0,0,3,3) = powf(SIG_W_A,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// Rw.block(3,3,3,3) = powf(SIG_W_G,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// Rw.block(6,6,3,3) = 2.0f*powf(SIG_A_D,2.0f)/TAU_A*Eigen::Matrix<float,3,3>::Identity();
// Rw.block(9,9,3,3) = 2.0f*powf(SIG_G_D,2.0f)/TAU_G*Eigen::Matrix<float,3,3>::Identity();
// // ... P
// P.block(0,0,3,3) = powf(P_P_INIT,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// P.block(3,3,3,3) = powf(P_V_INIT,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// P.block(6,6,2,2) = powf(P_A_INIT,2.0f)*Eigen::Matrix<float,2,2>::Identity();
// P(8,8) = powf(P_HDG_INIT,2.0f);
// P.block(9,9,3,3) = powf(P_AB_INIT,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// P.block(12,12,3,3) = powf(P_GB_INIT,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// // ... R
// R.block(0,0,2,2) = powf(SIG_GPS_P_NE,2.0f)*Eigen::Matrix<float,2,2>::Identity();
// R(2,2) = powf(SIG_GPS_P_D,2.0f);
// R.block(3,3,3,3) = powf(SIG_GPS_V,2.0f)*Eigen::Matrix<float,3,3>::Identity();
// ... Rw
Rw(0,0) = SIG_W_A*SIG_W_A; Rw(1,1) = SIG_W_A*SIG_W_A; Rw(2,2) = SIG_W_A*SIG_W_A;
Rw(3,3) = SIG_W_G*SIG_W_G; Rw(4,4) = SIG_W_G*SIG_W_G; Rw(5,5) = SIG_W_G*SIG_W_G;
Rw(6,6) = 2.0f*SIG_A_D*SIG_A_D/TAU_A; Rw(7,7) = 2.0f*SIG_A_D*SIG_A_D/TAU_A; Rw(8,8) = 2.0f*SIG_A_D*SIG_A_D/TAU_A;
Rw(9,9) = 2.0f*SIG_G_D*SIG_G_D/TAU_G; Rw(10,10) = 2.0f*SIG_G_D*SIG_G_D/TAU_G; Rw(11,11) = 2.0f*SIG_G_D*SIG_G_D/TAU_G;
// ... P
P(0,0) = P_P_INIT*P_P_INIT; P(1,1) = P_P_INIT*P_P_INIT; P(2,2) = P_P_INIT*P_P_INIT;
P(3,3) = P_V_INIT*P_V_INIT; P(4,4) = P_V_INIT*P_V_INIT; P(5,5) = P_V_INIT*P_V_INIT;
P(6,6) = P_A_INIT*P_A_INIT; P(7,7) = P_A_INIT*P_A_INIT; P(8,8) = P_HDG_INIT*P_HDG_INIT;
P(9,9) = P_AB_INIT*P_AB_INIT; P(10,10) = P_AB_INIT*P_AB_INIT; P(11,11) = P_AB_INIT*P_AB_INIT;
P(12,12) = P_GB_INIT*P_GB_INIT; P(13,13) = P_GB_INIT*P_GB_INIT; P(14,14) = P_GB_INIT*P_GB_INIT;
// ... R
R(0,0) = SIG_GPS_P_NE*SIG_GPS_P_NE; R(1,1) = SIG_GPS_P_NE*SIG_GPS_P_NE; R(2,2) = SIG_GPS_P_D*SIG_GPS_P_D;
R(3,3) = SIG_GPS_V*SIG_GPS_V; R(4,4) = SIG_GPS_V*SIG_GPS_V; R(5,5) = SIG_GPS_V*SIG_GPS_V;
// .. then initialize states with GPS Data
lat_ins = lat;
lon_ins = lon;
alt_ins = alt;
vn_ins = vn;
ve_ins = ve;
vd_ins = vd;
// specific force
f_b(0,0) = ax;
f_b(1,0) = ay;
f_b(2,0) = az;
/* initialize the time */
_t = 0;
// initialized flag
initialized = true;
} else {
// get the change in time
_dt = (float)_t;
_t = 0;
lla_ins(0,0) = lat_ins;
lla_ins(1,0) = lon_ins;
lla_ins(2,0) = alt_ins;
V_ins(0,0) = vn_ins;
V_ins(1,0) = ve_ins;
V_ins(2,0) = vd_ins;
// AHRS Transformations
C_N2B = quat2dcm(quat);
C_B2N = C_N2B.transpose();
// Attitude Update
dq(0) = 1.0f;
dq(1) = 0.5f*om_ib(0,0)*_dt;
dq(2) = 0.5f*om_ib(1,0)*_dt;
dq(3) = 0.5f*om_ib(2,0)*_dt;
quat = qmult(quat,dq);
quat.normalize();
// Avoid quaternion flips sign
if (quat(0) < 0) {
quat = -1.0f*quat;
}
// obtain euler angles from quaternion
theta = asinf(-2.0f*(quat(1,0)*quat(3,0)-quat(0,0)*quat(2,0)));
phi = atan2f(2.0f*(quat(0,0)*quat(1,0)+quat(2,0)*quat(3,0)),1.0f-2.0f*(quat(1,0)*quat(1,0)+quat(2,0)*quat(2,0)));
psi = atan2f(2.0f*(quat(1,0)*quat(2,0)+quat(0,0)*quat(3,0)),1.0f-2.0f*(quat(2,0)*quat(2,0)+quat(3,0)*quat(3,0)));
// Velocity Update
dx = C_B2N*f_b + grav;
vn_ins += _dt*dx(0,0);
ve_ins += _dt*dx(1,0);
vd_ins += _dt*dx(2,0);
// Position Update
dxd = llarate(V_ins,lla_ins);
lat_ins += _dt*dxd(0,0);
lon_ins += _dt*dxd(1,0);
alt_ins += _dt*dxd(2,0);
// Jacobian
Fs.setZero();
// ... pos2gs
Fs.block(0,3,3,3) = Eigen::Matrix<float,3,3>::Identity();
// ... gs2pos
Fs(5,2) = -2.0f*G/EARTH_RADIUS;
// ... gs2att
Fs.block(3,6,3,3) = -2.0f*C_B2N*sk(f_b);
// ... gs2acc
Fs.block(3,9,3,3) = -C_B2N;
// ... att2att
Fs.block(6,6,3,3) = -sk(om_ib);
// ... att2gyr
Fs.block(6,12,3,3) = -0.5f*Eigen::Matrix<float,3,3>::Identity();
// ... Accel Markov Bias
Fs.block(9,9,3,3) = -1.0f/TAU_A*Eigen::Matrix<float,3,3>::Identity();
Fs.block(12,12,3,3) = -1.0f/TAU_G*Eigen::Matrix<float,3,3>::Identity();
// State Transition Matrix
PHI = Eigen::Matrix<float,15,15>::Identity()+Fs*_dt;
// Process Noise
Gs.setZero();
Gs.block(3,0,3,3) = -C_B2N;
Gs.block(6,3,3,3) = -0.5f*Eigen::Matrix<float,3,3>::Identity();
Gs.block(9,6,6,6) = Eigen::Matrix<float,6,6>::Identity();
// Discrete Process Noise
Q = PHI*_dt*Gs*Rw*Gs.transpose();
Q = 0.5f*(Q+Q.transpose());
// Covariance Time Update
P = PHI*P*PHI.transpose()+Q;
P = 0.5f*(P+P.transpose());
// Gps measurement update
if ((TOW - previousTOW) > 0) {
previousTOW = TOW;
lla_gps(0,0) = lat;
lla_gps(1,0) = lon;
lla_gps(2,0) = alt;
V_gps(0,0) = vn;
V_gps(1,0) = ve;
V_gps(2,0) = vd;
lla_ins(0,0) = lat_ins;
lla_ins(1,0) = lon_ins;
lla_ins(2,0) = alt_ins;
V_ins(0,0) = vn_ins;
V_ins(1,0) = ve_ins;
V_ins(2,0) = vd_ins;
// Position, converted to NED
pos_ecef_ins = lla2ecef(lla_ins);
pos_ned_ins = ecef2ned(pos_ecef_ins,lla_ins);
pos_ecef_gps = lla2ecef(lla_gps);
pos_ned_gps = ecef2ned(pos_ecef_gps,lla_ins);
// Create measurement Y
y(0,0) = (float)(pos_ned_gps(0,0) - pos_ned_ins(0,0));
y(1,0) = (float)(pos_ned_gps(1,0) - pos_ned_ins(1,0));
y(2,0) = (float)(pos_ned_gps(2,0) - pos_ned_ins(2,0));
y(3,0) = (float)(V_gps(0,0) - V_ins(0,0));
y(4,0) = (float)(V_gps(1,0) - V_ins(1,0));
y(5,0) = (float)(V_gps(2,0) - V_ins(2,0));
// Kalman gain
K = P*H.transpose()*(H*P*H.transpose() + R).inverse();
// Covariance update
P = (Eigen::Matrix<float,15,15>::Identity()-K*H)*P*(Eigen::Matrix<float,15,15>::Identity()-K*H).transpose() + K*R*K.transpose();
// State update
x = K*y;
denom = (1.0 - (ECC2 * pow(sin(lla_ins(0,0)),2.0)));
denom = sqrt(denom*denom);
Re = EARTH_RADIUS / sqrt(denom);
Rn = EARTH_RADIUS*(1.0-ECC2) / denom*sqrt(denom);
alt_ins = alt_ins - x(2,0);
lat_ins = lat_ins + x(0,0) / (Re + alt_ins);
lon_ins = lon_ins + x(1,0) / (Rn + alt_ins) / cos(lat_ins);
vn_ins = vn_ins + x(3,0);
ve_ins = ve_ins + x(4,0);
vd_ins = vd_ins + x(5,0);
// Attitude correction
dq(0,0) = 1.0f;
dq(1,0) = x(6,0);
dq(2,0) = x(7,0);
dq(3,0) = x(8,0);
quat = qmult(quat,dq);
quat.normalize();
// obtain euler angles from quaternion
theta = asinf(-2.0f*(quat(1,0)*quat(3,0)-quat(0,0)*quat(2,0)));
phi = atan2f(2.0f*(quat(0,0)*quat(1,0)+quat(2,0)*quat(3,0)),1.0f-2.0f*(quat(1,0)*quat(1,0)+quat(2,0)*quat(2,0)));
psi = atan2f(2.0f*(quat(1,0)*quat(2,0)+quat(0,0)*quat(3,0)),1.0f-2.0f*(quat(2,0)*quat(2,0)+quat(3,0)*quat(3,0)));
abx = abx + x(9,0);
aby = aby + x(10,0);
abz = abz + x(11,0);
gbx = gbx + x(12,0);
gby = gby + x(13,0);
gbz = gbz + x(14,0);
}
// Get the new Specific forces and Rotation Rate,
// use in the next time update
f_b(0,0) = ax - abx;
f_b(1,0) = ay - aby;
f_b(2,0) = az - abz;
om_ib(0,0) = p - gbx;
om_ib(1,0) = q - gby;
om_ib(2,0) = r - gbz;
}
}
// returns the pitch angle, rad
float uNavINS::getPitch_rad() {
return theta;
}
// returns the roll angle, rad
float uNavINS::getRoll_rad() {
return phi;
}
// returns the yaw angle, rad
float uNavINS::getYaw_rad() {
return constrainAngle180(psi-psi_initial);
}
// returns the heading angle, rad
float uNavINS::getHeading_rad() {
return constrainAngle360(psi);
}
// returns the INS latitude, rad
double uNavINS::getLatitude_rad() {
return lat_ins;
}
// returns the INS longitude, rad
double uNavINS::getLongitude_rad() {
return lon_ins;
}
// returns the INS altitude, m
double uNavINS::getAltitude_m() {
return alt_ins;
}
// returns the INS north velocity, m/s
double uNavINS::getVelNorth_ms() {
return vn_ins;
}
// returns the INS east velocity, m/s
double uNavINS::getVelEast_ms() {
return ve_ins;
}
// returns the INS down velocity, m/s
double uNavINS::getVelDown_ms() {
return vd_ins;
}
// returns the INS ground track, rad
float uNavINS::getGroundTrack_rad() {
return atan2f((float)ve_ins,(float)vn_ins);
}
// returns the gyro bias estimate in the x direction, rad/s
float uNavINS::getGyroBiasX_rads() {
return gbx;
}
// returns the gyro bias estimate in the y direction, rad/s
float uNavINS::getGyroBiasY_rads() {
return gby;
}
// returns the gyro bias estimate in the z direction, rad/s
float uNavINS::getGyroBiasZ_rads() {
return gbz;
}
// returns the accel bias estimate in the x direction, m/s/s
float uNavINS::getAccelBiasX_mss() {
return abx;
}
// returns the accel bias estimate in the y direction, m/s/s
float uNavINS::getAccelBiasY_mss() {
return aby;
}
// returns the accel bias estimate in the z direction, m/s/s
float uNavINS::getAccelBiasZ_mss() {
return abz;
}
// This function gives a skew symmetric matrix from a given vector w
Eigen::Matrix<float,3,3> uNavINS::sk(Eigen::Matrix<float,3,1> w) {
Eigen::Matrix<float,3,3> C;
C(0,0) = 0.0f; C(0,1) = -w(2,0); C(0,2) = w(1,0);
C(1,0) = w(2,0); C(1,1) = 0.0f; C(1,2) = -w(0,0);
C(2,0) = -w(1,0); C(2,1) = w(0,0); C(2,2) = 0.0f;
return C;
}
// This function calculates the rate of change of latitude, longitude, and altitude.
Eigen::Matrix<double,3,1> uNavINS::llarate(Eigen::Matrix<double,3,1> V,Eigen::Matrix<double,3,1> lla) {
double Rew, Rns, denom;
Eigen::Matrix<double,3,1> lla_dot;
denom = (1.0 - (ECC2 * pow(sin(lla(0,0)),2.0)));
denom = sqrt(denom*denom);
Rew = EARTH_RADIUS / sqrt(denom);
Rns = EARTH_RADIUS*(1.0-ECC2) / denom*sqrt(denom);
lla_dot(0,0) = V(0,0)/(Rns + lla(2,0));
lla_dot(1,0) = V(1,0)/((Rew + lla(2,0))*cos(lla(0,0)));
lla_dot(2,0) = -V(2,0);
return lla_dot;
}
// This function calculates the ECEF Coordinate given the Latitude, Longitude and Altitude.
Eigen::Matrix<double,3,1> uNavINS::lla2ecef(Eigen::Matrix<double,3,1> lla) {
double Rew, denom;
Eigen::Matrix<double,3,1> ecef;
denom = (1.0 - (ECC2 * pow(sin(lla(0,0)),2.0)));
denom = sqrt(denom*denom);
Rew = EARTH_RADIUS / sqrt(denom);
ecef(0,0) = (Rew + lla(2,0)) * cos(lla(0,0)) * cos(lla(1,0));
ecef(1,0) = (Rew + lla(2,0)) * cos(lla(0,0)) * sin(lla(1,0));
ecef(2,0) = (Rew * (1.0 - ECC2) + lla(2,0)) * sin(lla(0,0));
return ecef;
}
// This function converts a vector in ecef to ned coordinate centered at pos_ref.
Eigen::Matrix<double,3,1> uNavINS::ecef2ned(Eigen::Matrix<double,3,1> ecef,Eigen::Matrix<double,3,1> pos_ref) {
Eigen::Matrix<double,3,1> ned;
ned(2,0)=-cos(pos_ref(0,0))*cos(pos_ref(1,0))*ecef(0,0)-cos(pos_ref(0,0))*sin(pos_ref(1,0))*ecef(1,0)-sin(pos_ref(0,0))*ecef(2,0);
ned(1,0)=-sin(pos_ref(1,0))*ecef(0,0) + cos(pos_ref(1,0))*ecef(1,0);
ned(0,0)=-sin(pos_ref(0,0))*cos(pos_ref(1,0))*ecef(0,0)-sin(pos_ref(0,0))*sin(pos_ref(1,0))*ecef(1,0)+cos(pos_ref(0,0))*ecef(2,0);
return ned;
}
// quaternion to dcm
Eigen::Matrix<float,3,3> uNavINS::quat2dcm(Eigen::Matrix<float,4,1> q) {
Eigen::Matrix<float,3,3> C_N2B;
C_N2B(0,0) = 2.0f*powf(q(0,0),2.0f)-1.0f + 2.0f*powf(q(1,0),2.0f);
C_N2B(1,1) = 2.0f*powf(q(0,0),2.0f)-1.0f + 2.0f*powf(q(2,0),2.0f);
C_N2B(2,2) = 2.0f*powf(q(0,0),2.0f)-1.0f + 2.0f*powf(q(3,0),2.0f);
C_N2B(0,1) = 2.0f*q(1,0)*q(2,0) + 2.0f*q(0,0)*q(3,0);
C_N2B(0,2) = 2.0f*q(1,0)*q(3,0) - 2.0f*q(0,0)*q(2,0);
C_N2B(1,0) = 2.0f*q(1,0)*q(2,0) - 2.0f*q(0,0)*q(3,0);
C_N2B(1,2) = 2.0f*q(2,0)*q(3,0) + 2.0f*q(0,0)*q(1,0);
C_N2B(2,0) = 2.0f*q(1,0)*q(3,0) + 2.0f*q(0,0)*q(2,0);
C_N2B(2,1) = 2.0f*q(2,0)*q(3,0) - 2.0f*q(0,0)*q(1,0);
return C_N2B;
}
// quaternion multiplication
Eigen::Matrix<float,4,1> uNavINS::qmult(Eigen::Matrix<float,4,1> p, Eigen::Matrix<float,4,1> q) {
Eigen::Matrix<float,4,1> r;
r(0,0) = p(0,0)*q(0,0) - (p(1,0)*q(1,0) + p(2,0)*q(2,0) + p(3,0)*q(3,0));
r(1,0) = p(0,0)*q(1,0) + q(0,0)*p(1,0) + p(2,0)*q(3,0) - p(3,0)*q(2,0);
r(2,0) = p(0,0)*q(2,0) + q(0,0)*p(2,0) + p(3,0)*q(1,0) - p(1,0)*q(3,0);
r(3,0) = p(0,0)*q(3,0) + q(0,0)*p(3,0) + p(1,0)*q(2,0) - p(2,0)*q(1,0);
return r;
}
// bound yaw angle between -180 and 180
float uNavINS::constrainAngle180(float dta) {
if(dta > M_PI) dta -= (M_PI*2.0f);
if(dta < -M_PI) dta += (M_PI*2.0f);
return dta;
}
// bound heading angle between 0 and 360
float uNavINS::constrainAngle360(float dta){
dta = fmod(dta,2.0f*M_PI);
if (dta < 0)
dta += 2.0f*M_PI;
return dta;
}