What's the easiest way to measure temperature with the Stamp? Easy -- use a thermistor, right? Thermistors are cheap two-terminal devices that change resistance in relation to temperature.

The Stamp can measure resistance using the RCTime command, therefore it can also measure temperature. Well, sort of. The problem is, RCTime doesn't return resistance, it returns "units". And even if you know the resistance, it isn't trivial to convert that into a temperature -- especially with the Stamp's integer-only math.

That's where a PAK-I can come in very handy. The ability to do real floating point math is a big help when you want to do engineering conversions.

The hardware for this project is very simple. Just connect a PAK-I like it shows in the manual (use pin 0 for data and pin 1 for clock). Then connect a Radio Shack 271-110A thermistor between pin 7 and ground. Put a .1uF capacitor between pin 7 and +5V. Then you are all set!

The Solution

Reading a raw value from the thermistor is very easy. You can read more about the RCTime command in the Stamp manual, but the idea is:

HIGH Therm
Pause 1
RCTIME Therm,1,raw

The Stamp manual says that the raw count will equal to 600 x R x C where R is in kohms and C is in microfarads. Since C is fixed at .1uF in this case, a little algebra reveals that R = raw/60. I wanted to work in ohms, so I'll multiply this result by 1000.

Next, you need to relate the R value to temperature. The back of the thermistor's cardboard package has a table of temperatures from -60C to 110C. I decided to limit my interest to 0C to 40C. It doesn't usually get to either extreme in my lab!

You can use any curve fit program that you want. However, since this is a Stamp project, I used Parallax's GAUSFIT program (available on their Web site -- it is part of one of the Stamp I application notes). I prepared a file that had the resistance in ohms related to the temperature in centigrade. Be careful not to add any blank lines at the end of the file:

22050 5
14680 15
12090 20
10000 25
8313 30
6941 35
5928 40

This file, named GF.DAT by default, provides input to GAUSFIT. If you run it with no arguments, it creates a 3rd order polynomial to fit the curve. In other words, the program will generate 4 constants (C0 to C3). The temperature will then be:

temp = C3 x R**3 + C2 x R**2 + C1 x R + C0

(where **3 is the 3rd power, **2 is the 2nd power).

This works pretty well, but the maximum error (reported by the program) is over 0.5 at 40 degrees (1.35%) -- not very accurate. Adding a number to the command line lets you change the order of the polynomial. Setting the order to 4 give you an extra term in the polynomial and reduces the maximum error to around 0.25 at 35 degrees (0.7%) -- a good deal better. The thermistor is rated at 1%, but the capacitor is probably 10% or more depending on the kind you use.

The PAK-II can perform exponentiation directly, but the PAK-I can't. You could compute x**4 as x*x*x*x, but it is simpler to rewrite the polynomial like this:

temp = C0+R x (C1+R x (C2+R x (C3+R x C4)))

Note: I could have done a little work and reformulated the GF.DAT file so that it correlated raw counts to temperatures. However, I decided to do it in 2 steps so there would be more code to study. You can try working out a new GF.DAT file on your own if you like.

According to GAUSFIT, the numbers you need are:

C0 = 84.78281

C1= -1.099857E-02

C2 = 7.037247E-07

C3 = -2.325349E-11

C4 = 2.954709E-16

The PAK only has 2 registers, X and Y. It also has two temporary holding registers. The best way to approach the problem is like you were doing it by hand or using an RPN calculator. So the sequence will be:

Load R
Load C4
*
Load C3
+
Load R
*
Load C2
+
Load R
*
Load C1
+
Load R
*
Load C0
+

You can load integers directly into the PAK, but it is more efficient to load them as floating point numbers using a 32-bit hex format. The PAK includes a program FConvert that runs on your PC that converts numbers into the PAK's hex format.

The numbers we need for our program are:

60 - $84700000
1000 - $887A0000
C4 - $4B2A53CF
C3 - $5BCC8A34
C2 - $6A3CE798
C1 - $78B43359
C0 - $852990CC

You can follow the program's flow below. The program reads the raw value 3 times and averages it. Then it converts to resistance. It stores the resistance value into the PAK's temporary register and uses it to perform the polynomial expansion.

The program uses the Stamp PAK library which is just a handy way to make the appropriate SHIFTIN and SHIFTOUT calls. Here are a few key calls used:

FLoadX - Load a hex value (in fpxhigh, fpxlow) into X
FLoadY - Load a hex value into Y
FLoadInt - Load an integer into X
FAdd, FMult - X=X+Y or X=X*M
FXtoY - Y = X
FSto0, FRcl0 - Store or recall X
FDump - Dump the floating point number using DEBUG

Of course, if you wanted to actually do something with the temperature, you would probably convert it to an integer (FInt). You might not want to read degrees. For example, suppose you wanted to display the result on a serial LCD. You could multiply the result by 10 in the PAK and then convert it to an integer (using FInt). Then you could display the temperature like this:

