Thermistor Reading Trouble

Hi All,

I'm currently undertaking a project that uses a thermistor and a voltage divider circuit to calculate the temperature.
I've set up a room temperature test and my circuit gives credible results against another reference thermometer and I'm satisfied with its performance in that range. However, once I place my thermistor probe into a water basin that is kept at a constant temperature by using a hot plate, my thermistor gives a reading that is 5-6 degrees more than my reference thermometer. Does any one have any ideas why there is such a degradation in performance and how can I rectify this?

The function that is used for the voltage reading:

/*
#####GET TEMP FLOAT DESCRIPTION#####  
This function converts a thermistor reading into a corresponding temp in ^C
The thermistor is incorporated into a Voltage Divider Circuit:

   +Vref---[Thermistor]---+--[1.8K]---GND
                          |
                         ADC @ thermPin

ADC Values were externally calculated from the calibration table using: ADC = 1023*1k8/(Rtherm+1k8)
The LUT is an array of integer constants containing the predicted ADC values for all temperatures between +7^C to +49.9^C.
The array index starts at zero, which corresponds to a temperature of +7^C.
A linear interpolation between the two closest entries is performed to give a finer output resolution.
*/

//##CONSTANTS##
/*1K8*/
const int LUT_Therm[] PROGMEM = //LUT containing ADC values - saved into program memory as running low on SRAM
{
  223, 224, 225, 226, 227, 228, 229, 230, 231, 232,         //7^C to 7.9^C
  233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
  244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
  255, 256, 257, 258, 259, 260, 261, 263, 264, 265,
  266, 267, 268, 269, 271, 272, 273, 274, 275, 277,
  278, 279, 280, 281, 283, 284, 285, 286, 287, 289,
  290, 291, 292, 294, 295, 296, 297, 299, 300, 301,           //13^C to 13.9^C
  302, 304, 305, 306, 308, 309, 310, 312, 313, 314,
  315, 317, 318, 319, 321, 322, 323, 325, 326, 327,
  329, 330, 332, 333, 334, 336, 337, 338, 340, 341,
  343, 344, 345, 347, 348, 350, 351, 353, 354, 355,
  357, 358, 360, 361, 363, 364, 366, 367, 369, 370,
  371, 373, 374, 376, 377, 379, 380, 382, 383, 385,
  387, 388, 390, 391, 393, 394, 396, 397, 399, 400,
  402, 404, 405, 407, 408, 410, 411, 413, 415, 416,
  418, 419, 421, 423, 424, 426, 428, 429, 431, 433,
  434, 436, 438, 439, 441, 443, 444, 446, 448, 449,
  451, 453, 454, 456, 458, 460, 461, 463, 465, 466,
  468, 470, 472, 473, 475, 477, 479, 480, 482, 484,
  486, 488, 489, 491, 493, 495, 497, 498, 500, 502,
  504, 506, 507, 509, 511, 513, 515, 517, 519, 520,
  522, 524, 526, 528, 530, 532, 534, 535, 537, 539,
  541, 543, 545, 547, 549, 551, 553, 555, 557, 559,
  560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 
  580, 582, 584, 586, 588, 590, 592, 594, 596, 598,
  600, 602, 604, 606, 608, 611, 613, 615, 617, 619,
  621, 623, 625, 627, 629, 631, 633, 635, 638, 640,
  642, 644, 646, 648, 650, 652, 655, 657, 659, 661,
  663, 665, 667, 670, 672, 674, 676, 678, 680, 683,
  685, 687, 689, 691, 694, 696, 698, 700, 702, 705,
  707, 709, 711, 714, 716, 718, 720, 723, 725, 727,
  729, 732, 734, 736, 738, 741, 743, 745, 747, 750, 
  752, 754, 757, 759, 761, 764, 766, 768, 771, 773,
  775, 778, 780, 782, 785, 787, 789, 792, 794, 796,
  799, 801, 803, 806, 808, 811, 813, 815, 818, 820,
  822, 825, 827, 830, 832, 834, 837, 839, 842, 844,
  847, 849, 851, 854, 856, 859, 861, 864, 866, 868,
  871, 873, 876, 878, 881, 883, 886, 888, 891, 893,
  896, 898, 901, 903, 906, 908, 911, 913, 916, 918, 
  921, 923, 926, 928, 931, 933, 936, 938, 941, 943,
  946, 948, 951, 953, 956, 958, 961, 963, 966, 969,
  971, 974, 976, 979, 981, 984, 987, 989, 992, 994,
  997, 999, 1002, 1005, 1007, 1010, 1012, 1015, 1017, 1020, //40^C to 49.9^C
  1023                                                     //50^C
};
const int constLUTArraySize = sizeof(LUT_Therm)/sizeof(LUT_Therm[0]); //number of elements in array

