Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a reverse lookup table from measured values

I'm recording two voltages from an encoder (of known speed) during calibration. Creating this (voltage over angle), then I need to measure the voltages and get the corresponding angle.

Two voltages over angle

I would like to find values in between recorded points too for more precision, some sort of arccos lookup table, but I'm stuck and can't make an efficient way to do that.

Do I have to research my value in the array by comparing a lot of them? That would be very slow, right?

I have tried using an arccos function but that wouldn't compensate for mechanical irregularities I guess. Also I already calculated the averages and amplitudes of the waves.

Edit: As asked, the goal is to measure the position of a motor for FOC. The setup is as follows:

enter image description here

This is what I get with the atan2 function:

x = 2*(hallA.read() - moyenneA)/(maxA-minA);
y = 2*(hallB.read() - moyenneB)/(maxB-minB);

enter image description here

like image 396
antoine serry Avatar asked Oct 15 '22 23:10

antoine serry


2 Answers

Ultimately you can't add any more information to your data than you already have. Except by sampling more often. That said, you could use linear interpolation to estimate what the voltage would be between any two measured points. Look up linear interpolation for the details but to summarize if you have a point C in time between points A and B that is 25% of the way between A and B. Then you calculate the voltage that is weighted the same. So Voltage C = Voltage A + 25%(Voltage A - Voltage B).

As for calculating the angle, that's not my area of expertise. But I think I'd calculate the linear interpolated value 1/2 sample time behind the point I'm interested in and the interpolated value 1/2 sample time ahead of the point I'm interested in and calculate the angle of the slope. But, I am not a signal processing expert. There's been many books written on that subject. There are other interpolation functions available, reading about signal process interpolation on the Internet will probably get you tons of more info.

If you have a concern about mechanical or electrical noise in your data, then I would recommend using a smoothing algorithm. I know fast Fourier transforms are very popular. But I'm sure there are many other options. There must be filtering libraries and algorithms on the Internet. But as I say, this isn't my area of expertise. Maybe my response will trigger someone else to answer with more expertise. One method of eliminating high frequency noise is simply to sample less often.

like image 136
Jeff Spencer Avatar answered Oct 21 '22 02:10

Jeff Spencer


Well as other s mentioned already your data contains noise, and distortion in amplitude. However its more than likely your data also contains temporal distortion (angle parameter of sinwave is non linear nor in exact/constant phase shift) That would make use of atan,acos,asin very inaccurate and problematic.

I would:

  1. FIR filter the data

    so use sliding average centered around value so the output will not phase shift. This is O(n) and one only once. Do not forget to use temp array...

  2. detect monotonic intervals

    simply detect the intervals where your signals are only decreasing or only increasing... This is also O(n) and done one only once.

  3. use binary search

    simply go through all intervals and if your measured voltages are inside their range (first and last value of the interval) use binary search on the interval. The selection of interval is O(1) as number of intervals is always 4 or 5 depending on the starting phases.

    binary search itself is O(log(n))

Here a small C++/VCL example:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
// LUT data
const int n=1317;
double atan2_u0[n];         // u0 [V] red
double atan2_u1[n];         // u1 [V] blue
//  ix = ang*183/50         // index -> [deg]
// ang = ix*50/183          // [deg] -> index

