view src/compass_calib.c @ 623:c40025d8e750

3.03 beta released
author heinrichsweikamp
date Mon, 03 Jun 2019 14:01:48 +0200
parents ca4556fb60b9
children cd58f7fc86db
line wrap: on
line source

/////////////////////////////////////////////////////////////////////////////
//
// compass_calib.c                                    next generation V3.03.4
//
// Calibrate hard-iron for magnetic compass measurements.
// Copyright (c) 2012-2019, JD Gascuel, HeinrichsWeikamp, all rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

//  2015-05-22  [jDG] Make a smaller calibration code (15.6 --> 6.7 KB).
//  2019-05-14  [rl]  make it even smaller again by another 2.000 byte

#include "configuration.inc"
#include "compass.h"


#ifdef _compass


//////////////////////////////////////////////////////////////////////////////
//
// mH: Put compass data into bank 8 (stack) and bank 9 (variables)
// rl: could also be overlaid with p2_deco.c stack...
//
#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

//////////////////////////////////////////////////////////////////////////////

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	third  order moment
static float Saaw;							// Suuw + Svvw + Swww	third  order moment

static float yu, yv, yw;					// temp solution vector
static float uc, vc, wc;					// temp sphere's center

static float yh, uh, S0, S1, S2, S3;		// transfer vars for compass_solve_helper()
static float discriminant, delta;			// transfer vars  for calc_discriminant()


//////////////////////////////////////////////////////////////////////////////

#ifndef UNIX
#	pragma code compass_calib
#endif

//////////////////////////////////////////////////////////////////////////////

void compass_reset_calibration()								// 202 byte
{
	RESET_C_STACK;

	compass_N                                   = 0;
	Su           = Sv           = Sw            = 0.0;
	Suu          = Svv          = Sww           = 0.0;
	Suv          = Suw          = Svw           = 0.0;
	Saau         = Saav         = Saaw          = 0.0;
	compass_CX_f = compass_CY_f = compass_CZ_f  = 0;	// int16
}

//////////////////////////////////////////////////////////////////////////////

void compass_add_calibration()									// 1488 byte
{
	overlay float SQR_yu, SQR_yv, SQR_yw;	// squared values;

	RESET_C_STACK;

	// get filtered/calibrated magnetic direction				// 276 byte
	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;

	// compute squared values									// 156 byte
	SQR_yu = yu*yu;
	SQR_yv = yv*yv;
	SQR_yw = yw*yw;

	// increment count
	compass_N++;

	// add to all moments										// 156 byte
	Su   += yu;
	Sv   += yv;
	Sw   += yw;

	//															// 156 byte
	Suu  += SQR_yu;
	Svv  += SQR_yv;
	Sww  += SQR_yw;

	//															// 312 byte
	Suv  += yu*yv;
	Suw  += yu*yw;
	Svw  += yv*yw;

	//															// 104 byte
	Saa   = SQR_yu + SQR_yv + SQR_yw;


	//															// 312 byte
	Saau += yu * Saa;
	Saav += yv * Saa;
	Saaw += yw * Saa;
}

//////////////////////////////////////////////////////////////////////////////

static void calc_discriminant(PARAMETER char column)
{
	// basic symmetric 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 in a couple of single terms to reduce
	// the amount of required ".tmpdata" memory
	discriminant  = a * (e * i - f * h);
	discriminant -= b * (d * i - f * g);
	discriminant += c * (d * h - e * g);

	// apply delta if column = 1/2/3
	if( column ) discriminant *= delta;
}

//////////////////////////////////////////////////////////////////////////////

static void compass_solve_helper(void)							// 338 byte
{
	// computes:
	//
	// yh = S0 - Saa*uh - compass_dotc(Su*uh + 2*S1, Sv*uh + 2*S2, Sw*uh + 2*S3);
	//
	// with compass_dotc(u,v,w) = u*uc + v*vc + w*wc the equation becomes:
	//
	// yh = S0 - Saa*uh - ( (Su*uh + 2*S1)*uc + (Sv*uh + 2*S2)*vc + (Sw*uh + 2*S3)*wc )
	//
	// this equation is now split into several single terms
	// to reduce the amount of required ".tmpdata" memory:

	yh  = S0;
	yh -= Saa*uh;
	yh -= (Su*uh + 2*S1) * uc;
	yh -= (Sv*uh + 2*S2) * vc;
	yh -= (Sw*uh + 2*S3) * wc;
}

//////////////////////////////////////////////////////////////////////////////

void compass_solve_calibration()								// 1738 byte
{
	overlay float inv_compass_N;

	RESET_C_STACK;

	// compute center of measured magnetic directions			// 200 byte
	inv_compass_N = 1 / compass_N;
	uc = Su * inv_compass_N;
	vc = Sv * inv_compass_N;
	wc = Sw * inv_compass_N;

	// normalize partial sums
	//
	// 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 difference to the mean.
	//
	// So:
	// 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;

	// (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
	// 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

	Saa = Suu + Svv + Sww;

	// compute yu = Saau - Saa*uc - compass_dotc(Su*uc + 2*Suu, Sv*uc + 2*Suv, Sw*uc + 2*Suw);
	//
	uh = uc; S0 = Saau; S1 = Suu; S2 = Suv; S3 = Suw; compass_solve_helper(); yu = yh;

	// compute yv = Saav - Saa*vc - compass_dotc(Su*vc + 2*Suv, Sv*vc + 2*Svv, Sw*vc + 2*Svw);
	//
	uh = vc; S0 = Saav; S1 = Suv; S2 = Svv; S3 = Svw; compass_solve_helper(); yv = yh;

	// compute yw = Saaw - Saa*wc - compass_dotc(Su*wc + 2*Suw, Sv*wc + 2*Svw, Sw*wc + 2*Sww);
	//
	uh = wc; S0 = Saaw; S1 = Suw; S2 = Svw; S3 = Sww; compass_solve_helper(); yw = yh;


	//---- 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 symmetric, with a positive diagonal, hence discriminant is always not null

	//compute delta value
	calc_discriminant(0); delta = 0.5f / discriminant;

	// compute new center, with offset values
	calc_discriminant(1); uc += discriminant;
	calc_discriminant(2); vc += discriminant;
	calc_discriminant(3); wc += discriminant;

	// add correction due to already applied calibration
	compass_CX_f += (short)(32768 * uc);
	compass_CY_f += (short)(32768 * vc);
	compass_CZ_f += (short)(32768 * wc);
}

#endif	// _compass