Mercurial > public > hwos_code
diff src/compass_calib.c @ 282:7d9edd3b8c86
Make a more compact COMPASS calibration code (<7KB), and add more tests.
author | jDG |
---|---|
date | Fri, 22 May 2015 14:50:40 +0200 |
parents | a4bff632e97b |
children | ca4556fb60b9 |
line wrap: on
line diff
--- a/src/compass_calib.c Tue May 26 17:40:13 2015 +0200 +++ b/src/compass_calib.c Fri May 22 14:50:40 2015 +0200 @@ -1,14 +1,14 @@ +////////////////////////////////////////////////////////////////////////////// +/// compass_calib.c +/// Calibrate hard-iron for magnetic compass measurements. +/// Copyright (c) 2012-2015, JD Gascuel, HeinrichsWeikamp, all right reserved. +////////////////////////////////////////////////////////////////////////////// +// 2015-05-22 [jDG] Make a smaller calibration code (15.6 --> 6.7 KB). + #include "compass.h" -static unsigned short int compass_N; - -static float Su, Sv, Sw; -static float Suu, Svv, Sww, Suv, Suw, Svw; -static float Suuu, Svvv, Swww; -static float Suuv, Suuw, Svvu, Svvw, Swwu, Swwv; - ////////////////////////////////////////////////////////////////////////////// -// mH: Crude work-around, needs to be made right +// mH: Put compass data into bank 8 (stack) and bank 9 (variables) #ifndef UNIX # pragma udata overlay bank8=0x800 static char C_STACK[256]; // Overlay C-code data stack here. @@ -19,11 +19,24 @@ _endasm # pragma udata overlay bank9_compass #else -# define RESET_C_STACK +# define RESET_C_STACK #endif ////////////////////////////////////////////////////////////////////////////// +static unsigned short int compass_N; + +static float Su, Sv, Sw; // First order moments. +static float Suu, Svv, Sww, Suv, Suw, Svw; // Second order moments. +static float Saa; // Suu + Svv + Sww +static float Saau; // Suuu + Svvu + Swwu // Third order moment. +static float Saav; // Suuv + Svvv + Swwv +static float Saaw; // Suuw + Svvw + Swww +static float yu, yv, yw; // temp solution vector. +static float uc, vc, wc; // temp sphere's center. + +////////////////////////////////////////////////////////////////////////////// + void compass_reset_calibration() { RESET_C_STACK; @@ -31,74 +44,103 @@ compass_N = 0; Su = Sv = Sw = 0.0; Suu = Svv = Sww = Suv = Suw = Svw = 0.0; - Suuu = Svvv = Swww = 0.0; - Suuv = Suuw = Svvu = Svvw = Swwu = Swwv = 0.0; - compass_CX_f = compass_CY_f = compass_CZ_f = 0.0; + Saau = Saav = Saaw = 0.0; + compass_CX_f = compass_CY_f = compass_CZ_f = 0; } +////////////////////////////////////////////////////////////////////////////// + void compass_add_calibration() { - OVERLAY float u, v, w; RESET_C_STACK; - u = (compass_DX_f - compass_CX_f) / 32768.0f; - v = (compass_DY_f - compass_CY_f) / 32768.0f; - w = (compass_DZ_f - compass_CZ_f) / 32768.0f; + // Get filtered/calibrated magnetic direction: + yu = (compass_DX_f - compass_CX_f) / 32768.0f; + yv = (compass_DY_f - compass_CY_f) / 32768.0f; + yw = (compass_DZ_f - compass_CZ_f) / 32768.0f; + // Add to all moments: compass_N++; - Su += u; - Sv += v; - Sw += w; - Suv += u*v; - Suw += u*w; - Svw += v*w; - Suu += u*u; - Suuu += u*u*u; - Suuv += v*u*u; - Suuw += w*u*u; - Svv += v*v; - Svvv += v*v*v; - Svvu += u*v*v; - Svvw += w*v*v; - Sww += w*w; - Swww += w*w*w; - Swwu += u*w*w; - Swwv += v*w*w; + + Su += yu; + Sv += yv; + Sw += yw; + + Suu += yu*yu; + Suv += yu*yv; + Suw += yu*yw; + Svv += yv*yv; + Svw += yv*yw; + Sww += yw*yw; + + Saa = yu*yu + yv*yv + yw*yw; + Saau += yu * Saa; + Saav += yv * Saa; + Saaw += yw * Saa; +} + +////////////////////////////////////////////////////////////////////////////// + +static float compass_discriminent(PARAMETER char column) +{ + // Basic symetric matrix: + OVERLAY float a = Suu, d = Suv, g = Suw; + OVERLAY float b = Suv, e = Svv, h = Svw; + OVERLAY float c = Suw, f = Svw, i = Sww; + + // Substitute a column, if asked to: + if( column==1 ) { a = yu; b = yv; c = yw; } + if( column==2 ) { d = yu; e = yv; f = yw; } + if( column==3 ) { g = yu; h = yv; i = yw; } + + // Do the math: + return a * (e * i - f * h) + - b * (d * i - f * g) + + c * (d * h - e * g); +} + +////////////////////////////////////////////////////////////////////////////// + +static float compass_dotc(PARAMETER float u, float v, float w) +{ + return u*uc + v*vc + w*wc; } ////////////////////////////////////////////////////////////////////////////// void compass_solve_calibration() { - OVERLAY float yu, yv, yw; OVERLAY float delta; - OVERLAY float uc, vc, wc; RESET_C_STACK; + //---- Compute center of measured magnetic directions -------------------- + uc = Su/compass_N; + vc = Sv/compass_N; + wc = Sw/compass_N; + //---- Normalize partial sums -------------------------------------------- // - // u, v, w should be centered on the mean value um, vm, wm: - // x = u + um, with um = Sx/N + // We measured the (u, v, w) values, and need the centered (x, y, z) ones + // around the sphere center's (uc, vc, wc) as: + // uc = Su / N; The mean value + // x = u - uc; The differnce to the mean. // // So: - // (u + um)**2 = u**2 + 2u*um + um**2 - // Su = 0, um = Sx/N - // Sxx = Suu + 2 um Su + N*(Sx/N)**2 = Suu + Sx**2/N - // Suu = Sxx - Sx**2/N - yu = Su/compass_N; - yv = Sv/compass_N; - yw = Sw/compass_N; + // x**2 = (u - uc)**2 = u**2 - 2u*uc + uc**2 + // + // We need the Sxx sum of 2nd orders: + // Sxx = Suu - 2 uc Su + N*uc*(Su/N) = Suu - uc Su + Suu -= Su*uc; + Svv -= Sv*vc; + Sww -= Sw*wc; - Suu -= Su*yu; - Svv -= Sv*yv; - Sww -= Sw*yw; - - // (u + um)(v + vm) = uv + u vm + v um + um vm - // Sxy = Suv + N * um vm - // Suv = Sxy - N * (Sx/N)(Sy/N); - Suv -= Su*yv; - Suw -= Su*yw; - Svw -= Sv*yw; + // (u - uc)(v - vc) = uv - u vc - v uc + uc vc + // Sxy = Suv - Su vc - Sv uc + N uc vc + // = Suv - Su vc - N vc uc + N uc vc + // = Suv - Su vc + Suv -= Su*vc; + Suw -= Su*wc; + Svw -= Sv*wc; // (u + um)**3 = u**3 + 3 u**2 um + 3 u um**2 + um**3 // Sxxx = Suuu + 3 um Suu + 3 um**2 Su + N.um**3 @@ -115,116 +157,26 @@ // Suuv = Sxxy - (Sy/N) Suu - 2 (Sx/N) Suv - (Sx/N)**2 Sy // = Sxxy - Suu*Sy/N - 2 Suv*Sx/N - Sx*Sx*Sy/N/N // = Sxxy - (Suu + Sx*Sx/N)*Sy/N - 2 Suv*Sx/N - Suuu -= (3*Suu + Su*yu)*yu; - Suuv -= (Suu + Su*yu)*yv + 2*Suv*yu; - Suuw -= (Suu + Su*yu)*yw + 2*Suw*yu; - - Svvu -= (Svv + Sv*yv)*yu + 2*Suv*yv; - Svvv -= (3*Svv + Sv*yv)*yv; - Svvw -= (Svv + Sv*yv)*yw + 2*Svw*yv; - - Swwu -= (Sww + Sw*yw)*yu + 2*Suw*yw; - Swwv -= (Sww + Sw*yw)*yv + 2*Svw*yw; - Swww -= (3*Sww + Sw*yw)*yw; + Saa = Suu + Svv + Sww; + yu = Saau - Saa*uc - compass_dotc(Su*uc + 2*Suu, Sv*uc + 2*Suv, Sw*uc + 2*Suw); + yv = Saav - Saa*vc - compass_dotc(Su*vc + 2*Suv, Sv*vc + 2*Svv, Sw*vc + 2*Svw); + yw = Saaw - Saa*wc - compass_dotc(Su*wc + 2*Suw, Sv*wc + 2*Svw, Sw*wc + 2*Sww); //---- Solve the system -------------------------------------------------- // uc Suu + vc Suv + wc Suw = (Suuu + Svvu + Swwu) / 2 // uc Suv + vc Svv + wc Svw = (Suuv + Svvv + Swwv) / 2 // uc Suw + vc Svw + wc Sww = (Suuw + Svvw + Swww) / 2 // Note this is symetric, with a positiv diagonal, hence - // it always have a uniq solution. - yu = 0.5f * (Suuu + Svvu + Swwu); - yv = 0.5f * (Suuv + Svvv + Swwv); - yw = 0.5f * (Suuw + Svvw + Swww); - delta = Suu * (Svv * Sww - Svw * Svw) - - Suv * (Suv * Sww - Svw * Suw) - + Suw * (Suv * Svw - Svv * Suw); - - uc = (yu * (Svv * Sww - Svw * Svw) - - yv * (Suv * Sww - Svw * Suw) - + yw * (Suv * Svw - Svv * Suw) )/delta; - vc = (Suu * ( yv * Sww - yw * Svw) - - Suv * ( yu * Sww - yw * Suw) - + Suw * ( yu * Svw - yv * Suw) )/delta; - wc = (Suu * (Svv * yw - Svw * yv ) - - Suv * (Suv * yw - Svw * yu ) - + Suw * (Suv * yv - Svv * yu ) )/delta; + // discriminent is always not null. + delta = 0.5f / compass_discriminent(0); - // Back to uncentered coordinates: - // xc = um + uc - uc = Su/compass_N + compass_CX_f/32768.0f + uc; - vc = Sv/compass_N + compass_CY_f/32768.0f + vc; - wc = Sw/compass_N + compass_CZ_f/32768.0f + wc; - - // Then save the new calibrated center: - compass_CX_f = (short)(32768 * uc); - compass_CY_f = (short)(32768 * vc); - compass_CZ_f = (short)(32768 * wc); -} - -////////////////////////////// TEST CODE ///////////////////////////////////// + // So computed new center, with offsetted values: + uc += compass_discriminent(1) * delta; + vc += compass_discriminent(2) * delta; + wc += compass_discriminent(3) * delta; -#ifdef TEST_COMPASS_CALIBRATION - -#include <QtDebug> -#include <stdio.h> - -#include <math.h> -#include <stdlib.h> - -short compass_DX_f, compass_DY_f, compass_DZ_f; -short compass_CX_f, compass_CY_f, compass_CZ_f; - -inline float uniform() { - return (rand() & 0xFFFF) / 65536.0f; + // Add correction due to already applyed calibration: + compass_CX_f += (short)(32768 * uc); + compass_CY_f += (short)(32768 * vc); + compass_CZ_f += (short)(32768 * wc); } -inline float sqr(float x) { - return x*x; -} - -static const float radius = 0.21f; -static const float cx = 0.79f, cy = -0.46f, cz = 0.24f; -// const float cx = 0, cy = 0, cz = 0; - -void check_compass_calib() -{ - - // Starts with no calibration at all: - compass_CX_f = compass_CY_f = compass_CZ_f = 0; - - // Try 10 recalibration passes: - for(int p=0; p<10; ++p) - { - compass_reset_calibration(); - - //---- Generates random points on a sphere ------------------------------- - // of radius,center (cx, cy, cz): - for(int i=0; i<100; ++i) - { - float theta = uniform()*360.0f; - float phi = uniform()*180.0f - 90.0f; - - float x = cx + radius * cosf(phi)*cosf(theta); - float y = cy + radius * cosf(phi)*sinf(theta); - float z = cz + radius * sinf(phi); - - compass_DX_f = short(32768 * x); - compass_DY_f = short(32768 * y); - compass_DZ_f = short(32768 * z); - compass_add_calibration(); - } - - compass_solve_calibration(); - qDebug() << "Center =" - << compass_CX_f/32768.0f - << compass_CY_f/32768.0f - << compass_CZ_f/32768.0f; - - float r2 = sqr(compass_CX_f/32768.0f - cx) - + sqr(compass_CY_f/32768.0f - cy) - + sqr(compass_CZ_f/32768.0f - cz); - if( r2 > 0.01f*0.01f ) - qWarning() << " calibration error: " << sqrtf(r2); - } -} -#endif // TEST_COMPASS_CALIBRATION