// monotonic intervals
const int m=10;             // max interval count
int atan2_n=0;              // intervals count
int atan2_ix[m];            // interval start index
int atan2_s0[m];            // direction of atan2_u0
int atan2_s1[m];            // direction of atan2_u1
//---------------------------------------------------------------------------
double LUT_atan2(double _u0,double _u1)
    {
    int i,i0,i1,m;
    double u,u0,u1,U0,U1,s0,s1;
    // process interals
    for (i=0;i<atan2_n;i++)
        {
        i0=atan2_ix[i];
        i1=atan2_ix[i+1]-1;
        // test BBOX
        u0=atan2_u0[i0]; U0=atan2_u0[i1]; s0=atan2_s0[i];
        u1=atan2_u1[i0]; U1=atan2_u1[i1]; s1=atan2_s1[i];
        if (s0<0.0){ u=u0; u0=U0; U0=u; }
        if (s1<0.0){ u=u1; u1=U1; U1=u; }
        if ((_u0<u0)||(_u0>U0)) continue;
        if ((_u1<u1)||(_u1>U1)) continue;
        // binary search
        for (m=1;m<=(i1-i0);m<<=1); m>>=1; if (!m) m=1; // m = exp2(log2(i1-i0))
        for (;m;m>>=1)
            {
            i0+=m;
            if (i0>i1){ i0-=m; continue; }
            u0=atan2_u0[i0];
            u1=atan2_u1[i0];
            if (((_u0-u0)*s0<0.0)||((_u1-u1)*s1<0.0)) i0-=m;
            }
        return double(i0)*50.0/183.0;
        }
    return 0.0;
    }
//---------------------------------------------------------------------------
void LUT_atan2_init()       // filter u0,u1 and compute intervals
    {
    int i,j,k,r;
    double u0,u1,du0,du1,U0,U1,tmp0[n],tmp1[n];
    // centered sliding avg, window is <-r,+r>
    r=25; du0=1.0/double(r+r+1);
    for (i=0;i<n;i++)   // all samples
        {
        u0=0.0; u1=0.0;
        for (j=i-r;j<=i+r;j++)
            {
            k=j;
            if (k< 0) k+=n;
            if (k>=n) k-=n;
            u0+=atan2_u0[k];
            u1+=atan2_u1[k];
            }
        tmp0[i]=u0*du0;
        tmp1[i]=u1*du0;
        }
    for (i=0;i<n;i++)
        {
        atan2_u0[i]=tmp0[i];
        atan2_u1[i]=tmp1[i];
        }
    // find monotonic intervals
    atan2_n=0; atan2_ix[0]=0;
    u0=atan2_u0[0]; U0=atan2_u0[1]; atan2_s0[0]=0.0;
    u1=atan2_u1[0]; U1=atan2_u1[1]; atan2_s1[0]=0.0;
    for (i=2;i<n;i++)
        {
        // actual delta du0,du1
        u0=U0; U0=atan2_u0[i]; du0=U0-u0; if (du0<0.0) du0=-1.0; else if (du0>0.0) du0=+1.0;
        u1=U1; U1=atan2_u1[i]; du1=U1-u1; if (du1<0.0) du1=-1.0; else if (du1>0.0) du1=+1.0;
        if (fabs(atan2_s0[atan2_n])<1e-3) atan2_s0[atan2_n]=du0;
        if (fabs(atan2_s1[atan2_n])<1e-3) atan2_s1[atan2_n]=du1;
        // if sign changed add new interval
        if ((du0*atan2_s0[atan2_n]<-0.1)||(du1*atan2_s1[atan2_n]<-0.1))
            {
            if (atan2_n>=m-1) break;
            atan2_n++;
            atan2_ix[atan2_n]=i;
            atan2_s0[atan2_n]=du0;
            atan2_s1[atan2_n]=du1;
            }
        }
    // add end of table as end of intervals
    atan2_n++;
    atan2_ix[atan2_n]=n;
    atan2_s0[atan2_n]=0.0;
    atan2_s1[atan2_n]=0.0;
    }
