Calculating NTC temperature

MrChips

Joined Oct 2, 2009
35,093
...by doing a linear least-squares fit to a straight line.

Most spreadsheets can do this for you as well as programs like MATLAB and Mathematica.

The formulas to find the coefficients are derived in any textbook on linear regression analysis.
 

WBahn

Joined Mar 31, 2012
33,110
Next I will show you how to do it without an ADC.
I would use a TDC - time to digital converter. Multiple ways of doing it. The simplest is to use two comparators to measure the rise time between two points fractional voltages. Since this time is proportional to the RC time constant, you can choose a combination of C and your comparator thresholds to get an easily calibrated and conveniently scaled direct reading of the resistance (or of the capacitance, if that is what you were interested in).

Here, though, we want something that is proportional to the inverse of the resistance -- in other words, something that will take more time as the resistance goes down. There are a number of ways of doing this. For instance, using a current mirror to steer current away from the capacitor. The trick is to find something that has the same functional relationship at its core as the one you are trying to capture.
 

MrChips

Joined Oct 2, 2009
35,093
One of the problems to avoid with thermistors is the self-heating effect. If the current is high enough (and this happens as the resistance goes lower at higher temperatures) the thermistor itself will generate enough heat resulting in elevated readings.

Here is a simple method to reduce self-heating as well as to take a reading without the need for an ADC.


Simply connect the RC circuit shown above to an I/O port of a microcontroller. If the MCU uses CMOS technology (which means almost all mcu today) the current drain into the input port will be very low.

How does this work?

Start with the I/O pin defined as an output pin and send a zero out.
Disable any internal pull-up to that pin.
Switch the I/O pin to input and monitor the digital input as the capacitor charges through the thermistor. Eventually, the input will switch from 0 to 1. Count the number of clock cycles it takes for the input to change from 0 to 1.

The problem with this method is the number acquired decreases as the temperature increases, i.e. the RC time-constant decreases with increasing temperature. This results in higher resolution at low temperatures and decreasing resolution as the temperature increases.

But there is a simple solution to this problem. Rather than measure RC time-constant, why not measure frequency? How do we get a frequency out of this simple RC circuit?

Coming up next...
 

WBahn

Joined Mar 31, 2012
33,110
See if you can guess what is coming next...
You make an ASM that uses the RC to establish the period, which means that the frequency is inversely proportional to the resistance and, hence, directly proportional to the temperature. You then count how many cycles you see on the pin in a fixed amount of time. Choose the 'fixed amount of time' wisely, and you make the computation on the cycle count consist of a shift and/or adding a constant.
 

THE_RB

Joined Feb 11, 2008
5,438
Now suppose you didn't want to use floating point arithmetic or you wanted to write the code in assembler. How would you calculate

temp = 0.0953 * ADC_COUNTS - 23

You can scale 0.0953 by multiplying with 65536 = 6180

The equation becomes:

temp = 6180 * (ADC_COUNTS)/ 65536 - 23

Thus multiply ADC_COUNTS by 6180 using 32-bit precision and select the upper 16 bits of the result, then subtract 23.
...
MrChips, I understand you have used a binary division in your code as it would execute faster and possibly be smaller in code, however C compilers will usually link in both mult and divide libraries once the user starts doing 32bit multiplications.

Since your excellent posts are accessible to beginners etc, I think for the purpose of clarity it would have been better to process this;
temp = 0.0953 * ADC_COUNTS - 23

to this;
temp = ((ADC_COUNTS * 953) / 10000) - 23

as that more clearly shows the decimal conversion of the multiplier 0.0953.

Also we see lot so examples on the forum where people don't use appropriate sized variables for mutliplying up, so it's probably worthwhile posting a code segment they can use which will be safe from error by using a deliberate 32bit variable for the calc, and allowing a more useful 8bit or 16bit variable for the result to be easily displayed;

unsigned long math32; // calc requires a 32bit variable
math32 = ADC_COUNTS; // transfer data to 32bit before calcs
math32 = ((math32 * 953) / 10000) - 23;
temp = math32; // transfer result back to final variable for display etc