Serout LCD,baudrate,[dec fpxlow/10, ".", fpxlow//10]

Dividing by 10 (integer division) results in the whole number part; the // operator gives the remainder from that division which gives you the part to the right of the decimal point. You could also use the FDigit command (like FDump does) to pick numbers apart.

You can read the PAK manual online, and also read technical notes about the PAK in our new document library.

Source Code

' Thermistor temp meter with EU conversion
therm con 7
raw var word
temp var word
i var byte ' loop count

' Stuff for PAK-I
' Change these to suit your setup
datap con 0 ' Data pin (I/O)
datapin var in0
clk con 1 ' Clk pin (output)



' Constants for options
FSaturate con $80
FRound con $40

output clk
output datap


fpstatus var byte ' FPSTATUS
fpx var word ' Integer used by some routines
fpdigit var byte ' Digit returned from DIGIT
fpxlow var word ' The X register low & high
fpxhigh var word
fpb var byte ' Temporary byte
' The X register in bytes
fpxb0 var fpxlow.lowbyte
fpxb1 var fpxlow.highbyte
fpxb2 var fpxhigh.lowbyte
fpxb3 var fpxhigh.highbyte

gosub freset ' always reset!

' Start of main code
fpx=0 ' temporary accumulator for averaging raw
for i=1 to 3
HIGH Therm
Pause 1
RCTIME Therm,1,raw
fpx=fpx+raw
next
raw=fpx/3 ' get average raw value
debug ?raw
' compute R from formula raw = 600 x R x C (R in K, C in uF)
' or R = raw/60 (assuming C=.1uF)
' load 60 into Y
fpxhigh=$8470
fpxlow=$0000
gosub FLoadY
fpx=raw
gosub FLoadInt
gosub FDiv
' * 1000 to conver from Kohm to ohm
fpxhigh=$887A
fpxlow=$0000
gosub FLoadY
gosub FMult
fpx=5
Debug "R="
gosub FDump
Debug CR
' Now compute C4xR**4 + C3xR**3 + C2xR**2 + C1xR + C0
' PAK-II can do **n but PAK-I can't directly!
' Rewrite as:
' C0+R(C1+R(C2+R(C3+RC4)))
gosub FSto0 ' Store resistance
fpxhigh=$4B2A ' C4
fpxlow=$53CF
gosub FLoadY
gosub FMult ' X=C4*R
fpxhigh=$5BCC
fpxlow=$8A34
Gosub FLoadY
gosub FAdd ' X= C3+C4*R
gosub FXtoY
gosub FRcl0
gosub FMult ' X= R(C3+C4*R)
fpxhigh=$6A3C
fpxlow=$E798
gosub FLoadY
gosub FAdd ' X=C2+R(C3+C4*R)
gosub FXtoY
gosub FRcl0
gosub FMult ' X=R(C2+R(C3+C4*R))
fpxhigh=$78B4
fpxlow=$3359
gosub FLoadY
gosub FAdd ' X = C1+R(C2+R(C3+C4*R))
gosub FXtoY
gosub FRcl0
gosub FMult ' X = R*(C1+R(C2+R(C3+C4*R)))
fpxhigh=$8529
fpxlow=$90CC
gosub FLoadY
gosub FAdd ' X=C0 + all that other stuff
fpx=5
gosub FDump


end



' Start of PAK code

' Note: Not all of this is used in this program

' You could remove unused routines to save space

' Reset the Pak1
FReset:
LOW DATAP
LOW CLK
HIGH CLK
HIGH DATAP
LOW CLK
return


' Wait for +,-,*,/,INT,FLOAT, & DIGIT
Fwaitdata:
input DATAP
if DATAPIN=1 then Fwaitdata
return


'Change sign
FChs:
fpb=10
FSendByte:
Shiftout datap,clk,MSBFIRST,[fpb]
return

'Absolute Value
FAbs:
fpb=17
goto FSendByte

' Store0
FSto0:
fpb=18
goto FSendByte

'Store1
FSto1:
fpb=$92
goto FSendByte

'Rcl0
FRcl0:
fpb=19
goto FSendByte

'Rcl1
FRcl1:
fpb=$93
goto FSendByte

' Load X with fpxhigh, fpxlow
FLoadX:
Shiftout datap,clk,MSBFIRST,[1,fpxb3,fpxb2,fpxb1,fpxb0]
return

' Load Y
FLoadY:
Shiftout datap,clk,MSBFIRST,[2,fpxb3,fpxb2,fpxb1,fpxb0]
return


' Load X with 0
FZeroX:
Shiftout datap,clk,MSBFIRST,[1,0,0,0,0]
return

' Load Y with 0
FZeroY:
Shiftout datap,clk,MSBFIRST,[2,0,0,0,0]
return

' Load an integer from FPX to X
FLoadInt:
Shiftout datap,clk,MSBFIRST,[1,0,0,fpx.highbyte,fpx.lowbyte]
' Convert to Int
Shiftout datap,clk,MSBFIRST,[7]
goto fpstat

' to int
FInt:
Shiftout datap,clk,MSBFIRST,[11]
gosub Fwaitdata
Shiftin datap,clk,MSBFIRST,[fpstatus]
if fpstatus<>0 then FInterr


' Read the X register
FreadX:
fpb=3
gosub FSendByte
ShiftIn datap,clk,MSBPRE,[fpxb3,fpxb2,fpxb1,fpxb0]
fpx = fpxlow
FInterr:
return


' Swap X and Y
FSwap:
fpb=4
goto FSendByte

' Load X with pi
FPI:
Shiftout datap,clk,MSBFIRST,[1,$80,$49,$F,$DB]
return

' Load X with e
Fe:
Shiftout datap,clk, MSBFIRST,[1,$80,$2D,$F8,$54]
return

' X=X*Y
FMult:
fpb=12
fpstats:
gosub FSendByte
fpstat:
gosub FWaitdata
Shiftin datap,clk,MSBPRE,[fpstatus]
return ' status

' X=X/Y
FDiv:
fpb=13
goto fpstats

' X=X+Y
FAdd:
fpb=15
goto fpstats

' X=X-Y
FSub:
fpb=14
goto fpstats

' Get Digit (fpx is digit #) return in fpdigit
FGetDigit:
Shiftout datap,clk,MSBFIRST,[5,fpx]
Fgetdigw:
gosub fwaitdata
ShiftIn datap,clk,MSBPRE,[fpdigit]
return


' Dump a number fpx is # of digits before decimal point
' Assumes 6 digits after decimal point
FDump:
fdj var byte
fdnz var bit
fdjj var byte
fdjj=fpx
fpx=0
fdnz=0
gosub FgetDigit
' Remove this line to print + and space
if fpdigit="+" or fpdigit=" " then Fdumppos
Debug fpdigit
Fdumppos
for fdj=1 to fdjj
fpx=fdjj+1-fdj
gosub FgetDigit
if fpdigit="0" and fdnz=0 then FdumpNext
fdnz=1
Debug fpdigit
Fdumpnext
next
Debug "."
for fpx=$81 to $86
gosub FgetDigit
Debug fpdigit
next
return

' Set options in fpx
' $80 = saturate
' $40 = round
FOption:
Shiftout datap,clk,MSBFIRST,[$10,fpx]
return

FXtoY:
fpb=$17
goto FSendByte

FYtoX:
fpb=$18
goto FSendByte

' Set I/O Direction (dir in fpx)
IODir:
Shiftout datap,clk,MSBFIRST,[$14,fpx]
return

' Write bits in FPX to I/O port
IOWrite:
Shiftout datap,clk,MSBFIRST,[$16,fpx]
return

' Read bits to FPX
IORead:
fpb=$15
gosub FSendByte
Shiftin datap,clk,MSBPRE,[fpx]
return

' Square Root, set tolerance below
fsqrt:
gosub fsto1 ' R1=target
' guess half the original #
fpxhigh=$8000
fpxlow=0
gosub floady
gosub fdiv
gosub fsto0 ' R0=guess
goto fsqrterr

freguess:
gosub frcl0 ' get guess
gosub fsquare ' square it
gosub fswap ' put it in Y
gosub frcl1 ' get target
gosub fswap ' x=guess squared; y=target
gosub fsub ' subtract
gosub freadx ' get x to fpxhigh,fpxlow
gosub frcl0 ' get guess
gosub fswap ' y=guess
fpx=2 ' x=2
gosub floadint
gosub fmult ' x=2*guess
gosub fswap ' y=2*guess
gosub floadx ' x=guess squared - target
gosub fdiv ' x=x/y
gosub fswap ' y=x/y term
gosub frcl0 ' x=guess
gosub fsub ' x=guess-term
gosub fsto0 ' new guess
fsqrterr:
gosub fsquare
gosub fswap
gosub frcl1
gosub fsub
gosub fabs
' Select your error tolerance
' more precise values may fail to converge or take a long time
' check for error<.01
fpxhigh=$7823
fpxlow=$D70A
' check for error <.001
'fpxhigh=$7503
'fpxlow=$126f
' check for error<.0001 - warming may not converge
'fpxhigh=$7151
'fpxlow=$B717
gosub floady
gosub fsub
fpx=0
gosub fgetdigit
if fpdigit="+" then freguess
' Found it!
gosub frcl0
return
End

' X=X**2 ; does not destroy Y, but destroys fpxlow/fpxhigh
Fsquare:
gosub fswap ' get y
gosub freadx ' save it
gosub fytox ' y->x
gosub fmult ' x=x*y
gosub floady ' restore old y
return

Back Home


This article is copyright 1999, 2000 by AWC. All Rights Reserved.


Site contents © 1997-2018 by AWC, Houston TX    (281) 334-4341