Mercurial > public > hwos_code
view src/compass.c @ 655:c7b7b8a358cd default tip
hwOS tech 3.22 release
author | heinrichsweikamp |
---|---|
date | Mon, 29 Apr 2024 13:05:18 +0200 |
parents | 75e90cd0c2c3 |
children |
line wrap: on
line source
////////////////////////////////////////////////////////////////////////////// /// compass.c /// Compute north direction from magnetic/acceleration measures V3.02.1 /// /// Copyright (c) 2012-2015, JD Gascuel, heinrichs weikamp gmbh, all right reserved. ////////////////////////////////////////////////////////////////////////////// // HISTORY // 2012-12-01 [jDG] Creation // 2012-12-23 [jDG] Added filtering. // 2012-12-30 [jDG] Added calibration (spherical best fit). // 2015-05-22 [jDG] Minor cleanups. Smaller calibration code. #include "configuration.inc" #include "compass.h" #ifdef _compass ////////////////////////////////////////////////////////////////////////////// // // Put compass data into bank 13 (stack) and bank 9 (variables) // #ifndef UNIX # pragma udata overlay bank13=0xd00 static char C_STACK[256]; // overlay C-code data stack here # define C_STACK_ADDR C_STACK # define RESET_C_STACK \ _asm \ LFSR 1,C_STACK_ADDR \ LFSR 2,C_STACK_ADDR \ _endasm # pragma udata bank9a = 0x900 # pragma code compass_run # define PARAMETER static # define OVERLAY overlay #else # define RESET_C_STACK # define PARAMETER # define OVERLAY #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 assembler 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 scaled 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; OVERLAY char failsafe=250; //---- Pre-scale both numerator and denominator -------------------------- while( (((a>>8) | (b>>8)) & 0xC0) == 0 ) { failsafe--; if( failsafe == 0 ) break; a <<= 1; b <<= 1; } //---- Make division trials ---------------------------------------------- d = 0x4000; // start 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 (sign 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; OVERLAY char failsafe = 250; //---- Fold into one quadrant -------------------------------------------- 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 ) { failsafe--; if( failsafe == 0 ) break; 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; //---- Detect uncalibrated compass --------------------------------------- if( !compass_CX_f && !compass_CY_f && !compass_CZ_f ) { // no usable compass is signaled by bit 15 set to 1 compass_heading_new = 32768; return; } //---- 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); //---- 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 /* 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); //---- calculate current yaw = e-compass angle Psi ----------------------- // Result in degree (no need of 0.01 deg precision... compass_heading_new = itan(-iBfy, iBfx) / 100; // Result in 0..360 range: if( compass_heading_new < 0 ) compass_heading_new += 360; } #endif // _compass