float getTempFloat(int pinAnalogue)
{
  //#####VARIABLE DELARATIONS#####
  int i; //used as a counter / array index 'pointer'
  int LUT_val; //stores the current value of the LUT eg. i
  int LUT_prevVal; //stores the previous value of the LUT eg. i-1
  int ADCValue; //stores value return by reading anaolgue pin
  float fraction; //returns the interpolated temp
  
  //take an analogue reading.
  analogRead(pinAnalogue);  //dummy reading to settle the MUX
  delay(10);
  ADCValue = analogRead(pinAnalogue);
  /*DEBUGGING*/
  /*Serial.print(F("ADC: "));
  Serial.println(ADCValue);*/

  //find i such that the ADC value lies between LUT[i-1] and LUT[i]
  for (i = 0; i < constLUTArraySize; i++)
  {
    LUT_prevVal = LUT_val; //LUT value from previous loop is now previous value
    LUT_val = pgm_read_word(&LUT_Therm[i]); //store current LUT val ++ since LUT is in PROGMEN - have to use pgm_read_word and array addresses to get value
    /*DEBUGGING*/
    /*Serial.print(F("Previous LUT Val = "));
    Serial.println(LUT_prevVal);
    Serial.print(F("Current LUT Val = "));
    Serial.println(LUT_val);*/
    if (LUT_val >= ADCValue) //this is the ADC Hi value
    {
      break; //break out of for
    }//end if
  }//end for
  //special case: adc == LUT[0]
  if (i == 0 && ADCValue == LUT_val)
  {
    /*DEBUGGING*/
    /*Serial.print(F("In special case of LUT[0]"));*/
    return 7.0; //return first value stored (223 = 7^C)
  }//end if
  // Report an error if out of range.
  if (ADCValue < pgm_read_word(&LUT_Therm[0]) || ADCValue >= pgm_read_word(&LUT_Therm[constLUTArraySize-1]))
  {
    /*DEBUGGING*/
    /*Serial.print(F("In special case of out of LUT range"));
    Serial.print(F("ADCVal = "));
    Serial.println(ADCValue);
    Serial.print(F("LUT[0] = "));
    Serial.println(pgm_read_word(&LUT_Therm[0])); 
    Serial.print(F("LUT[MAX] = "));
    Serial.println(pgm_read_word(&LUT_Therm[constLUTArraySize-1]));*/
    return NAN;
  }//end if
  //return interpolated temperature
  fraction = float(ADCValue - LUT_prevVal) / (LUT_val - LUT_prevVal);
  /*DEBUGGING*/
  /*Serial.print(F("Fraction = "));
  Serial.println(fraction);
  Serial.print(F("getTempFloat returns = "));
  Serial.println(7.0 + (i-1 + fraction) / 10.0,2);*/
  return 7.0 + (i-1 + fraction) / 10.0;
}//end getTempFloat function

Any help is appreciated.
Thanks.

and a VDR circuit

A what???

You may be using the "wrong" equation/expression. A voltage-divider's output (the way a thermistor is usually used) is not linear with respect to changes in resistance and your linearization math may not be perfect.

The thermistor itself may be non-linear at temperature extremes.

You could also be running into practical-range limits or you may be loosing resolution at "extreme" temperatures (for one reason or another).

What's the "raw" analog reading when it fails?

Do you have a multimeter to check the resistance of the thermistor at this temperature (alone, disconnected from the circuit)?

my thermistor gives a reading that is 5-6 degrees more than my reference thermometer...

...can I rectify this?

Typically, there are two "calibrations". Imagine an X-Y graph with the actual value on the X-axis and the reading on the Y-axis. A perfectly calibrated reading would be a straight line starting at zero and going-up at a 45 degree angle.

The first calibration is offset where you'd add or subtract a constant value from the reading. You normally adjust the zero-point by adjusting offset.* Since it's addition/subtraction, it changes all readings by the same amount (not what you want if your readings are correct at room temperature). The offset correction can be positive or negative and if no offset correction is needed the offset correction is zero.

The 2nd is gain (or slope) where you multiply by a correction factor, The slope correction can be a positive or negative number and it can greater or equal to one. If no slope correction is needed, the correction factor is 1. You normally adjust the gain at the maximum reading (or there are more advanced "best fit" methods). The gain/slope doesn't affect zero and it has less effect on lower readings than at higher readings (because it's multiplication).

Or, there are more advanced calibration methods where you have multiple calibration points along the line/curve with interpolation in-between. Where I work we have a digital-to-analog converter with 10 calibration points (as well as offset & slope).

  • In your application, you'd probably "zero" (adjust the offset) at room temperature instead of zero degrees.

For the most accurate results, each thermistor needs to be individually calibrated. The calibration table built into your program uses a very simplistic and inaccurate model for the thermistor response. Here is a much better one.

Adafruit has a good overview on the calibration process in general.

EoinScully:
However, once I place my thermistor probe into a water basin that is kept at a constant temperature by using a hot plate, my thermistor gives a reading that is 5-6 degrees more than my reference thermometer. Does any one have any ideas why there is such a degradation in performance and how can I rectify this?

Thanks.

is this a bare probe or a waterproof one ?

Boardburner2:
is this a bare probe or a waterproof one ?

The thermistor would be bare - would putting it into some sort of waterproof housing suffice? When finished, my probe won't actually be used in water but to calibrate it I'm using the water basin.

