How to create lookup table

How do I go about creating a lookup table:
I need to output temperature based on adc readings and the sensor i am using is not linear so i cannot simply calculate out the temperatures.
Here is a rough example of the temp vs voltage i need.

Temp Voltage
50° F (10° C) 3.51 V DC
68° F (20° C) 3.07 V DC
86° F (30° C) 2.60 V DC
104° F (40° C) 2.13 V DC
122° F (50° C) 1.7 V DC
140° F (60° C) 1.33 V DC
158° F (70° C) 1.02 V DC
176° F (80° C) 0.78 V DC
194° F (90° C) 0.60 V DC
212° F (100° C) 0.46 V DC

one way is to skip values

50,3.51,68.3.07,86,2.60

then you can use a for loop and skip every other value

I doubt your voltage readings will ever be that exact.
Make an array with the 20 entries you do know:
flloat vArray[20] = {
0.46, 100,
0.6, 90,
0.78, 80,
1.02, 70,
1.33, 60,
1.7, 50,
2.13, 40,
2.6, 30,
3.07, 20,
3.51, 10,};

Get a volt reading, see which 2 it is in between, interpolate between the temps.
Make it a straight line between temps, use a custom interpolation scale if needed.

[edit - fixed naming to be consistent)

temp = base temp + (high temp- base temp)(Vread - Vbase) / (Vhigh - Vbase)
ex.0.48
temp = 194 + (212-194)
(0.48-0.46)/(0.6-0.46) = 196.57
ex 0.58
temp = 194 + (212-194)*(0.58-0.46)/(0.6-0.46) = 209.42

Nice idea there, I use a look up table to make a rough mapping from a analogue input to a nonlinear scale for the 'intervention decay rate' of a Yaw Control RC Car project, its based on a switch case and sticks out as something that really should be change in the code. It had not occured to me to use space between the steps in the scale, this gives me a good reason to get rid of the switch/case and put something more useful in its place instead.

Still learning,

Thanks

Duane B

Yaw Control - Looking back at it, I knew I didn't like the mapping code for some reason, and its worse than I remembered -

It's unfortunate that the pairs of figures you're given are nicely spaced according to the output value not the input.

Normally using a lookup table you would use the input value as an index into the table and select the two nearest entries, then interpolate between them to get the result. However, you can use the table in reverse, and this would suit the conversion data better in this case: iterate through the table find the two cells that contain the closest matches to your input; the indices give you the corresponding output values - interpolate between them to get your result.

You are getting an analog value from 0 to 1023. You create an array LookUpTable[1024] = { and list your temperature values that match that input value}

Such a large lookup table would most probably exhaust the SRAM. I would put it into flash with PROGMEM macros. In addition the output fits into single bytes. Actually the output seem to be confined to a 0-100 degrees centigrade scale. So what I would do is: I would create an array (in PROGMEM) for the degrees centigrade.

VoltageByCentigrade[101] = { ...}

Then I would do implement a linear or binary search (depending on the performance requirements vs. ease of implementation) for the closes match. The index of the match gives the desired temperature in centigrades.

Which leaves the question how to populate this table. I would do this with the help of whatever interpolation tool is at hand. Most probably excel or open office but your mileage may vary.

Given that you only have ten data points to hold in the map, I really can't see the point in making up a 'brute force' lookup table that attempts to hold the result of every possible value.

Just put the values you have been given in a 1-D array with ten entries in it, and use an interpolation algorithm to do the lookup.

Well, I should have written "in your shoes I would ...". Actually I would do neither. I would actually determine a suitable interpolation formula and use no tables at all. However I know how do this without looking into any literature. But the OP seems to lack quite a lot of mathmatical background, so in his case a brute force lookup seems appropriate to me.

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.

[edit - fixed naming to be consistent)

temp = base temp + (high temp- base temp)(Vread - Vbase) / (Vhigh - Vbase)
ex.0.48
temp = 194 + (212-194)
(0.48-0.46)/(0.6-0.46) = 196.57
ex 0.58
temp = 194 + (212-194)*(0.58-0.46)/(0.6-0.46) = 209.42

flloat vArray[20] = {
0.46, 100, //0
0.6, 90, //2
0.78, 80, //4
1.02, 70, //6
1.33, 60, //8
1.7, 50, //10
2.13, 40, //12
2.6, 30, //14
3.07, 20, //16
3.51, 10,}; //18

for (x= 0; x<18; x=x+2){
  if (Vread >=Varray[x] && Vread <=Varray[x+2]){
    calctemp = Varray[x+3] + ( (Varray[x+1] - Varray[x+3]) * ( (Vread - Varray[x]) / (Varray[x+2] - Varray[x]) ) )
 // temp = base temp + (high temp- base temp) * (Vread - Vbase) / (Vhigh - Vbase)
  }
}

or something pretty close to that. Maybe tweak the array formatting or the start/stop points of the search a little

Not exactly standard C/C++ but tested and working on my UNO:

byte a;

void setup() {
  Serial.begin( 9600 );
  a = 25;
  switch ( a )
  {
    case 0 ... 19 :
      Serial.println( "0 to 19" );
      break;
    case 20 ... 39 :
      Serial.println( "20 to 39" );
      break;
    case 40 ... 59 :
      Serial.println( "40 to 59" );
      break;
    default :
      Serial.println( "60+" );
  }
}

void loop() {};
1 Like

CrossRoads:
flloat vArray[20] = {
0.46, 100, //0
0.6, 90, //2
0.78, 80, //4
1.02, 70, //6
1.33, 60, //8
1.7, 50, //10
2.13, 40, //12
2.6, 30, //14
3.07, 20, //16
3.51, 10,}; //18

You table is twice as big as it needs to be - since the sample points are at exact 10C increments, that dimension of the array is redundant. All you need is a 1-D array with ten values in it. The algorithm would be to walk the array to find the two elements with values that straddle the input value; the array indices for these two elements give you the two corresponding output values. Then you do a linear interpolation between the two input values and the two output values.

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

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

1 Like

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

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;
    }
}