Linearizing an NTC Thermistor to Simplify Calculations

copyright, P. H. Anderson, Sept 25, '10


Introduction

This note discusses how a common NTC thermistor may be linearized to simplify calculations. The technique utilizes two identical thermistors and two resistors. I was able to get a reasonable straight line but opted for three piece wise lines to achieve greater accuracy.

Detailed Description

Figure #1 is a schematic of the linearized network which includes two identical NTC thermistors both located at the measurement point and two fixed resistors R1 = 15K and R2 = 4.7K. In Figure #2, I have redrawn the two thermistors to stress that this might be thought of as a three termianl device. Yellow Spring Instrument, acquired by Measurements Specialties offers such a three terminal thermistor as a 44201 for nominally $52.00.

Yellow Spring apparently used this technique for linearization and protected it in US Patent 3,316,765 issued in 1967. The patent helped but, of course, lacked any detailed calculations. I have spent a lot of time using Excel to exploring this problem over the past few weeks and knowing the limited computational capabilities of some 44 years ago, my hat is sure off to these folks.

I improvised with two small Vishay 2381 640 66103 10K NTC thermistor which cost me about 25 cents. The resistors are a few pennies. So, you should be able to configure this type of circuit for about a $1.00.

I used a spreadsheet quickly determine the linearity of Req as I modified the values of fixed resistors R1 and R2. Col A are the temperatures in degrees C and Col B are the corresponding thermistor resistances. (I cut and pasted these from the data sheet. Col D is the series equivalent of the 15K fixed resistor and the left most thermistor. Col E is the second thermistor and Col F is the paralell equivalent of Col D and E. Col H is the Req between point A and B.

A plot of Col H vs Col A appears reasonably linear. I spent a good deal of time striving for Req series that was linear enough to be accurately approximated with a single equation of the form y = mx + b, actuall did come quite cloase. But, in the end, I had to check myself and argue, "but today even the most promitive microprocessor handle three separte straight lines. At the bottom of Figure #3 I developed these equations and the results are in Col I. The plot of Col I this lies right on top of that of Col H as a function of temperature.

Interfacing with a Processor

Figure 5 illustrates the use of a volatage divider to determine the Req of the network. Knowing Req, the Tc may be caculated using the straight line approximations.

Using voltage division;

(1)  Vin = Req * Vref / (Req + 2200);

Expressing Vin as a function of ADVal

(2)  Vin = ADVal * Vref / 1024.0

Equating (1) and (2), note that Vref cancels out;

(3) Req = 2200 / ((1024 / ADVal) - 1)

or

(4) Req = 2200 * ADVal / (1024 - ADVal)
It is worthy of note that the product of 2200 and ADVal is larger that 16-bits.

Sample Program - PICAXE

In the following, 32 A/D conversions are performed and these are averaged. Equation (2) is used to determine Req. As 2200 and ADVal are both 16-bit quantities, I used the ** operator.

     NumHigh = 2200 ** ADVal ' High 16 bits in NumHigh
     NumLow = 2200 * ADVal   ' Low 16 bits in NumLow
	 
     Denom = 1024 - ADVal
There may well be a more elegant technique for determining the quotient, but I used repeated subtraction of Denom for the 32-bit NumHigh and NumLow.
     Req = 0
     Do While NumHigh <> 0 Or NumLow >= Denom
       If NumLow < Denom Then
          NumHigh = NumHigh - 1
       End If
       NumLow =  NumLow - Denom
       Req = Req + 1
     Loop

The Tc is then calculated using one of three different linear equations.

Range 1.  Tc_10 = 974 - Req * 10 / 39

Range 2.  Tc_10 = 1084 - Req * 10 / 31

Range 3.  Tc_10 = 1340 - Req * 10 / 17

This code uses a mere 200 program words (of 4096 on a PICAXE-20X). With a few simple modifications, I mapped this over to a PICAXE-08M which has a 256 byte program capacity. My guess is that one might be able to accommodate at least one thermistor (perhaps two) and control an output so as to realize a thermostat for nominally $2.50 in parts.


' THERM_LIN - Linear Thermistor
'
' PICAXE-20X2 ' Uses about 200 program words
'
' Peter H Anderson, Sept 29, '10

  Symbol ADVal = W0	' note that I am doubling up on RAM locations
  Symbol Sum = W1
  Symbol NumHigh = W1
  Symbol NumLow = W2
  Symbol Denom = W3
  Symbol Req = W4
  Symbol Req_10 = W1
  Symbol Tc_10 = W2
  Symbol X = W0

  Symbol N = B9
  Symbol Whole = B10
  Symbol Fract = B11

  DirsB = %11111111
  ADCSetup = %0000000000001000 ' ADC3

  Pause 1000

  Do
     GoSub ADConv
     GoSub CalcReq
     SerTxD ("Req = ", #Req, CR, LF);
     GoSub CalcTc_10
     GoSub DisplayTc
     Pause 60000 ' one minute
  Loop

ADConv:

   Sum = 0

   For N = 1 to 64			' sum 64 readings
      ReadADC10 3, ADVal
      Sum = Sum + ADVal
   Next

   ADVal = Sum / 64			' calculate the average

   Return

CalcReq:

     NumHigh = 2200 ** ADVal
     NumLow = 2200 * ADVal
     Denom = 1024 - ADVal

     ' Now do the division by repeated subtraction
     Req = 0
     Do While NumHigh <> 0 Or NumLow >= Denom
       If NumLow < Denom Then
          NumHigh = NumHigh - 1
       End If
       NumLow =  NumLow - Denom
       Req = Req + 1
     Loop

     Return ' result in word Req

CalcTc_10:

     
     Req_10 = Req * 10

     SerTxD ("Req_10 = ", #Req_10, CR, LF)
   
     If Req > 3700 Then
         Tc_10 = 999 ' error
     ElseIf Req > 1838 Then
         
         X = Req_10 / 39   ' Req * 10 / -39 - 3797 * 10 / -39
         Tc_10 = 974 - X
         SerTxD ("Tc_10 = ", #Tc_10, CR, LF)
         
     ElseIf Req > 827 Then
         X = Req_10 / 31   ' Req * 10 / -31 - 3760 * 10 / -32
         Tc_10 = 1084 - X
     ElseIf Req > 570 Then
         X = Req_10 / 17   ' Req * 10 / -17 - 2278 * 10 / -17
         Tc_10 = 1340 - X
     Else
         Tc_10 = 0
     EndIf

     Return ' result is Tc_10
     

DisplayTc:

     Whole = Tc_10 / 10		' Tc whole
     Fract = Tc_10 % 10		' Tc tenths of a degree

     SerTxD (#Whole, ".", #Fract, CR, LF)
     
     Return


Sample Code - Arduino

The Arduino code closely follows the PICAXE.

Note that in performing the calculations, I opted not to use floating point math, only to confirm I could.

But, I did at first run into a problem which I concluded was truncation.

        R_therm = 2200 * adval / (1024 - adval);  // 2200 * adval will truncate
2200 and adval are both integers and thus the computed product is 16 bits, with the most important higher order bits beinging lost.

This is remedied by typecasting either 2200 or adaval before multiplying.

        R_therm = ((unsigned long) 2200) * adval / (1024 - adval); 


// LIN_THERM - Arduino
//
// Continually measures temperature using linearized thermistor.
//
// copyright, Peter H Anderson, Oct 1, '10

unsigned int ad_meas(byte channel, byte num);
void display_temperature(int T);

void setup()
{
   Serial.begin(9600);
   delay(2000);
}

void loop()
{
    unsigned long Rtherm;
    unsigned int adval, R_therm, R_therm_10;
    int T_10;
    
    Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!");
    
    while(1)
    {
        adval = ad_meas(0, 32);
    
        R_therm = ((unsigned long) 2200) * adval / (1024 - adval); 
        
        if (R_therm > 3700)
        {
            T_10 = 9999;   // out of range
        }
        else if (R_therm > 1830)
        {
            T_10 = (R_therm * 10 - 37970) / -39;
        }
        else if (R_therm > 827)
        {
            T_10 = (R_therm * 10 - 33600) / -31;
        }
        else if (R_therm > 570)
        {
            T_10 = (R_therm * 10 - 22780) / -17;
        }
        else
        {
            T_10 = 9999;  // out of range
        }
        display_temperature(T_10);
        
        delay(5000);
    }        
}

unsigned int ad_meas(byte channel, byte num)
{
   unsigned int sum = 0, adval;
   byte n;
  
   for (n=0; n<num; n++)
   {
       adval = analogRead(channel);
       sum = sum + adval;
   }
   return(sum / num);
}

void display_temperature(int T)
{
    int whole, fract;
    
    whole = T / 10;  fract = T % 10;
    
    Serial.print(whole, DEC);
    Serial.print(".");
    Serial.println(fract, DEC);
}