Measuring temperature with NTC-The Steinhart-Hart Formula

I know, this subject can be found all over the web, but the calculations sometimes are   presented a bit cryptic as if the author  wanted to put as many nested calculations on one line, so I just wanted to write something that explains it step by step, especially where the actual calculations are concerned. Lady Ada does a good job explaining the use of the thermistor, but I added some more explicit calculations.
The Arduino has several ADC ports that we can use to read a voltage, or rather an ‘ADC value’. If the Analog port is connected to Vcc, the max value one  reads is 1023 and of course when connected to ground it is 0.
spanningsdelerNow if we make a voltage divider which is typically two resistors in series between Vcc and Ground and the analogport in the middle, the reading will depend on the ratio of the two resistors: If they are equal, the reading will be 512, halfway to 1023. If one of the resistors, say the bottom one is an NTC, then the readings on the analogport will vary with the temperature:
If the temperature goes down, the value of the resistor increases and so will the reading on the analog port.

Suppose we have a 10k Seriesresistor and an NTC that for now we call ‘R’.
Then the voltage that can be measured in the middle is

Vo=R/(R+10K) * Vcc

The analogPort readings however don’t give a voltage but an ADC value that can easily be calculated
ADC value= Vo*1023/Vcc  // if for instance the Vo=4Volt the ADC = 818
or
ADC value= 1023 *(Vo/Vcc)

If we now combining the two formulas or as it is called ‘substitute’ Vo in the formula for ADC we get the following:
ADC value= (R/(R+10K))*Vcc*1023/Vcc
As we multiply by Vcc but also divide by Vcc we can take that out of the equation and end up with
ADC value= (R/(R+10k))*1023
ADC value=  1023*R/(10+R)
if we want to get the value of R out of that equation, that becomes
R=10k/(1023/ADC-1)
If that goes a bit too fast, here is the equation worked out. I prefer pictures over the ‘in line’ formulas as some people have problems understanding the PEMDAS / BODMAS order of operation.

CodeCogsEqn(1)

This becomes
CodeCogsEqn(7)
subtraction of R
CodeCogsEqn(2)Work  -R into the multiplication
CodeCogsEqn(3)As we are interested in R, divide both sides by the enclosed fracture
CodeCogsEqn(4)
The ’10’ stood for ’10k’CodeCogsEqn(5)
and as we don’t always use a 10k we just make it more general:
CodeCogsEqn(6)So, as long as we know the value of the series resistor, we can calculate the value of the NTC from the measured ADC value. Now remember, this is valid voor a pull-up configuration. If it is a pull down configuration, the calculation of the ADC to resistor value is  the inverse.

Rntc = Rseries*(1023/ADC-1);// for pull down
Rntc = Rseries/(1023/ADC – 1)); // for pull-up configuration

So what would that look like in a  program?

//Measure NTC value
byte NTCPin = A0;
const int SERIESRESISTOR = 10000;
void setup()
{
	Serial.begin(9600);
}
void loop()
{
	float ADCvalue;
	float Resistance;
	ADCvalue = analogRead(NTCPin);
	Serial.print("Analog value ");
	Serial.print(ADCvalue);
	Serial.print(" = ");
//convert value to resistance
	Resistance = (1023 / ADCvalue) - 1;
	Resistance = SERIESRESISTOR / Resistance;
	Serial.print(Resistance);
	Serial.println(" Ohm");
	delay(1000);
}
//end program

Knowing the resistance of the NTC is nice but it doesn’t tell us much about the temperature… or does it?
Well many NTC’s have a nominal value that is measured at 25 degrees Centigrade, so if you have a 10k NTC and you measure it to be 10k, that means it is 25 degrees at that moment. That doesn’t help you much when the measurement is different.
You could keep a table in which every resistance value stands for a temperature. Those tables are very accurate but require a lot of work and memory space.

However, there is a formula, the Steinhart-Hart equation, that does a good approximation of converting resistance values of an NTC to temperature. Its not as exact as the thermistor table ( after all it is an approximation) but its fairly accurate.

The Steinhart-Hart equation looks like this:
CodeCogsEqn(8)That is a rather complex equation that requires several parameters (A, B, C) that we normally do not have for the run of the mill NTC.There are two things we can do. We can take 3 readings with a calibrated temperature and then work out the A, B and C parameters.

CodeCogsEqn

but fortunately there is a simplification of this formula, called the B-parameter Equation. That one looks as follows:
CodeCogsEqn(9) To is the nominal temperature, 25 °C in Kelvin (= 298.15 K). B is the coefficient of the thermistor (3950 is a common value). Ro is the nominal resistance of the NTC  (thus at 25 degrees). Let’s say we have a 10Kohm NTC. We only need to plug in R (the resistance measured) to get T (temperature in Kelvin) which we then convert to °C.

The program looks as follows:

//---------------
byte NTCPin = A0;
#define SERIESRESISTOR 10000
#define NOMINAL_RESISTANCE 10000
#define NOMINAL_TEMPERATURE 25
#define BCOEFFICIENT 3950

