diff src/compass_calib.c @ 0:11d4fc797f74

init
author heinrichsweikamp
date Wed, 24 Apr 2013 19:22:45 +0200
parents
children a4bff632e97b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/compass_calib.c	Wed Apr 24 19:22:45 2013 +0200
@@ -0,0 +1,229 @@
+#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
+#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
+
+//////////////////////////////////////////////////////////////////////////////
+
+void compass_reset_calibration()
+{
+    RESET_C_STACK;
+
+    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;
+}
+
+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;
+
+    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;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void compass_solve_calibration()
+{
+    OVERLAY float yu, yv, yw;
+    OVERLAY float delta;
+    OVERLAY float uc, vc, wc;
+    RESET_C_STACK;
+
+    //---- Normalize partial sums --------------------------------------------
+    //
+    // u, v, w should be centered on the mean value um, vm, wm:
+    // x = u + um, with um = Sx/N
+    //
+    // 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;
+
+    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 + 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
+    // Su = 0, um = Sx/N:
+    // Suuu = Sxxx - 3 Sx*Suu/N - N.(Sx/N)**3
+    //      = Sxxx - 3 Sx*Suu/N - Sx**3/N**2
+
+    // (u + um)**2 (v + vm) = (u**2 + 2 u um + um**2)(v + vm)
+    // Sxxy = Suuv + vm Suu + 2 um (Suv + vm Su) + um**2 (Sv + N.vm)
+    //
+    // Su = 0, Sv = 0, vm = Sy/N:
+    // Sxxy = Suuv + vm Suu + 2 um Suv + N um**2 vm
+    //
+    // 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;
+
+    //---- 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;
+
+    // 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 /////////////////////////////////////
+
+#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;
+}
+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