//---------------------------------------------------------------------------
void data_generate() // extract mesurede values from image
    {
    int x,y,x0=44,y0=478,dx=227-x0,dy=y0-424,r,g,b,i;
    double u0,u1,du=1.0/double(dy);
    BYTE *c; DWORD *q;
    // convert bmp -> pnt[]
    Graphics::TBitmap *bmp=new Graphics::TBitmap;
    bmp->LoadFromFile("in.bmp");
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    for (x=x0;x<bmp->Width;x++)
        {
        u0=u1=-1.0;
        for (y=0;y<=y0;y++)
            {
            q=(DWORD*)bmp->ScanLine[y];
            c=(BYTE*)(q+x);
            b=c[0]; g=c[1]; r=c[2]; i=r+g+b;
            if ((r>200)&&(i<500)){ u0=y0-y; u0*=du; break; }    // [V]
            }
        for (y=0;y<=y0;y++)
            {
            q=(DWORD*)bmp->ScanLine[y];
            c=(BYTE*)(q+x);
            b=c[0]; g=c[1]; r=c[2]; i=r+g+b;
            if ((b>200)&&(i<500)){ u1=y0-y; u1*=du; break; }    // [V]
            }
        if (u0<-0.5) u0=u1;
        if (u1<-0.5) u1=u0;
        i=x-x0;
        atan2_u0[i]=u0+1.0;
        atan2_u1[i]=u1+1.0;
        }
    delete bmp;
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    if (!_redraw) return;

    // clear buffer
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));

    int i;
    double x,y,dy=ys/10.0,r=5.0;
    // LUT
    bmp->Canvas->Pen->Color=clRed;
    for (i=0;i<n;i++)
        {
        x=(i*xs)/n;
        y=ys-(atan2_u0[i]*dy);
        if (!i) bmp->Canvas->MoveTo(x,y);
        else    bmp->Canvas->LineTo(x,y);
        }
    bmp->Canvas->Pen->Color=clBlue;
    for (i=0;i<n;i++)
        {
        x=(i*xs)/n;
        y=ys-(atan2_u1[i]*dy);
        if (!i) bmp->Canvas->MoveTo(x,y);
        else    bmp->Canvas->LineTo(x,y);
        }
    // intervals
    bmp->Canvas->Pen->Color=clDkGray;
    for (i=0;i<=atan2_n;i++)
        {
        x=(atan2_ix[i]*xs)/n;
        bmp->Canvas->MoveTo(x,0);
        bmp->Canvas->LineTo(x,ys);
        }


    // atan2
    double u0=8.0,u1=1.7,a;
    a=LUT_atan2(u0,u1);     // [V,V] -> [deg]
    x=(183.0*a*xs)/(50.0*n);    // [deg] -> [pixel]

    bmp->Canvas->Pen->Color=clWhite;
    bmp->Canvas->MoveTo(x,0);
    bmp->Canvas->LineTo(x,ys);

    y=ys-(u0*dy);
    bmp->Canvas->Pen->Color=clWhite;
    bmp->Canvas->Brush->Color=clRed;
    bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);

    y=ys-(u1*dy);
    bmp->Canvas->Pen->Color=clWhite;
    bmp->Canvas->Brush->Color=clBlue;
    bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);

    // render backbuffer
    Main->Canvas->Draw(0,0,bmp);
    _redraw=false;
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    data_generate();
    LUT_atan2_init();
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    pyx=NULL;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    if (pyx) delete[] pyx;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    xs=ClientWidth;  xs2=xs>>1;
    ys=ClientHeight; ys2=ys>>1;
    bmp->Width=xs;
    bmp->Height=ys;
    if (pyx) delete[] pyx;
    pyx=new int*[ys];
    for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------

I extracted the input data from image of yours and converted to [V] and [deg]. You can ignore the VCL rendering and window stuff. The only important thing here are these functions:

void LUT_atan2_init();
double LUT_atan2(double _u0,double _u1);

the first precomputes all the stuff needed (call just once) and the second returns angle in [deg] for voltages u0,u1.

This might however have slight accuracy problems if your u0,u1 are near interval edge where one voltage is in one interval and the other in the next which might happen if noise is added. In such case you can do bianry search of u0 and u1 separately remebering the 2 locations for both and then chech the 4 combinations which one is the correct one. After that just use mid position between the found two indexes. For this you woul dhave the intervals separate too (but it would be just 2 or 3 per signal).

Also you can add "sub pixel" precision by adding linear interpolation after the binary search (I was too lazy for that so its not in my example)

Here preview:

preview

Red,Blue curves are your u0,u1 signals, the gray lines are the found interval edges, dots are tested (measured) voltages and white is the found angle

like image 39
Spektre Avatar answered Oct 21 '22 02:10

Spektre