I hope you don't think I am being overly critical, I was just trying to add something to the great posts you have already provided.
 

MrChips

Joined Oct 2, 2009
35,093
BTW, there is a typo going from post #36 to #39, my error.

The original equation on post #36 is:

Temp = 0.0943 * ADC_COUNTS - 23

The #39 post should read:
Now suppose you didn't want to use floating point arithmetic or you wanted to write the code in assembler. How would you calculate

temp = 0.0943 * ADC_COUNTS - 23

You can scale 0.0943 by multiplying with 65536 = 6180

The equation becomes:

temp = 6180 * (ADC_COUNTS)/ 65536 - 23

Thus multiply ADC_COUNTS by 6180 using 32-bit precision and select the upper 16 bits of the result, then subtract 23.

To adjust the equation in order to calibrate for gain and offset shifts, all you have to trim are the two values 6180 and 23.
Sorry about any confusion.
 

MrChips

Joined Oct 2, 2009
35,093
BTW, there's still a lot of mileage left in this thread.
I will show how to read the thermistor without using an ADC.

Then I will go back and show how we can improve the linearity of the thermistor by adding one resistor.

Please come back later...
 

MrChips

Joined Oct 2, 2009
35,093
The RC circuit shown in post #44 will produce a time period. We would like to convert this into a frequency.

You write a tight loop (preferably in assembler) that outputs LOW, releases the pin and wait for the input pin to rise to HIGH. Do this repeatedly, counting how many times this occurs.

Prior to this, you set a hardware timer running for a fixed length of time. In the loop described above, you monitor the overflow flag of the timer. When the timer overflows you exit the loop. You now have the number of RC cycles that occured during the fixed time-base.

One simple way to calibrate this is to conduct an experiment over the desired temperature range using an accurate thermometer. Collect about 10 data points of counts vs temperature. Then do the curve fit off line and find the appropriate coefficients to a straight line or polynomial.

Here is actual asm code on a Atmel AVR ATtiny2313:

Rich (BB code):
;define registers
A         EQU       16
B         EQU       17
C         EQU       18

;index registers
X         EQU       26
XHI       EQU       27

*********************************************************
*         Thermistor Interface
*********************************************************

;thermistor is on PD6
THERM     EQU       6

;get frequency measurement
;for Thermistor sensor
getTherm  LDI       XHI $FF
          LDI       X   $FF
;set for 1-sec timebase
;init value = 34286 = $85EE
          CBR       flags TOV1mask
          LDI       A $85
          OUT       TCNT1H A
          LDI       A $EE
          OUT       TCNT1L A
          LDI       A 4       ;start timer1
          OUT       TCCR1B,A
;charge THERM capacitor
getF1     SBRC      flags TOV1
          BRA       getF0               ;exit
          ADIW      X 1
          SBI       PORTD THERM
          SBI       DDRD  THERM
          LDI       C 5
          CALL      delayC
          CBI       DDRD  THERM         ;disconnect
          CBI       PORTD THERM         ;remove weak P/U
getF2     SBIS      PIND  THERM
          BRA       getF1
          SBRS      flags TOV1
          BRA       getF2
getF0     SBI       DDRD  THERM         ;set output LO
          CBI       PORTD THERM
          RET
When the subroutine returns the 16-bit X-register contains the acquired counts.
 

MrChips

Joined Oct 2, 2009
35,093
CORRECTION

The thermistor and capacitor are not connected as shown.
The thermistor and capacitor are both connected in parallel between the I/O pin (PD6) and GND.

Hence, the operation of the circuit and the code is as follows:
PD6 is enabled as OUTPUT and a HIGH is send out, charging C to Vcc.
PD6 is then configured as INPUT and C will discharge through the thermistor.
When the PIND bit-6 falls to 0, the sequence is repeated.

At the end of the measurement cycle, PD6 output is enabled with 0 output. Hence no current flows through the thermistor while it is idle.

Sorry, it was 8 years ago when I did this project.

Edit: Here is the revised circuit to match the code:
 
Last edited:

Thread Starter

nerdegutta

Joined Dec 15, 2009
2,689
With the thermistor and resistor arranged like this:



I have this reading:



