Mercurial > public > hwos_code
annotate src/compass.c @ 620:cd986267a5ca
potential compass bug "fixed"
author | heinrichsweikamp |
---|---|
date | Fri, 22 Feb 2019 16:30:05 +0100 |
parents | ca4556fb60b9 |
children | 1ad0531e9078 |
rev | line source |
---|---|
282
7d9edd3b8c86
Make a more compact COMPASS calibration code (<7KB), and add more tests.
jDG
parents:
214
diff
changeset
|
1 ////////////////////////////////////////////////////////////////////////////// |
7d9edd3b8c86
Make a more compact COMPASS calibration code (<7KB), and add more tests.
jDG
parents:
214
diff
changeset
|
2 /// compass.c |
604 | 3 /// Compute north direction from magnetic/acceleration measures |
4 /// | |
282
7d9edd3b8c86
Make a more compact COMPASS calibration code (<7KB), and add more tests.
jDG
parents:
214
diff
changeset
|
5 /// Copyright (c) 2012-2015, JD Gascuel, HeinrichsWeikamp, all right reserved. |
0 | 6 ////////////////////////////////////////////////////////////////////////////// |
7 // HISTORY | |
8 // 2012-12-01 [jDG] Creation | |
9 // 2012-12-23 [jDG] Added filtering. | |
10 // 2012-12-30 [jDG] Added calibration (spherical best fit). | |
282
7d9edd3b8c86
Make a more compact COMPASS calibration code (<7KB), and add more tests.
jDG
parents:
214
diff
changeset
|
11 // 2015-05-22 [jDG] Minor cleanups. Smaller calibration code. |
0 | 12 |
13 #include "compass.h" | |
14 | |
15 ////////////////////////////////////////////////////////////////////////////// | |
604 | 16 // mH: crude work-around, needs to be fixed up |
0 | 17 #ifndef UNIX |
604 | 18 # pragma udata overlay bank8=0x800 |
19 static char C_STACK[256]; // overlay C-code data stack here | |
20 # define RESET_C_STACK \ | |
21 _asm \ | |
22 LFSR 1, 0x800 \ | |
23 LFSR 2, 0x800 \ | |
24 _endasm | |
25 # pragma udata overlay bank9_compass | |
26 # pragma code compass_run | |
0 | 27 #else |
604 | 28 # define RESET_C_STACK |
0 | 29 #endif |
30 | |
31 ////////////////////////////////////////////////////////////////////////////// | |
32 // fifth order of polynomial approximation of atan(), giving 0.05 deg max error | |
33 // | |
604 | 34 #define K1 (5701) // needs K1/2**16 |
35 #define K2 (1645) // needs K2/2**48 WAS NEGATIV | |
36 #define K3 ( 446) // needs K3/2**80 | |
37 | |
0 | 38 |
39 ////////////////////////////////////////////////////////////////////////////// | |
604 | 40 // Interface to assembler multiplies |
41 | |
0 | 42 Int16 umul(PARAMETER Int16 a, PARAMETER Int16 b) |
43 { | |
604 | 44 extern Int16 compass_umul(void); |
45 extern Int16 compass_a, compass_b; | |
46 compass_a = a; | |
47 compass_b = b; | |
48 return compass_umul(); | |
0 | 49 } |
50 | |
51 Int16 imul(PARAMETER Int16 a, PARAMETER Int16 b) | |
52 { | |
604 | 53 extern Int16 compass_imul(void); |
54 extern Int16 compass_a, compass_b; | |
55 compass_a = a; | |
56 compass_b = b; | |
57 return compass_imul(); | |
0 | 58 } |
59 | |
60 ////////////////////////////////////////////////////////////////////////////// | |
61 /// Returns a / b * 2**16 | |
62 /// | |
604 | 63 /// A 16/16 -> 16 bits divide, returning a scaled result. |
0 | 64 /// Used to multiply fractional numbers in the range 0..1, |
65 /// represented as 0..32767. | |
604 | 66 |
0 | 67 Int16 udiv(PARAMETER Int16 a, PARAMETER Int16 b) |
68 { | |
604 | 69 OVERLAY Int16 d, r; |
620 | 70 OVERLAY char failsafe=250; |
71 | |
604 | 72 //---- Pre-scale both numerator and denominator -------------------------- |
73 while( (((a>>8) | (b>>8)) & 0xC0) == 0 ) | |
74 { | |
620 | 75 failsafe--; |
76 if (failsafe==0) break; | |
604 | 77 a <<= 1; |
78 b <<= 1; | |
79 } | |
0 | 80 |
604 | 81 //---- Make division trials ---------------------------------------------- |
82 d = 0x4000; // start with 0.5, because 1.0 is sign bit | |
83 b >>= 1; // hence pre-shift b | |
84 r = 0; | |
85 do { | |
86 if( a >= b ) // a is big enough ? | |
87 { | |
88 a -= b; // then count d times b out of it | |
89 r |= d; // and accumulate that bit | |
90 } | |
91 b >>= 1; // then loop trying twice smaller | |
92 d >>= 1; | |
93 } while( b ); | |
94 return r; | |
0 | 95 } |
96 | |
97 ////////////////////////////////////////////////////////////////////////////// | |
98 /// Computes atan(y/x) in Angle, for x, y in range 0..32767 | |
99 /// | |
100 /// Results a single quadrant Angle, in the range 0 .. Q_PI/2 | |
604 | 101 |
0 | 102 Angle utan(PARAMETER Int16 y, PARAMETER Int16 x) |
103 { | |
604 | 104 OVERLAY Int16 ratio, angle, x2, x3; |
0 | 105 |
604 | 106 //---- Handle zero divisor ----------------------------------------------- |
107 if( x == 0 ) | |
108 return (y == 0) ? 0 : Q_PIO2; | |
0 | 109 |
604 | 110 //---- Make it half-quadrant : 0 .. 45 deg ------------------------------- |
111 ratio = (x > y) ? udiv(y, x) : udiv(x, y); | |
0 | 112 |
604 | 113 //---- Then apply the polynomial approximation --------------------------- |
114 angle = umul(K1, ratio); // r*K1 / 2**16 | |
115 x2 = umul(ratio, ratio); // r**2 / 2**16 | |
116 x3 = umul(x2, ratio); // r**3 / 2**32 | |
117 angle -= umul(x3, K2); // K2*r**3 / 2**48: NEGATIV. | |
0 | 118 |
604 | 119 x3 = umul(x3, x2); // r**5 / 2**64 |
120 angle += umul(x3, K3); // K3*r**5 / 2**80 | |
0 | 121 |
604 | 122 //---- Recover the full quadrant ----------------------------------------- |
123 return (x < y) ? (Angle)(Q_PIO2 - angle) | |
124 : (Angle)(angle); | |
0 | 125 } |
126 | |
127 ////////////////////////////////////////////////////////////////////////////// | |
128 /// Computes atan2(y/x) in Angle, for x, y in range -32768 to 32767 | |
129 /// | |
130 /// Results a four quadrant Angle, in the range -Q_PI .. +Q_PI | |
604 | 131 |
0 | 132 Angle itan(PARAMETER Int16 y, PARAMETER Int16 x) |
133 { | |
604 | 134 // Beware: -32768 is not properly handled (sign error) |
135 if( x == -32768 ) x = -32767; | |
136 if( y == -32768 ) y = -32767; | |
0 | 137 |
604 | 138 if( x >= 0 ) |
139 if( y >= 0 ) // first quadrant: 0..90 deg. | |
140 return utan(y,x); | |
141 else // fourth quadrant: 0..-90 deg | |
142 return -utan(-y,x); | |
143 else | |
144 if( y >= 0 ) // second quadrant: 90..180 deg | |
145 return Q_PI - utan(y, -x); | |
146 else // third quadrant: -90..-180 deg; | |
147 return -Q_PI + utan(-y, -x); | |
0 | 148 } |
149 | |
150 ////////////////////////////////////////////////////////////////////////////// | |
151 /// Computes cos(theta) = sqrtf(x2/h2), | |
152 /// when theta = atan(y/x) and h2=x*x+y*y | |
153 /// | |
604 | 154 |
0 | 155 Int16 cosxh(PARAMETER Int16 x2, PARAMETER Int16 h2) |
156 { | |
604 | 157 OVERLAY Int16 r = 0; |
158 OVERLAY Int16 d = 0x4000; | |
0 | 159 |
604 | 160 do { |
161 OVERLAY Int16 a = r + d; | |
162 a = umul(a, a); | |
163 a = umul(a, h2); | |
164 if( a <= x2 ) r += d; | |
165 d >>= 1; | |
166 } while( d ); | |
0 | 167 |
604 | 168 return r; |
0 | 169 } |
170 | |
171 ////////////////////////////////////////////////////////////////////////////// | |
172 /// Computes both sin and cos of angle y/x, | |
173 /// with h = sqrt(x**2+y**2). | |
174 /// | |
604 | 175 |
0 | 176 void sincos(PARAMETER Int16 x, PARAMETER Int16 y, Int16* sin, Int16* cos) |
177 { | |
604 | 178 OVERLAY Int16 x2, y2, h2; |
620 | 179 OVERLAY char failsafe = 250; |
0 | 180 |
604 | 181 //---- Fold into one quadant --------------------------------------------- |
182 OVERLAY char neg = 0; | |
183 if( x < 0 ) | |
184 { | |
185 neg |= 1; | |
186 x = -x; | |
187 } | |
188 if( y < 0 ) | |
189 { | |
190 neg |= 2; | |
191 y = -y; | |
192 } | |
0 | 193 |
604 | 194 //---- Pre-scale both numerator and denominator ---------------------- |
195 while( (((x>>8) | (y>>8)) & 0xE0) == 0 ) | |
196 { | |
620 | 197 failsafe--; |
198 if (failsafe == 0) break; | |
604 | 199 x <<= 1; |
200 y <<= 1; | |
201 } | |
0 | 202 |
604 | 203 //---- Uses trig() to do the stuff one on quadrant ------------------- |
204 x2 = umul(x,x); | |
205 y2 = umul(y,y); | |
206 h2 = x2 + y2; | |
207 x2 = cosxh(x2, h2); | |
0 | 208 |
604 | 209 //---- Results back in four quadrants -------------------------------- |
210 *cos = (neg & 1) ? -x2 : x2; | |
211 y2 = cosxh(y2, h2); | |
212 *sin = (neg & 2) ? -y2 : y2; | |
0 | 213 } |
214 | |
215 ////////////////////////////////////////////////////////////////////////////// | |
216 // | |
217 | |
218 void compass(void) | |
219 { | |
604 | 220 OVERLAY Int16 sin, cos; |
221 OVERLAY Int16 iBfx, iBfy, Gz; | |
222 OVERLAY Int16 iBpx, iBpy, iBpz; | |
223 | |
224 RESET_C_STACK; | |
0 | 225 |
604 | 226 //---- Make hard iron correction ----------------------------------------- |
227 // Measured magnetometer orientation, measured ok | |
228 // From matthias drawing: (X,Y,Z) --> (X,Y,Z) : no rotation | |
229 iBpx = compass_DX_f - compass_CX_f; // X | |
230 iBpy = compass_DY_f - compass_CY_f; // Y | |
231 iBpz = compass_DZ_f - compass_CZ_f; // Z | |
0 | 232 |
604 | 233 //---- Calculate sine and cosine of roll angle Phi ----------------------- |
234 sincos(accel_DZ_f, accel_DY_f, &sin, &cos); | |
0 | 235 |
604 | 236 //---- rotate by roll angle (-Phi) --------------------------------------- |
237 iBfy = imul(iBpy, cos) - imul(iBpz, sin); | |
238 iBpz = imul(iBpy, sin) + imul(iBpz, cos); | |
239 Gz = imul(accel_DY_f, sin) + imul(accel_DZ_f, cos); | |
0 | 240 |
604 | 241 //---- calculate sin and cosine of pitch angle Theta --------------------- |
242 sincos(Gz, -accel_DX_f, &sin, &cos); // NOTE: changed sin sign | |
0 | 243 |
604 | 244 /* correct cosine if pitch not in range -90 to 90 degrees */ |
245 if( cos < 0 ) cos = -cos; | |
0 | 246 |
604 | 247 ///---- de-rotate by pitch angle Theta ----------------------------------- |
248 iBfx = imul(iBpx, cos) + imul(iBpz, sin); | |
0 | 249 |
604 | 250 //---- Detect uncalibrated compass --------------------------------------- |
251 if( !compass_CX_f && !compass_CY_f && !compass_CZ_f ) | |
252 { | |
253 compass_heading = -1; | |
254 return; | |
255 } | |
0 | 256 |
604 | 257 //---- calculate current yaw = e-compass angle Psi ----------------------- |
258 // Result in degree (no need of 0.01 deg precision... | |
259 compass_heading = itan(-iBfy, iBfx) / 100; | |
0 | 260 |
604 | 261 // Result in 0..360 range: |
262 if( compass_heading < 0 ) | |
263 compass_heading += 360; | |
0 | 264 } |