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!

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.

' 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

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

Site contents © 1997-2014 by AWC, 310 Ivy Glen, League City, TX 77573 (281) 334-4341