jremington:
For the most accurate results, each thermistor needs to be individually calibrated. The calibration table built into your program uses a very simplistic and inaccurate model for the thermistor response.

I got my LUT values by first measuring the resistance of my thermistor at the temperature ranges I require then used the voltage divider rule to see what voltage it would be? Is that enough or do I need to calibrate it again?

EoinScully:
The thermistor would be bare - would putting it into some sort of waterproof housing suffice? When finished, my probe won't actually be used in water but to calibrate it I'm using the water basin.

Water conducts electricity, you need to sort that out first.

Hi,
Put the thermistor in a plastic bag in the water, the water pressure will keep the bag pressed to the thermistor, may just take a little more time to equalise.

Tom... :slight_smile:

I got my LUT values by first measuring the resistance of my thermistor at the temperature ranges I require

OK, I misunderstood the comment in the code.

Did you actually measure every value that went into that LUT?

EoinScully:
Hi All,

I'm currently undertaking a project that uses a thermistor and a voltage divider circuit to calculate the temperature.
I've set up a room temperature test and my circuit gives credible results against another reference thermometer and I'm satisfied with its performance in that range. However, once I place my thermistor probe into a water basin that is kept at a constant temperature by using a hot plate, my thermistor gives a reading that is 5-6 degrees more than my reference thermometer. Does any one have any ideas why there is such a degradation in performance and how can I rectify this?

Where did you get the correction table for that thermistor?

In order to use a thermistor and get any kind of decent readings, you need to calibrate each one individually.

And, since they are non-linear, you need to take many samples at different points and generate a polynomial curve to correct the data.

I would suggest, if possible, save yourself the grief and use an ordinary LM-34 or LM-35 (it's a little TO-93 "transistor" type of package that gives you a calibrated millivolts per degrees F or degrees C depending on the part number (I think the 34 if F and the 35 is in C)).

DVDdoug:
A what???

You may be using the "wrong" equation/expression. A voltage-divider's output (the way a thermistor is usually used) is not linear with respect to changes in resistance and your linearization math may not be perfect.

The thermistor itself may be non-linear at temperature extremes.

You could also be running into practical-range limits or you may be loosing resolution at "extreme" temperatures (for one reason or another).

What's the "raw" analog reading when it fails?

Do you have a multimeter to check the resistance of the thermistor at this temperature (alone, disconnected from the circuit)?
Typically, there are two "calibrations". Imagine an X-Y graph with the actual value on the X-axis and the reading on the Y-axis. A perfectly calibrated reading would be a straight line starting at zero and going-up at a 45 degree angle.

The first calibration is offset where you'd add or subtract a constant value from the reading. You normally adjust the zero-point by adjusting offset.* Since it's addition/subtraction, it changes all readings by the same amount (not what you want if your readings are correct at room temperature). The offset correction can be positive or negative and if no offset correction is needed the offset correction is zero.

The 2nd is gain (or slope) where you multiply by a correction factor, The slope correction can be a positive or negative number and it can greater or equal to one. If no slope correction is needed, the correction factor is 1. You normally adjust the gain at the maximum reading (or there are more advanced "best fit" methods). The gain/slope doesn't affect zero and it has less effect on lower readings than at higher readings (because it's multiplication).

Or, there are more advanced calibration methods where you have multiple calibration points along the line/curve with interpolation in-between. Where I work we have a digital-to-analog converter with 10 calibration points (as well as offset & slope).

  • In your application, you'd probably "zero" (adjust the offset) at room temperature instead of zero degrees.

If you're talking about slope/intercept (i.e. a first order Y=MX+B) kind of thing, that won't work with a thermistor. They are not linear.

The Steinhart-Hart equation defines the (non-linear) formula for a thermistor. If that equation makes you a little cross-eyed you could use MultiMap to perform interpolation between your known resistance/temperature data points.

Hi,
Is your circuit description correct, you have the thermistor and resistor connected from Vref to gnd?
Your resolution will be limited, because there will be no way your analog input will have values ranging from gnd to Vref.
Try from 5V and gnd.

Tom.... :slight_smile:

Is your thermistor an NTC (maybe you stated this but it didn't jump out)? if so it needs to go from GND to the resistor. Also does your data sheet have the equation to compute the temp based on resistance?

Here's what I do.

Kris

Chagrin:
The Steinhart-Hart equation defines the (non-linear) formula for a thermistor. If that equation makes you a little cross-eyed you could use MultiMap to perform interpolation between your known resistance/temperature data points.

The way the OP has it wired is an attempt to linearize the thermistor. And, to use the Steinhart-Hart algorithm, you need to know (or derive) the coefficients it uses. Which is basically what I said.

Easier way is to take several readings at different temperatures (the more the better), then generate an Nth order polynomial to linearize the data. The more samples you have, the higher order polynomial you can generate. But, you need to be careful that the polynomial doesn't fly off into never-never land at the ends (they have a habit of doing that) so at least two of the calibration measurements should be as close to "coldest" and "hottest" as possible.

As far as "Multimap" is concerned, that looks like a simple piecewise-linear approximation which really won't "do it" for a thermistor (unless accuracy at every temperature in the range is unimportant).