View previous topic :: View next topic |
Author |
Message |
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
interpolation method |
Posted: Tue Mar 15, 2011 2:42 am |
|
|
Hello All,
I am looking for a simple and fast interpolation method.
I use a 18f2620 at 40MHz (H4 enabled)
I use an analogue input (0-255)
according to this input value I will read a value from memory.
At the moment I have divided the input range in 12 steps which can be set up by the user. He can change the input level and the value stored at that level.
Now I would like to interpolate for values between 2 input levels.
At the moment I use the common 2D math function:
Code: | Y = Y1+ (X-X1)*(Y2-Y1)/(X2-X1) |
But this takes a long time to calculate, too long...
Now I was thinking of an other interpolation method
Code: |
Analogue lower level = ANA_L (f.e. 50)
Analogue higher level = ANA_H (f.e. 150)
Actual Analogue level = ANA (f.e. 95)
Output lower level = OUT_L (f.e. 2000)
Output_higher level = OUT_H (f.e. 4000)
Desired Output level = OUT
I calculate the ANA range: ANA_H - ANA-L = 100
and I calculate the difference between ANA and ANA_L = 45
and the OUT range OUT_H - OUT-L = 2000
now I keep deviding the ANA range by 2
if the result is bigger than ANA_L I will subtract it from the previous result, else I will add it.
The same is done for the OUT range
ANA/2 ANAresult OUT/2 OUTresult
100 100 2000 2000
50 50 1000 1000
25 25 500 500
13 38 250 750
6 44 125 875
3 47 63 938
2 45 31 906
1 45 16 891
0 45 8 898
last step is to add the result to OUT_L
This gives about the same result as the formula above (2900 ~ 2898)
|
Is this a wise (read "fast") way to do it?
Or are there other methods?
I can not use lookup tables because the user can set the ANA_L, ANA_H, OUT_L and OUT_H himself...
Thanks in advance,
Blob |
|
|
epitalon
Joined: 11 Mar 2010 Posts: 11 Location: france
|
|
Posted: Tue Mar 15, 2011 12:47 pm |
|
|
Quote: |
I can not use lookup tables because the user can set the ANA_L, ANA_H, OUT_L and OUT_H himself...
|
You can compute the look up table each time the user changes one value. Would it be too costly ? |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9245 Location: Greensville,Ontario
|
|
Posted: Tue Mar 15, 2011 1:20 pm |
|
|
If you have enough RAM, the lookup table is only 256 bytes, create it once, after the user inputs the in-lo,in-hi,out-lo,out-hi values.Then it's a simple,fast...read A2D,call lookup table, carryon... |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Wed Mar 16, 2011 2:51 am |
|
|
Hello,
Thanks for your reply.
The lookup table is not really an option.
In fact the user can set 12 input levels for the ADC with corresponding OUT value.
depending on other inputs there are 4 sets of ANA_L, ANA_H, OUT_L and OUT_H.
OUT_L and OUT_H also depend on the rotation speed of my project.
there are 309 possible rpm values so I have 4*309*12 = 14832 different values for OUT_L and OUT_H.
So a formula or loop is the best option I guess...
Thanks,
Blob |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19546
|
|
Posted: Wed Mar 16, 2011 3:10 am |
|
|
There may be lots of different output possibilities, but if you are only using the 8bit ADC, there are only 256 possibilities here.
The point being made was that once you have the set points from the user, you could 'precalculate' all the possible outputs for the 256 possible inputs (so only 256 possibilities), which may take a second or so to do, but save this as a 256 element table, and in use, you have near instantaneous solution for every possible ADC value.
On the calculation though, time will depend massively on the maths type being used. You might want to retry your arithmetic being careful about using 'lower' maths types when possible, and see if the time becomes more acceptable.
Best Wishes |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Wed Mar 16, 2011 4:35 am |
|
|
The project I am building is monitoring a rotating disk.
The value of the 8 bit analogue input will result in a start address of a table. The user can define 12 set points => 12 tables.
The rpm of the disk going from 550 rpm up to 16000.
Every 50 rpm refers to a memory location so I have 309 rpm addresses.
Then there is an other digital input which tells me what dataset I have to use.
I have 4 sets.
Together with the start address and the data set, the rpm address will point to an output value.
It is correct that I have only 255 input possibilities, but the rpm and the digital input is not constant, so it will be very hard to use a lookup table.
I am writing a small loop with only divisions by 2.
I assume /2 equals x >> 1? and takes not long to perform...
I will post it when ready.
Best regards,
Blob |
|
|
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Wed Mar 16, 2011 6:29 am |
|
|
Maybe you could pre-calculate all your divisors 1/(x2-x1) and store them in a table. Then when doing the real math you can multiply by the pre-calculated number, rather than actually doing the division which takes a lot of time. _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
|
sseidman
Joined: 14 Mar 2005 Posts: 159
|
|
Posted: Wed Mar 16, 2011 8:03 am |
|
|
Can you multiply all your numbers up high enough such that integer division would be accurate enough?? I do all I can to avoid floats. |
|
|
Blob
Joined: 02 Jan 2006 Posts: 75 Location: Neeroeteren, Limburg, Belgium
|
|
Posted: Wed Mar 16, 2011 8:55 am |
|
|
Hello,
My interpolator works in the the available time
for those who are interested here is the code
Code: |
//read ADC and compare with set points of user to determine x in start_adr[x]
//setpoint ADC_L < ADC_in < ADC_H
//according to digital inputs select dataset => start_adr[x] += dataset
//measure rpm value to obtain the adr value
OUT_H = read_program_eeprom(start_adr[x]+adr);
OUT_L = read_program_eeprom(start_adr[x-1]+adr);
if(OUT_H > OUT_L) //rising
{
bl_rising = true;
OUT_DIFF = OUT_H-OUT_L;
}
else
{
bl_rising = false;
OUT_DIFF = OUT_L-OUT_H;
}
ADC_in = ADC_in - ADC_L; //relative position of ADC_in
ADC_diff = ADC_H - ADC_L; //width of interval
ADC_HELP = ADC_diff; //help variable
OUT = OUT_diff; //set out
for(i=0;i<4;i++) //do 4 times (more accurate result when higher)
{
if(ADC_HELP > ADC_in) //if greater than
{
ADC_HELP -= ADC_diff/2; //subtract half of the input step
OUT -= OUT_diff/2; //subtract half of the output step
}
else if(ADC_HELP < ADC_in)
{
ADC_HELP += ADC_diff/2; //add half of the input step
OUT += OUT_diff/2; //add half of the output step
}
OUT_diff /= 2; //refine output step
ADC_diff /= 2; //refine input step
}
OUT += OUT_L;
|
Best regards,
Blob |
|
|
epitalon
Joined: 11 Mar 2010 Posts: 11 Location: france
|
|
Posted: Thu Mar 17, 2011 3:20 am |
|
|
Quote: |
My interpolator works in the the available time
|
That is clever. But achieved precision is limited.
I wonder if you could achieve better precision and similar or better execution time by precalculating (Y2-Y1)/(X2-X1) in fixed point format :
example :
Code: |
// Precalculation
// Note 65535 = 2 at power 16 = 1 << 16
unsigned int32 ratio = (Y2 - Y1) * 65536 / (X2 - X1);
// Be carefull of not overflowing int32
// There is no such concern if (Y2 - Y1) is contained in 16 bit integer.
// Interpolation
unsigned int32 ratio = read_program_eeprom(start_adr_ratio[x]+adr);
OUT_diff = (unsigned int32) (ADC_in - ADC_L) * ratio / 65536;
|
That suggestion is in accordance with what suggested sseidman and SherpaDoug.
Note that any power of 2 is a good multiplicative factor because in that case, dividing by such factor involves only shifting bits.
But it is even better to use 2 raised at power 16 or 8 if you are using an 8 bit microcontroller because in that case, dividing by such factor does not involve shifting bits but shifting bytes. |
|
|
|