Tuesday, 9 October 2007

Measuring temperature the easy way

I have been asked for more details on my temperature measurement scheme so I have consolidated some of my previous articles :-

The objective is to measure temperature from room temperature to about 250°C using a thermistor. The thermistor resistance is a extremely non linear. It is approximated by a negative exponential of the reciprocal of absolute temperature.

Ro is resistance at known temperature To, in this case 25°C, expressed in Kelvin. Beta is a second parameter of the thermistor which can be calculated if you know the resistance at two different temperatures or can be found on the data sheet.

The RepRap thermistor is an Epcos B57540G0103+, data sheet here. R25 is 10KΩ and beta is around 3500. Several values are given on the datasheet for different temperature ranges illustrating that the above equation is only an approximation. Here is a graph of its resistance against temperature :-


This can be made more linear by putting a fixed resistors in parallel. The magic value to use appears to be the value of the thermistor at the middle of the temperature range. In this case it is about 470Ω. Here is the resulting combined resistance, the formula for two resistors in parallel is :-

1/R = 1/R1 + 1/R2

I.e. the total conductance is the sum of the two conductances.


The resulting resistance is a lot more linear, however to measure temperature with an ADC we need a voltage rather than a resistance. This is easy, instead of wiring the resistor in parallel connect it in series to a voltage source equal to the full scale voltage of the ADC.



The voltage across the thermistor is then :-

V = Vref . Rth / (R + Rth)

Here is a graph of the the output voltage when Vref is 5V.



Note that the voltage decreases as the temperature rises. This could be inverted by swapping the resistor and thermistor but I prefer to keep one end of the thermistor at 0V so I can use single screened cable. It is also a good idea to put a capacitor across the ADC input to filter out any noise when using long leads like RepRap does. I used a 10uF tantalum bead.

Another consideration is how much power is dissipated in the thermistor as it will cause heating and alter the reading. The maximum dissipation will occur when its value equals the value of the resistor. At this point half the voltage is across the thermistor so the power dissipated in it is :-

P = (Vref / 2)2 / R

In the example above this works out at 13.3mW. The thermistor datasheet specifies a maximum of 18mW and a dissipation factor (in air) of 0.4 mW /K. I think this means that the temperature will rise by 33°C by self heating. The error would be less when not in air, but it is still perhaps a bit high. My system uses a Vref of 1.5 volts which, because it is a square law, only dissipates 1.2mW giving a 3°C rise at the mid range temperature in air.

For a 5V system is is probably worth sacrificing some of the ADC resolution to reduce the self heating error. This can be done by using two resistors :-



The full scale voltage is now :-

Vfsd = Vref * R1 / (R1 + R2)

We also want the source impedance of this voltage, which is R1 in parallel with R2, to be 470Ω.

1/R = 1/R1 + 1/R2

Solving these simultaneous equations gives :-

R1 = R / (1 - Vfsd / Vref)

R2 = R . R1 / (R1 - R)

So for Vfsd = 1.5V, Vref = 5V and R = 470:

R1 = 671Ω and R2 = 1569Ω, preferred values are 680 and 1K6.

And finally here is the Python code to work out the temperature :-
from math import *

class Thermistor:
"Class to do the thermistor maths"
def __init__(self, r0, t0, beta, r1, r2):
self.r0 = r0 # stated resistance, e.g. 10K
self.t0 = t0 + 273.15 # temperature at stated resistance, e.g. 25C
self.beta = beta # stated beta, e.g. 3500
self.vadc = 5.0 # ADC reference
self.vcc = 5.0 # supply voltage to potential divider
self.vs = r1 * self.vcc / (r1 + r2) # effective bias voltage
self.rs = r1 * r2 / (r1 + r2) # effective bias impedance
self.k = r0 * exp(-beta / self.t0) # constant part of calculation

def temp(self,adc):
"Convert ADC reading into a temperature in Celcius"
v = adc * self.vadc / 1024 # convert the 10 bit ADC value to a voltage
r = self.rs * v / (self.vs - v) # resistance of thermistor
return (self.beta / log(r / self.k)) - 273.15 # temperature

def setting(self, t):
"Convert a temperature into a ADC value"
r = self.r0 * exp(self.beta * (1 / (t + 273.15) - 1 / self.t0)) # resistance of the thermistor
v = self.vs * r / (self.rs + r) # the voltage at the potential divider
return round(v / self.vadc * 1024) # the ADC reading

