How to create lookup table

wow, didn't expect to see so many great responses. I should have some free time tonight and will give some of these a shot. I'm not to worried about exhausting the memory of the atmega. The project is a simple cooling fan control module and i am going to add an LCD display to the dash to output the actual temp due to the current temp gauge being inop.

Thanks for everyone's suggestions.

Using an interpolation lookup only takes a few lines of code and reduces the size of the lookup table by a factor of 100 compared to the 'brute force' approach. Since the brute force approach also needs you to work out and type in those 1000 values, the brute force approach doesn't look remotely attractive to me.

Yes it would, that's what I said. But these "few" lines of code are not that easy to get straight for the original poster. Even the code by crossroads is not yet bugfree.

While we are at it, pasting

10. 3.51
20. 3.07
30. 2.6
40. 2.13
50. 1.7
60. 1.33
70. 1.02
80. 0.78
90. 0.6
100. 0.46

here http://www.xuru.org/rt/PR.asp

and setting degree to 4 yields the very good interpolation formula

y = -4.006410256·10^-8 x^4 + 1.056818182·10^-5 x^3 - 6.992278555·10^-4 x^2 - 2.977097902·10^-2 x + 3.8675

where y is the Voltage and x the temperature. This can be solved for x with any of the standard iteration methods

http://en.wikipedia.org/wiki/Root-finding_algorithm

With an initial value x=50 convergence is very good as well for this example.

Or one could go directly for an interpolation of the inverse like so

y = -9.139036479·10-1 x5 + 10.45267179 x4 - 47.57993558 x3 + 109.4453307 x2 - 151.2957522 x + 150.5091209