T1:
Rich (BB code):
temperature1 = log(((10240000/adc_raw)-9930));
temperature1 = 1 / (0.001171329051429 + (0.000224841652249 + (0.000000142860438 * temperature1 * temperature1 )) * temperature1 );
temperature1 = temperature1 - 273.15;
T2:
Rich (BB code):
temperature2 = (6180 * adc_raw) / 65536 - 23;
 

MrChips

Joined Oct 2, 2009
35,093
Very nice.
I have a few questions and comments.

What are the two readings 27.2C and 26.9C on your commercial thermometer?

How are you calculating and displaying to 1 decimal place on your LCD?
Are you using fprints( ) for example?
I presume you are using floating point arithmetic in C?

The equation I gave you is intended for integer arithmetic. The value 23 is already truncated. I will give you the correct values if you are doing floating point math.

Finally, I still have to show how you can improve on the linearity by adding a resistor in parallel with the thermistor (may not today, too busy).
 

MrChips

Joined Oct 2, 2009
35,093
If you are doing floating point math, the equation is

temp = 0.0942839 * ADC_COUNTS - 22.9818

but

temp = 0.0943 * ADC_COUNTS - 23

is still good for 0.1C accuracy.

If you are doing integer arithmetic to 0.1, I scale everything by 10

temp = (61800 * ADC_COUNTS)/65536 - 230

and insert the decimal place in the appropriate place (easy to do in assembler - when not using a PIC).
 
Last edited:

Thread Starter

nerdegutta

Joined Dec 15, 2009
2,689
What are the two readings 27.2C and 26.9C on your commercial thermometer?
It's an indoor/outdoor thermometer. The upper digits are temp inside, and the lower digits are supposed to be outside. The sensor is at the end of a 1.6m cable. Which is wrapped around on my desk.
How are you calculating and displaying to 1 decimal place on your LCD?
Are you using fprints( ) for example?
Rich (BB code):
sprintf(out_temp1, "%.1f", temperature1);
I presume you are using floating point arithmetic in C?

The equation I gave you is intended for integer arithmetic. The value 23 is already truncated. I will give you the correct values if you are doing floating point math.
The declaration:
Rich (BB code):
unsigned char adc_value[5];			//max value will be 1023=4 char and 1 place for the /0 total 5 bytes
unsigned char lcd_message1[20];
unsigned char lcd_message2[20];
unsigned char out_temp1[10], out_temp2[10];
unsigned char i;
float adc_raw;
float temperature1, temperature2;
I've heard that I use a lot of memory, but for this little project I'm using PIC18F25K22, which also is a bit overkill, so I won't focus on that now.


Finally, I still have to show how you can improve on the linearity by adding a resistor in parallel with the thermistor (may not today, too busy).
Looking forward to it. :)
 

MrChips

Joined Oct 2, 2009
35,093
I looked at the data again. Adding a resistor in parallel will reduce your sensitivity.

If you are using floating point arithmetic anyway, then you might as well go back to square one.

  1. Acquire the ADC counts
  2. Calculate the resistance R of the thermistor
  3. Take the log(R)
  4. fit to T vs log(R)
My equations are:


Assuming that the ADC Vref is the same as Vdd or Vcc, otherwise adjust as required.

R2 = 10000 where R2 is the load resistance
R = 1023*R2/ADC_COUNTS - 1
x = log(R)

where log( ) means the natural log or ln( )

a3 = 2.402675132814
a2 = -30.837075314796
a1 = 148.797883377607
a0 = -285.600560405728

T = a3*x^3 + a2*x^2 + a1*x + a0
 
Last edited:

MrChips

Joined Oct 2, 2009
35,093
This seems to be a better fit for the full range -40 to 40C.

Change R2 to 33000Ω and skip the log( ) function

x = ADC_COUNTS

T = a3*x^3 + a2*x^2 + a1*x + a0

a3 = 0.00000018524250
a2 = -0.00025641150597
a1 = 0.19058077255347
a0 = -55.86182880225911

BTW, a0 represents a temperature offset. Hence you can easily adjust your readings up or down by changing a0.

Here is the fit:

 
Last edited:
Top