Mercurial > public > hwos_code
diff src/compass.c @ 0:11d4fc797f74
init
author | heinrichsweikamp |
---|---|
date | Wed, 24 Apr 2013 19:22:45 +0200 |
parents | |
children | a17359244d93 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/compass.c Wed Apr 24 19:22:45 2013 +0200 @@ -0,0 +1,244 @@ +////////////////////////////////////////////////////////////////////////////// +// HISTORY +// 2012-12-01 [jDG] Creation +// 2012-12-23 [jDG] Added filtering. +// 2012-12-30 [jDG] Added calibration (spherical best fit). + +#include "compass.h" + +////////////////////////////////////////////////////////////////////////////// +// mH: Crude work-around, needs to be made right +#ifndef UNIX +# pragma udata overlay bank8=0x800 + static char C_STACK[256]; // Overlay C-code data stack here. +# define RESET_C_STACK \ + _asm \ + LFSR 1, 0x800 \ + LFSR 2, 0x800 \ + _endasm +# pragma udata overlay bank9_compass +#else +# define RESET_C_STACK +#endif + +////////////////////////////////////////////////////////////////////////////// +// fifth order of polynomial approximation of atan(), giving 0.05 deg max error +// +#define K1 (5701) // Needs K1/2**16 +#define K2 (1645) // Needs K2/2**48 WAS NEGATIV +#define K3 ( 446) // Needs K3/2**80 + +////////////////////////////////////////////////////////////////////////////// +// Interface to assembleur multiplies +Int16 umul(PARAMETER Int16 a, PARAMETER Int16 b) +{ + extern Int16 compass_umul(void); + extern Int16 compass_a, compass_b; + compass_a = a; + compass_b = b; + return compass_umul(); +} + +Int16 imul(PARAMETER Int16 a, PARAMETER Int16 b) +{ + extern Int16 compass_imul(void); + extern Int16 compass_a, compass_b; + compass_a = a; + compass_b = b; + return compass_imul(); +} + +////////////////////////////////////////////////////////////////////////////// +/// Returns a / b * 2**16 +/// +/// A 16/16 -> 16 bits divide, returning a scalled result. +/// Used to multiply fractional numbers in the range 0..1, +/// represented as 0..32767. +Int16 udiv(PARAMETER Int16 a, PARAMETER Int16 b) +{ + OVERLAY Int16 d, r; + + //---- Pre-scale both numerator and denominator -------------------------- + while( (((a>>8) | (b>>8)) & 0xC0) == 0 ) + { + a <<= 1; + b <<= 1; + } + + //---- Make division trials ---------------------------------------------- + d = 0x4000; // Starts with 0.5, because 1.0 is sign bit. + b >>= 1; // Hence pre-shift b. + r = 0; + do { + if( a >= b ) { // a is big enough ? + a -= b; // then count d times b out of it. + r |= d; // and accumulate that bit. + } + b >>= 1; // then loop trying twice smaller. + d >>= 1; + } while( b ); + return r; +} + +////////////////////////////////////////////////////////////////////////////// +/// Computes atan(y/x) in Angle, for x, y in range 0..32767 +/// +/// Results a single quadrant Angle, in the range 0 .. Q_PI/2 +Angle utan(PARAMETER Int16 y, PARAMETER Int16 x) +{ + OVERLAY Int16 ratio, angle, x2, x3; + + //---- Handle zero divisor ----------------------------------------------- + if( x == 0 ) + return (y == 0) ? 0 : Q_PIO2; + + //---- Make it half-quadrant : 0 .. 45 deg ------------------------------- + ratio = (x > y) ? udiv(y, x) : udiv(x, y); + + //---- Then apply the polynomial approximation --------------------------- + angle = umul(K1, ratio); // r*K1 / 2**16 + x2 = umul(ratio, ratio); // r**2 / 2**16 + x3 = umul(x2, ratio); // r**3 / 2**32 + angle -= umul(x3, K2); // K2*r**3 / 2**48: NEGATIV. + + x3 = umul(x3, x2); // r**5 / 2**64 + angle += umul(x3, K3); // K3*r**5 / 2**80 + + //---- Recover the full quadrant ----------------------------------------- + return (x < y) ? (Angle)(Q_PIO2 - angle) + : (Angle)(angle); +} + +////////////////////////////////////////////////////////////////////////////// +/// Computes atan2(y/x) in Angle, for x, y in range -32768 to 32767 +/// +/// Results a four quadrant Angle, in the range -Q_PI .. +Q_PI +Angle itan(PARAMETER Int16 y, PARAMETER Int16 x) +{ + // Beware: -32768 is not properly handled (sgn error). + if( x == -32768 ) x = -32767; + if( y == -32768 ) y = -32767; + + if( x >= 0 ) + if( y >= 0 ) // First quadrant: 0..90 deg. + return utan(y,x); + else // Fourth quadrant: 0..-90 deg + return -utan(-y,x); + else + if( y >= 0 ) // Second quadrant: 90..180 deg + return Q_PI - utan(y, -x); + else // Third quadrant: -90..-180 deg; + return -Q_PI + utan(-y, -x); +} + +////////////////////////////////////////////////////////////////////////////// +/// Computes cos(theta) = sqrtf(x2/h2), +/// when theta = atan(y/x) and h2=x*x+y*y +/// +Int16 cosxh(PARAMETER Int16 x2, PARAMETER Int16 h2) +{ + OVERLAY Int16 r = 0; + OVERLAY Int16 d = 0x4000; + + do { + OVERLAY Int16 a = r + d; + a = umul(a, a); + a = umul(a, h2); + if( a <= x2 ) r += d; + d >>= 1; + } while( d ); + + return r; +} + +////////////////////////////////////////////////////////////////////////////// +/// Computes both sin and cos of angle y/x, +/// with h = sqrt(x**2+y**2). +/// +void sincos(PARAMETER Int16 x, PARAMETER Int16 y, Int16* sin, Int16* cos) +{ + OVERLAY Int16 x2, y2, h2; + + //---- Fold into one quadant --------------------------------------------- + OVERLAY char neg = 0; + if( x < 0 ) + { + neg |= 1; + x = -x; + } + if( y < 0 ) + { + neg |= 2; + y = -y; + } + + //---- Pre-scale both numerator and denominator ---------------------- + while( (((x>>8) | (y>>8)) & 0xE0) == 0 ) + { + x <<= 1; + y <<= 1; + } + + //---- Uses trig() to do the stuff one on quadrant ------------------- + x2 = umul(x,x); + y2 = umul(y,y); + h2 = x2 + y2; + x2 = cosxh(x2, h2); + + //---- Results back in four quadrants -------------------------------- + *cos = (neg & 1) ? -x2 : x2; + y2 = cosxh(y2, h2); + *sin = (neg & 2) ? -y2 : y2; +} + +////////////////////////////////////////////////////////////////////////////// +// + +void compass(void) +{ + OVERLAY Int16 sin, cos; + OVERLAY Int16 iBfx, iBfy, Gz; + OVERLAY Int16 iBpx, iBpy, iBpz; + RESET_C_STACK; + + //---- Make hard iron correction ----------------------------------------- + // Measured magnetometer orientation, measured ok. + // From matthias drawing: (X,Y,Z) --> (X,Y,Z) : no rotation. + iBpx = compass_DX_f - compass_CX_f; // X + iBpy = compass_DY_f - compass_CY_f; // Y + iBpz = compass_DZ_f - compass_CZ_f; // Z + + //---- Calculate sine and cosine of roll angle Phi ----------------------- + sincos(accel_DZ_f, accel_DY_f, &sin, &cos); + compass_roll = itan(sin, cos) / 100; + + //---- rotate by roll angle (-Phi) --------------------------------------- + iBfy = imul(iBpy, cos) - imul(iBpz, sin); + iBpz = imul(iBpy, sin) + imul(iBpz, cos); + Gz = imul(accel_DY_f, sin) + imul(accel_DZ_f, cos); + + //---- calculate sin and cosine of pitch angle Theta --------------------- + sincos(Gz, -accel_DX_f, &sin, &cos); // NOTE: changed sin sign. + compass_pitch = itan(sin, cos) / 100; + + /* correct cosine if pitch not in range -90 to 90 degrees */ + if( cos < 0 ) cos = -cos; + + ///---- de-rotate by pitch angle Theta ----------------------------------- + iBfx = imul(iBpx, cos) + imul(iBpz, sin); + + //---- Detect uncalibrated compass --------------------------------------- + if( !compass_CX_f && !compass_CY_f && !compass_CZ_f ) + { + compass_heading = -1; + return; + } + + //---- calculate current yaw = e-compass angle Psi ----------------------- + // Result in degree (no need of 0.01 deg precision... + compass_heading = itan(-iBfy, iBfx) / 100; + + // Result in 0..360 range: + if( compass_heading < 0 ) + compass_heading += 360; +}