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.
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:
This is what I get with the atan2 function:
x = 2*(hallA.read() - moyenneA)/(maxA-minA);
y = 2*(hallB.read() - moyenneB)/(maxB-minB);
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.
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:
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...
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.
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:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With