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


  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?

  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.

  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



  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.

  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).



  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

  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.

  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}

  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.

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

  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.

  12. No I only delete spam comments.

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


  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."

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

    "preferred values are 680 and 1K6"

    It looks OK to me.

  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"

  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.

  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.

  19. About the 10uF capacitor...

    Does this make the circuit a low-pass filter with a cutoff frequency of 1/(2*pi*R_thermistor*C)=33Hz, one with 1/(2*pi*R2*C)=9.9Hz, or something else?

  20. It will make a low pass filter with the resister being parallel combination of the two resistors and the thermistor. I think it will be much higher frequency than Hz though. I think you have a decimal point in the wrong place.

  21. Like R=1/(1/R1+1/R2+1/Rtherm) ?

    I've been quite error prone lately, so I wouldn't be surprised. I did my math in R as:

    > C=10e-6;R=c(470,1600,680,1/(1/470+1/670+1/1600), 1); 1/(2*pi*R*C)
    [1] 33.862754 9.947184 23.405139 67.564407 15915.494309

    Looking at the last schematic, it seems like it is an analog adder and there are separate filters depending on where the noise is injected. Vref noise would be filtered by the R2*C lowpass, but noise induced in the long hot lead to the thermistor would be shunted directly to V. Noise in the ground thermistor lead would be filtered by Rtherm*C lowpass.

    If I want to filter out 60Hz noise on my long leads, it seems like I'd need another resistor in series with those leads.

  22. ... or maybe swap the capacitor and R1 in the last diagram and put a 1K between them. Then I'll get a 16Hz lowpass filter on any thermistor noise.

  23. If noise is injected at VREF it will be attenuated more but the time constant of the filter will always be equal to the parallel resistance of the three resistors times the capacitor.

    Yes your maths is right. my guess was wrong.

    Yes a series resistor before the capacitor would allow a bigger R in the RC but don't make it too big or the leakage current of the ADC input will cause a DC offset. Also the sample and hold input of the ADC might need a low impedance to settle fast enough, check the datasheet.

  24. I'm using a atmega32U4 (>10K impedance recommended) in a set of noise-prone electronics, http://reprap.org/wiki/Teensy_Breadboard , so I'm trying to figure out what's happening with the noise.

    My thoughts are these:

    If the capacitor is on the ADC side of the series resistor, the ADC should charge and settle quite fast.

    Any DC leakage current from the ADC can only flow towards the voltage divider junction and would actually be minimized by larger R. If the ADC is at Vref, the series resistor would be parallel with R1 and make the top leg resistance 1/(1/R1 + 1/Rseries).

    If Vref is the Vref of the ADCs, it seems like noise injected there would be common to both the input and comparator, so it should be ignored.

    It still looks like a superposition of three filters to me. Depending on the location of the noise injection, its path past the capacitor will travel through different resistances. Zero for an injection at the voltage divider junction or in the hot leg of the thermistor.

    I'm going to have to buy myself an oscilloscope and a signal generator and actually test these suppositions.

    Test-wise, I jammed a 10uf cap in parallel with the low leg of the voltage divider in my noisy system and it didn't seem to reduce the noise in my readings.

  25. I don't yet have an oscilloscope to do actual testing, but it seems like if I dropped R1, put an R4 in series with the high leg of the thermistor, with a circuit like:


    With values like: R4=68, R2=470, C1=10uf Ttherm=(10K@25, B=3600)

    You would get a lowpass RC filter on any lead noise noise injected at Vn of 1/(2pi R4 C1) = 236Hz.

    Current limiting of Vcc/(R2+R4+Rtherm(T<300C)) < 7ma @5V

    Voltage at Vdiv varying from 0.63 to 5, with the thermistor short- to open- circuited (ADC of 130 to 1023)

    If one is concerned about about self heating, why not break R2 into two segments and hook the ADC's Vref to the reduced voltage rather than the +5V Vcc?

    At a 1.1Vref, with r2=470, r4=30, I calculate 2mW at 300C, 1.7mw at 200C, 0.4C/count around 200C, and about a 1.1C/count around 20C.

    Is this the best circuit for this application or is it historically what RepRappers have been using?

    1. Adding your R4 reduces the resolution at the high temperature end, which is where we need it accurate.

      The time constant is not R4 C1 as I keep telling you. It is R2 in parallel with R4 for noise injected at Vn from a zero impedance source.

      Yes if VREF is on an external pin you can reduce it to reduce self heating. Reprap simply uses a 100K thermistor.

      The circuit works fine. Noise isn't normally a problem because the capacitor filters most of it and the firmware does over sampling and averaging.

    2. Around 200C, The resolution is coarser than a degree per count. Per a patched version of the above program:

      createTemperatureLookupNophead.py --r0=10000 --t0=25 --r1=671 --r2=1579 --num-temps=160 --beta=3500

      {63, 204},
      {64, 202},
      {65, 201},
      {66, 200},
      {67, 199},
      {68, 197},
      {69, 196},
      {70, 195},

      Since Hydraraptor uses a 1.5V vRef instead, you don't need to do the dividing with R1, and you can use R2=470 or whatever else instead and get around .25C/step resolution:

      createTemperatureLookupNophead.py --r0=10000 --t0=25 --r1=0 --r2=470 --num-temps=320 --beta=3500

      {214, 203},
      {217, 201},
      {220, 200},
      {223, 199},
      {226, 198},
      {229, 197},
      {232, 196},

      I know this circuit works for lots of people, but the vDiv resolution sacrifice costs about 3/4 of the resolution in the region of interest. With 5V and a 68ohm R4 as a current limiter at high temps, you don't need to dump 3/4 of the resolution.

      There wasn't firmware that worked for my admittedly cheap and noisy hardware, so I had to adapt Teacup and then write the oversampling and averaging routine myself. Since the bed and extruder temperature signal varies with a time constant on the order of tens of seconds, it seems the firmwares would not have needed oversampling and averaging if the input conditioning circuits were robust against the noise some people have experienced.

      (--num-temps= patch at http://reprap.org/wiki/Talk:Thermistor#num_temps_patch )

    3. I don't have access to the internal 1.5v Vref, so I need the second resistor to prevent room temperature being off the scale.

      I don't know why you are current limiting at high temps. The maximum dissipation in the thermistor is when its resistance equals the impedance of the divider, which is normally in the mid range, it is a power rather than current limitation.

      To get more resolution at the top end then the parallel resistance of R1 and R2 needs to be lower, or if that creates too much self heating the thermistor needs to be a higher resistance.

      Most people use 100K with 5V systems. I put a 100K on my electronics but kept the resistors the same. It gives much smoother readings at 240C but is very noisy at room temp.

      Why is you "cheap hardware" noisy?

    4. I didn't want an R4 so much for current limiting as for some RC filtering on noise injected at Vn. Without any resistance between the noise source and the capacitor, the noise is injected at the output of the RC filter. Looking at it as summing parallel impedances leading into the capacitor, any Vn noise injected behind an R4 would make the total parallel resistance 1/(1/R1+1/R2+1/Rtherm+1/R4), and as R4 approaches zero, Rsum would approach R4, and the time constant would go to zero. However it is moot because rather than noise from my long leads, I think the noise I was attempting to filter actually comes from the +/-1LSB of resolution of the ADC at low temps due to my poor choice of Rmagic.

      I thought my cheap breadboard hardware was likely noisy because it has lots of extra parallel traces fed by a spaghetti-nest of wires, but I now think it was likely due to large ADC noise at room temp due to a poor selection of a magic temperature.

      I had been picking the magic value to be in the middle of the *operating* temperatures. For my Epcos B57540G1104 (100K, beta=4092) if I'm looking to match 624 ohms at 200C, then with the divider calcs from the above I get R1=892, R2=2081, and I get .75C/count at 200C, but about 7C/count at 35C. Some ought-to-have-been-expected 1-bit ADC noise of ~7C at the low end was likely my problem and wouldn't have been filtered out by an R4-like thing anyway.

      I'm eliminating R1 and going with 2.5V mid-scale at 130C using R2=2700 with 0.37C/count at 200C and 1.0 C/count at 20C. I expect 1/7th the ADC noise at room temp, and 1/2 the ADC noise at working temps.

      If the max 2.31mW or 6C of self heating at 130C is a problem, I'll look into adding an R5=6800 between Vcc and Vref and hooking Vref to the AREF ADC input.

      Thanks for your article, the program, and the comments. I think I understand the effects of interactions between Rmagic, the 100K vs 10K thermistors, the biasing vs non-biasing voltage division circuits, and their effects on ADC resolution much better. Thanks again.

  26. The time constant is only zero if you inject noise from a source with zero impedance, which unless you make a direct connection with a perfect signal generator, is impossible. The capacitor will always provide some attenuation for real world noise sources.

    1. I observed actual noise at the Vn junction, which I thought I would have to model (in SPICE, or on paper) as an injected noise with zero impedance. When finally got around to using an an oscilloscope, I noticed that I had 50kHz noise at Vref as well, ultimately due to a noisy ATX power supply. The noise path to V through R2 gets filtered by the R2*C1, but the noise path into Vref wasn't significantly filtered (Direct connection to an ATX V_sb with 0.1uF), so the internal ADC sensor reported noisy readings with respect to Vref.

      How would one model real world, observed noise at a terminal other than as a low impedance injection? Swap capacitors, measure the attenuation, calculate the impedance, then model it as a perfect signal generator followed by the calculated resistance?

    2. Not sure. If it is inductively coupled noise then presumably the source impedance is the impedance of the inductance of the loop of wire plus the resistance of the thermistor.

    3. Looking for a hand here. I am using a 500K thermistor (for very good reason) & it seems that entering 500K breaks the code. I was able to make something that works with the thermistor calculator at http://calculator.josefprusa.cz/#TempLookup (missing atm) but I wanted to make something far more accurate as I will be going to 400C with this thermistor. My variables are:

      r0 = 500000
      t0 = 25
      rp = 4700

      t1 = 200; r1 = resistance(3.605)
      t2 = 400; r2 = resistance(0.2258)

      Can you help me out with this? Thanks in advance!

    4. I assume this is referring to the code in my later article "More accurate thermistor tables".

      The code breaks if three points are given that can't be right for a real thermistor as the logarithmic equation used can not fit three arbitrary points.

      The values you give seem very odd for a 500K thermistor and you will never get it accurate if you are trying to measure 0.22R with a 4K7 pullup.

  27. Nophead wrote an update: http://hydraraptor.blogspot.com/2012/11/more-accurate-thermistor-tables.html