void setup()
{
Serial.begin(9600);
}
void loop()
{
float ADCvalue;
float Resistance;
ADCvalue = analogRead(NTCPin);
Serial.print("Analog value ");
Serial.print(ADCvalue);
Serial.print(" = ");
//convert value to resistance
Resistance = (1023 / ADCvalue) - 1;
Resistance = SERIESRESISTOR / Resistance;
Serial.print(Resistance);
Serial.println(" Ohm");

float steinhart;
steinhart = Resistance / NOMINAL_RESISTANCE; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (NOMINAL_TEMPERATURE + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15; // convert to C

Serial.print("Temperature ");
Serial.print(steinhart);
Serial.println(" oC");
delay(1000);
}
//-------------

This ofcourse is not an ideal program. It is always good to take a few samples and to average them.
The following function can do that for you:

float sample(byte z)
/* This function will read the Pin 'z' 5 times and take an average.
 */
{
	byte i;
	float sval = 0;
	for (i = 0; i < 5; i++)
	{
	sval = sval + analogRead(z);// sensor on analog pin 'z'
	}
	sval = sval / 5.0;    // average
	return sval;
}

Feeding the series resistor and NTC from the 5 Volt supply from the Arduino is possible. The Arduino power line does have glitches though. For accurate measurements it is better to use the 3.3Volt line as analog reference and feed the resistor from there.
for that add the following code in the setup
// connect AREF to 3.3V and use that as VCC for the resistor and NTC!
analogReference(EXTERNAL);

24 thoughts on “Measuring temperature with NTC-The Steinhart-Hart Formula”

  1. Great explanation. After severall similar articles only with one a could put everithing to work. Great !

    There’s a small error on the code: Serial.print(“TemperatureSerial.print(steinhart);
    Must be:Serial.print(“Temperature “);
    Serial.print(steinhart);

    1. apologies for my late reply. I tried to find that error u mentioned but i dont see it. Could it be a browser glitch?

  2. This is a very helpful article, but I believe there are some potential divide by 0 situations in your code when calculating resistance. The odds are small, but if the raw ADC reading just happens to be the maximum ADC (1023 in your example) or is 0, a division by zero can occur.

    Resistance = (1023 / ADCvalue) – 1; // Divide by 0 if ADCValue is 0
    Resistance = SERIESRESISTOR / Resistance; // Divide by 0 if ADCValue is 1023

    1. Thanks Ken, I’ ll have a deeper look at it. Though I didnt invent the Steinhart formula, I will see if i implemented it correct

    1. What you say is right, but it looks OK in my browser, so there is nothing i can correct 😦
      I will tryan extra line in between

  3. Hi,

    Thanks for the article.
    Just 2 remarks:
    – when you put the reference temperature, you shall use °K instead of °C so NOMINAL_TEMPERATURE become 298.15.
    – sometime it’s prefered to write 10000.0 instead of 10000 when the define is used with floats.

    Dams

  4. Great and very helpful writeup, thanks for putting this together! I just got a sparkfun redboard and the Velleman VMA320 sensor and this seems to work well and is a great starting point for my project. Appreciate it!

  5. I’m glad to find this article – I’m making an Arduino BBQ smoker controller and want to get the food/pit probes accurate in the 150 – 250 deg F range. The probes are 200k ohms at 25 deg C. But since the midpoint of my range of interest is 200 deg F, where the probe resistance is around 25k ohms, would it be best to use a series resistor of 25k (actually 22k since that is common)? And use S/H or BETA values calculated to just the characteristics of the probes at that range? Thanks

    1. Thanks Dennis, the Steinhart formula is an approximation in the end you would need to validate your results anyhow, but having a resistor of 25k (or 22k) should give you the broadest range around your area of interest, provided your probe is somewhat linear.
      Now your probe is that in fact an NTC?… just checking as 250 Fahrenheit (121 Celsius) seems a tad hot for an ntc 🙂
      Anyway, it is best to calculate or measure what resistance your probe is at each end of your ‘window’ and then try and get that window evenly spaced out over your range.
      With a 25k you will have 200 F in the middle, but you just want to make sure that 150 and 250 are somewhat equidistant

  6. Great article, can you help me find detail on the “Work -R into the multiplication” step of the algebra.
    im really strugling with that step.
    Cant find a tutorial on that part to work out where you went with that.

    1. That is in fact quite simple and basic but a bit hard to type the equations on my phone so i have to do it a bit later (several hrs) when i am behind my desk again

    2. seems I cant post an image here
      try https://arduinodiy.files.wordpress.com/2022/01/explain-3.png

      Or in writing (using a lot of brackets to avoid any confusion about order of operations, but the explanation/work out is this:
      ((1023*R)/ADC)-R=10
      (R*(1023/ADC))-R=10
      (R*(1023/ADC))-(1*R)=10
      (R*(1023/ADC)) -(R*1)=10
      (R*(1023/ADC)) +(R*-1)=10
      R*((1023/ADC)-1)=10
      ((1023/ADC)-1)*R=10
      It is really very basic algebra

      1. Hey there. Thanks very much to take the time to show the expanded course. Much apreciated!

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.