[quote author=Udo Klein link=topic=118614.msg895425#msg895425 date=1345215788]

Using an interpolation lookup only takes a few lines of code and reduces the size of the lookup table by a factor of 100 compared to the 'brute force' approach. Since the brute force approach also needs you to work out and type in those 1000 values, the brute force approach doesn't look remotely attractive to me.

Yes it would, that's what I said. But these "few" lines of code are not that easy to get straight for the original poster. Even the code by crossroads is not yet bugfree.

[/quote]

What I posted in reply 11 is equivalent to a table in flash and the access code in one. And... it works. It just needs different data, different returns and more cases.

I realize this is an old thread, but it really helped me a lot, so I wanted to contribute my solution.

My equation is based on the code from CrossRoads, that was super-helpful! I’ve likely just re-written the function in a different way, but I’ve also tested it and it appears to be working.

Where vMin and vMax are min and max voltage readings and iMin and iMax are the edges of the corresponding range:

newVal = iMax - ((iMax - iMin) * ((reading - vMin) / (vMax - vMin)))

Let’s say our reading is 180. Look below at the lookup table. On the left are the voltage readings I’m getting from the sensor and on the right the corresponding measurements. Note that the voltage readings are ascending, while the measurements are descending.

// look-up tables for mapping readings to measurements
int[] theArray = {
  140, 300,
  151, 280,
  162, 260, 
  184, 240, 
  211, 220, 
  250, 200, 
  285, 180, 
  328, 160, 
  371, 140, 
  0, 0
};

180 lies between 162 and 184, the corresponding range is 260 to 240, inverted. We can already assume that our result should be a little bit higher than 240, because 180 is a little bit lower than 184.

iMin = 240
iMax = 260
vMin = 162
vMax = 184

newVal = 260 - ((260-240) * ((180-162)/(184-162)))
newVal = 243.64

:slight_smile:

Here’s the for loop I use to find the range of my reading and then interpolate that to measurements.

for (int i=0; i<theArray.length-2; i=i+2) {
    if ((A >= theArray[i]) && (A <= theArray[i+2])) {
      newVal = theArray[i+1] - ((theArray[i+1]-theArray[i+3]) * ((A-theArray[i]) / (theArray[i+2]-theArray[i])));
      break;
    }
}

newVal = 260 - ((260-240) * ((180-162)/(184-162)))
newVal = 243.64

be sure to use/enforce float math… as when the above values are all integers the outcome is … 260

for (int i = 0; i < theArray.length-2; i += 2)
{
    if ( (A >= theArray[i])  &&  (A < theArray[i+2]) ) 
   {
      newVal = theArray[i+1] - ((theArray[i+1] - theArray[i+3]) * (  ( (float) A - theArray[i] ) / (theArray[i+2] - theArray[i] ) ) );
      break;
    }
}

I always avoid float math on processors without FPU when possible, and even with, I don't like FP. Cast the intermediate values to 32 bit, it's more than a magnitude faster.

GoForSmoke: I always avoid float math on processors without FPU when possible, and even with, I don't like FP. Cast the intermediate values to 32 bit, it's more than a magnitude faster.

very true, but the integer division makes the math less precise as it truncates to 0 straightforward casting to long won't help for the given formula

newVal = 260 - ((260-240) * ((180-162)/(184-162)))

as in the division the numerator > denominator

doing the multiplication first improves the result - and there casting to long will prevent overflow.

newVal = 260 - ((260-240) * (180-162)) /(184-162)

the result is now 244 which is in fact quite good.

robtillaart:

GoForSmoke: I always avoid float math on processors without FPU when possible, and even with, I don't like FP. Cast the intermediate values to 32 bit, it's more than a magnitude faster.

very true, but the integer division makes the math less precise as it truncates to 0 straightforward casting to long won't help for the given formula

newVal = 260 - ((260-240) * ((180-162)/(184-162)))

as in the division the numerator > denominator

doing the multiplication first improves the result - and there casting to long will prevent overflow.

newVal = 260 - ((260-240) * (180-162)) /(184-162)

the result is now 244 which is in fact quite good.

Answer is to work in smaller units. If I want meters to 3 places then I work in millimeters or smaller depending on what math I will be doing. 32 bit int gives me 9 places. 64 bit int gives me 19 places. Both are much faster than 32 bit float.

GoForSmoke: ... Answer is to work in smaller units. If I want meters to 3 places then I work in millimeters or smaller depending on what math I will be doing. 32 bit int gives me 9 places. 64 bit int gives me 19 places. Both are much faster than 32 bit float.

Can be effective but not in this case, the point of my reasoning,

(180-162)/(184-162) => 0 if all numbers are treated as integer

making them long does not change that

(180000-162000)/(184000-162000) => 0L

very true, but the integer division makes the math less precise as it truncates to 0
straightforward casting to long won’t help for the given formula

Answer is to work in smaller units.

newVal = 260000 - (( 260000 - 240000 ) * ( 180000 - 162000 ) / ( 184000 - 162000 )) = 243636

void setup( void )
{
  Serial.begin( 115200 );
  Serial.println( );
  
  long range = 260000L - 240000L;
  long scaleMult = 180000L - 162000L; 
  long scaleDiv = 184000L - 162000L; 
  unsigned long newVal = 260000L - ( range * scaleMult / scaleDiv );

  Serial.println( newVal );
  
  Serial.print( newVal / 1000L );
  Serial.print( "." );
  newVal %= 1000L;
  if ( newVal < 100L )  Serial.print( "0" );
  if ( newVal < 10L )   Serial.print( "0" );
  if ( newVal == 0L )   Serial.print( "0" );
  else Serial.println( newVal );
}

void loop( void )
{
}

I used these techniques for industrial machining (NC/CNC), payroll and billing code long before FPU’s became standard default PC hardware. You want to see someone go off, let them find out that their paycheck is a penny too low or their bill is a penny too high. :roll_eyes:

robtillaart:

GoForSmoke: ... Answer is to work in smaller units. If I want meters to 3 places then I work in millimeters or smaller depending on what math I will be doing. 32 bit int gives me 9 places. 64 bit int gives me 19 places. Both are much faster than 32 bit float.

Can be effective but not in this case, the point of my reasoning,

(180-162)/(184-162) => 0 if all numbers are treated as integer

making them long does not change that

(180000-162000)/(184000-162000) => 0L

That's fine but for other values you can get 0 using 16 bit and non-zero using 32 bit. That is a result of precision.

For this typical algorithm that is not true as the division is to determine the fractional part between two consecutive values in the lookup table. That means the numerator is always smaller than the denominator resulting in 0 in any integer domain. By doing the multiplication part first you prevent to some extend the truncating division.

But you are right that there are many many algorithms that will have better accuracy when doing the math with more bits.

I see. Pre-scaling won't work that way with integers. But I don't pre-scale.

In Forth there is a scaling operator, */, that takes 3 values, multiplies the 1st 2 then divides by the 3rd. It promotes the values to double-word (16 bit to 32 bit) and returns a single word, all integers.

When step 1 is to multiply what is to be scaled by the scaler numerator, it's okay if the denominator is bigger.

multiplies the 1st 2 then divides by the 3rd.

that is exactly what I state

By doing the multiplication part first you prevent to some extend the truncating division.

Forth promotes the values to 32 bit to prevent an overflow of the 16 bits multiplication.
(Good old Forth knows its numerics)

Hello,

Apologies to dig up and old thread, but I'm dealing with a similar issue, the only difference my readings are both ascending:

float tankCalibration[30] = { 0, 0, 20.0, 151, 21.5, 200, 28.0, 300, 35.0, 400, 41.0, 500, 47.5, 600, 54.0, 700, 60.5, 800, 66.0, 900, 73.0, 1000, 79.5, 1100, 86.5, 1200, 96.5, 1300, 109.0, 1401 };

Does anyone know how I adapt the code here to lookup the right value?

Which code are you looking at? I am working on the exact same issue as you right now.

dlabun: Which code are you looking at? I am working on the exact same issue as you right now.

Did you not find the replies in this thread useful?