view src/compass_calib.c @ 57:130719108ccf

minor
author heinrichsweikamp
date Wed, 02 Oct 2013 08:32:35 +0200
parents 11d4fc797f74
children a4bff632e97b
line wrap: on
line source

#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