19 comments:

  1. If I had a problem like this:
    A thermistor, with R0=120000ohms and B=1132Km is used to measure the temperature change of the hypothermic patient. The change between 37.o and 37.2 degrees C. Calculate the change in resistance.

    Can you help?

    ReplyDelete
  2. Just put the numbers in the first equation of my article to get two resistance values for the two temperatures and subtract them. Don't forget to convert the temperatures to Kelvin first.

    ReplyDelete
  3. nophead,

    I'm trying to build a PID controller that heats a nichrome wire and measures the temperature with a thermistor. I'm confused at the part where you mention using a second resistor to reduce self-heating error. You say "We also want the source impedance of this voltage, which is R1 in parallel with R2, to be 470Ω." I don't see where R1 is parallel with R2

    Thanks,

    Nikhil
    nsthorat@gmail.com

    ReplyDelete
  4. A regulated power supply has an output impedance very close to zero. That means the impedance of a potential divider is the impedance of the two resistors in parallel.

    I.e. a potential divider is exactly equivalent to a voltage source equal to the potential defined by the resistors and a source impedance of the two in parallel.

    ReplyDelete
  5. Hey nophead, it's Nikhil again.

    I've been trying to implement the python code you wrote to make a lookup table, but I'm having trouble with the actual output values. It seems as though, with the adc -> temp conversion function I'm getting directly related adc and output temperature values (as the adc value increases, so does the output temperature).

    This is clearly not correct, is there a more up to date python script that you have (you reference this thermistor measurement in several places in your other posts).

    Thanks,

    Nikhil

    ReplyDelete
  6. The code is correct as I cut it from a working program, so I am not sure what you did wrong but there is a script in SVN that uses the same maths to make tables http://reprap.svn.sourceforge.net/viewvc/reprap/trunk/reprap/firmware/Arduino/utilities/createTemperatureLookup.py?view=markup&pathrev=3448

    ReplyDelete
  7. I'm trying to compile the python code in the link you posted, and I'm getting a "Math domain error" at line 57 "return (self.beta / log(r/self.k)) - 273.15"

    I've been trying to debug why it's doing that, and I've noticed that the resistance calculation goes extremely high then drops to a very large negative number (which causes the domain problem on the log function). This happens when the calculated voltage from the ADC becomes higher than the bias voltage (so the resistance calculation has a denominator of a very small negative number), so I can only get working temperature values when the adc is below about 300.

    I am using all the defaults except for the max_adc being 680 because R1 is 680.

    ReplyDelete
  8. Yes that is because it is impossible for the ADC reading to be more than the bias voltage.

    The bias voltage is the voltage that the potential divider formed by the resistors produces when the thermistor is open circuit, i.e. infinite. A finite thermistor resistance will always produce a lower value than that.

    The program calculates the max_adc value and the default value for R1 is 680, so I don't understand your last statement.

    // ./createTemperatureLookup.py --r0=10000 --t0=25 --r1=680 --r2=1600 --beta=3947 --max-adc=305
    // r0: 10000
    // t0: 25
    // r1: 680
    // r2: 1600
    // beta: 3947
    // max adc: 305
    #define NUMTEMPS 19
    short temptable[NUMTEMPS][2] = {
    {1, 608},
    {17, 262},
    {33, 215},
    {49, 188},
    {65, 170},
    {81, 156},
    {97, 145},
    {113, 135},
    {129, 126},
    {145, 117},
    {161, 109},
    {177, 102},
    {193, 94},
    {209, 86},
    {225, 78},
    {241, 69},
    {257, 59},
    {273, 47},
    {289, 28}
    };

    ReplyDelete
  9. can i please ask about max_adc?
    about gen3 EC2.2, mcu 168 with a 10 bit adc, the number of values is 1024(1024=2^10), with first being 0 and the max being 1023; in the script is correct 1023 (as in the max value) or its correct 1024 (as in the number of total values, e.g. zero being first)?
    Sorry for such a noob question, and Tyvm.

    ReplyDelete
  10. I think 1023 is correct as that is the biggest value you read from the ADC.

    ReplyDelete
  11. Just wanted to say thanks for a superb blog.
    You've helped me understand Thermistors, and I get the feeling you'll be handy when I get my bits for my RepRap too!

    Do you clip out "well done" and "thank you" comments? I don't see much apart from Noobmans manners.

    ReplyDelete
  12. No I only delete spam comments.

    ReplyDelete
  13. Nice Python script and useful blog - Thanks for you work and time

    Ray

    ReplyDelete
  14. Thanks for the posting. The hot end is the next thing on my list.

    There is a tiny typo at the end: "... values are 680 and 1K6."

    ReplyDelete
  15. Hi Dave,
    Sorry, but what is the typo?

    "preferred values are 680 and 1K6"

    It looks OK to me.

    ReplyDelete
  16. It looks to me as if it should say "1.6k"? If it is not a typo transposing the period and the K, maybe I'm reading it in american english. Would you speak 1600 as "one k six"? I'd speak it aloud as "one point six k" or "sixteen-hundred"

    ReplyDelete
  17. Yes it is 1.6K when spoken. 1K6 is a standard way of writing it though on schematics and components as it uses less space. Perhaps in prose it should be 1.6K.

    ReplyDelete
  18. That makes sense. The little decimal points may not always print well on real components. I understand now -- my electronics experience was all pre-SMD.

    ReplyDelete