Arduino Snippets: Temperature Measurement

Temperature seems an obvious thing to measure, so a bit of rummaging disgorged a classic LM335 temperature sensor that produce an output voltage directly calibrated in Kelvin at 10 mV/K: room temperature runs 296 K = 2.96 V. Nothing could be easier than this:

LM335 Temperature Sensor

The downside: a 1 °C temperature change corresponds to only 10 mV, which is barely two LSB of the Arduino ADC. In round numbers, a 1 °F change = 1 LSB, which doesn’t leave much room for measurement noise. I average five successive readings, which may be excessive, but the result seems stable enough:

```const float AVREF = 4.94;                    // Arduino analog reference

#define TAVG 5

float Kelvin;

for (byte i = 1; i < TAVG; i++)

return Kelvin * (100.0 * AVREF) / (TAVG * 1024.0);
}
```

For better accuracy, you must measure VCC on the Arduino board and plug that into the `AVREF` constant, because the ADC reference voltage comes from the power supply. If you’re powering the Arduino from a USB port, then don’t bother worrying about analog conversion accuracy, because VCC depends on which PC you use, the USB cable length, what load current you draw from the regulator, and probably the phase of the moon.

The magic number `100.0` converts 10 mV/K to K.

The four character DL1414 LED display works well enough for the kind of temperatures you might find around a human being and, if you have an LED bargraph display, you may as well throw that into the mix, too.

LM335 Temperature Sensor – 19 C

The bargraph has RRYYGGYYRR LEDs, so I scaled the temperature at 5 °C/bar and put 0 °C on the bottom of the display, which means 15-19 and 20-24 °C occupy the green bars in the middle. Fingertip temperatures light up the two yellow bars and body heat gets you into the red, so it’s a reasonable display. Just to show it works, here’s a closer look (0 °C is on the right, but you can reverse that easily enough):

LM335 Temperature Sensor – 25 C

The Arduino source code:

```// LM335 Temperature sensor sensor
// Ed Nisley - KE4ANU - November 2012

//#include <stdio.h>
//#include <math.h>

//----------
// Pin assignments

const byte PIN_TEMPERATURE = A1;			// Temperature sensor - LM335 = 10 mV/K

const byte PIN_MOSI = 8;			// data to shift reg
const byte PIN_SCK  = 6;			// shift clock to shift reg
const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
const byte PIN_RCKC  = 12;			// latch clock for LED character display

const byte PIN_HEARTBEAT = 13;				// DO - Arduino LED

//----------
// Constants

const int UPDATEMS = 1000;					// update LEDs only this many ms apart

const float AVREF = 4.94;					// Arduino analog reference
const float KTOC = -273.2;					// Kelvin to Centigrade offset

const float BARSCALE = 5.0;					// degrees per bar increment

#define TCCRxB 0x02							// Timer prescaler

#define LED_SIZE		4				// chars per LED
#define LED_DISPLAYS	1				// number of displays
#define LED_CHARS		(LED_DISPLAYS * LED_SIZE)

union DL1414_ {
word ShiftWord;				// word overlay
struct {					// bitfield sent to the display
unsigned int NotWrite:1;
unsigned int Ctl3_7:5;			// unused bits
unsigned int Data:7;
unsigned int Data7:1;			// unused bit
} ShiftBits;
};

//----------
// Globals

int Temperature, BaseTemperature;

word LEDBits;

char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string

unsigned long MillisNow;
unsigned long MillisThen;

//-- Helper routine for printf()

int s_putc(char c, FILE *t) {
Serial.write(c);
}

//-- Send bits to LED bar driver register

void SetBarBits(word Pattern) {

shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern >> 8);
shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern & 0x00ff);

digitalWrite(PIN_RCKB,HIGH);
digitalWrite(PIN_RCKB,LOW);

}

void PulsePinHigh(byte PinID) {
digitalWrite(PinID,HIGH);
digitalWrite(PinID,LOW);
}

//-- Write single char to DL1414

void WriteLEDChar(char Char,char CharID) {

union DL1414_ DL1414;

DL1414.ShiftBits.Data = Char & 0x7F;
DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars

DL1414.ShiftBits.NotWrite = 1;				// set up data and address

shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
PulsePinHigh(PIN_RCKC);

//	delay(1000);

DL1414.ShiftBits.NotWrite = 0;				// write the character

shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
digitalWrite(PIN_RCKC,HIGH);
PulsePinHigh(PIN_RCKC);

//	delay(1000);

DL1414.ShiftBits.NotWrite = 1;				// disable write

shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
PulsePinHigh(PIN_RCKC);

//	delay(1000);

}

void WriteLEDString(char *pString) {

for (byte i=0; (i < LED_CHARS) && *pString; ++i)
WriteLEDChar(*pString++,i);

return;
}

//-- Sample temperature with a dab of averaging

#define TAVG 5

float Kelvin;

for (byte i = 1; i < TAVG; i++)

return Kelvin * (100.0 * AVREF) / (TAVG * 1024.0);
}

//------------------
// Set things up

void setup() {
pinMode(PIN_HEARTBEAT,OUTPUT);
digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived

//  TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
//  TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11

pinMode(PIN_MOSI,OUTPUT);
digitalWrite(PIN_MOSI,LOW);

pinMode(PIN_SCK,OUTPUT);
digitalWrite(PIN_SCK,LOW);

pinMode(PIN_RCKB,OUTPUT);
digitalWrite(PIN_RCKB,LOW);

pinMode(PIN_RCKC,OUTPUT);
digitalWrite(PIN_RCKB,LOW);

Serial.begin(9600);
fdevopen(&s_putc,0);				// set up serial output for printf()

printf("Temperature sensor - LM335\r\nEd Nisley - KE4ZNU - November 2012\r\n");

WriteLEDString(LEDCharBuffer);

LEDBits = 0x5555;
SetBarBits(LEDBits);

printf("Base Temperature: %d C\n",(int)BaseTemperature);

delay(1000);

MillisThen = millis();

}

//------------------
// Run the test loop

void loop() {

MillisNow = millis();

if ((MillisNow - MillisThen) > UPDATEMS) {
digitalWrite(PIN_HEARTBEAT,HIGH);

printf("Temperature: %d C\n",(int)Temperature);

LEDBits = 0x0200 >> (1 + (int)(Temperature/BARSCALE));	// move upward on display!
SetBarBits(LEDBits);

sprintf(LEDCharBuffer,"%-3dC",(int)Temperature);
WriteLEDString(LEDCharBuffer);

digitalWrite(PIN_HEARTBEAT,LOW);

MillisThen = MillisNow;
}

}
```

1. #1 by Jeff Epler on 2012-12-12 - 10:38

Arduinos based on the atmega328 incorporate a temperature sensor. Apparently the procedure for its use is to select the 1.1v reference (call analogReference(3)) and then read the 8th ADC input (analogRead(8)). 1°C ~= 1mV ~= 0.9 ADC counts, and without calibration there’s only an accuracy of ±10°C anyway(!), so your LM335-based solution is better if you’ve got a spare ADC pin and a sensor in your junk drawer.

• #2 by Ed on 2012-12-12 - 11:06

an accuracy of ±10°C anyway(!)

That’s sort of like the 1.1 V bandgap “reference” with its 9% tolerance; a meaning of the word “reference” that I had not previously encountered…

In the process of doing the LED Curve Tracer firmware, I rummaged through the datasheet and back-calculated the bandgap voltage based on measuring the actual Vcc supply with a DVM. Turned out to be within spec, but without an external reference to calibrate the internal reference, what’s the point?

2. #3 by Jetguy on 2012-12-12 - 11:42

In my other hobbies of messing with IMU sensors, we use the 3.3V as the external reference source. Being that genuine Arduinos have decent onboard regulators, no reason to be using unregulated 5 volt.

And another good blog explaining the “why” http://blog.littlebirdelectronics.com/tutorial-arduino-and-the-aref-pin

• #4 by Ed on 2012-12-12 - 12:59

we use the 3.3V as the external reference source

Aye, that’ll work when you have control over the startup code to prevent shorting the internal & external references. Given a dedicated board running a single program, I think you could make that workable even on an Arduino, but I’d be reluctant to suggest it for general use.

The thing would probably survive the occasional short, producing even more Arduino Love.

unregulated 5 volt.

The stock regulators get you close to 1%, which is much better as a “reference” than the internal bandgap. That’s still give-or-take 10 LSB, though, which pokes out of the usual noise by a factor of maybe four, so better calibration would be useful for things with meaningful differences down to a few LSB.

That said, I harbor no illusions about the accuracy of the cheap DVMs around here… [sigh]

3. #5 by Red County Pete on 2012-12-12 - 13:03

Old home week again. I was the first product engineer (first job, just post cocoon) on National’s LM335. As I recall (circa mid-70s), it replaced a 3 terminal device that had a -10mV/K voltage with respect to a zener based reference, approx 7V or so. I believe it was loosely related to the temperature-compensated bandgap reference (LM117?).

Wouldn’t the at328 temp function get clobbered by the die heat? On another project, (much later) a co-worker used a 3 terminal Dallas Semi digital temperature IC for calibration purposes. Not much power dissipation, and it used a serial bitstream Handy when I/O is limited.

• #6 by Ed on 2012-12-12 - 14:00

I was the first product engineer … on National’s LM335.

Wow! There were giants in those days and you probably didn’t even notice: you thought they were normal!

temperature-compensated bandgap reference (LM117?)

Perhaps the LM336, which is sort of a +5 V reference / shunt regulator / Zener diode thingy. I used them in the HT GPS-audio circuit, mostly because I had them and (some circuit generations back) there wasn’t enough headroom for any of the series regulator in my heap.

Wouldn’t the at328 temp function get clobbered by the die heat?

I think so, particularly in the usual Arduino fire-all-the-LEDs-at-once project. Keeping a digital thumb on the die temperature for a project like that would be useful, as long as nobody thought it represented, say, air temperature outside the ase.

• #7 by Red County Pete on 2012-12-12 - 15:09

Can’t remember the number, but we had an IC that did a 1.1V reference. The zener references were a bit fragile–we tried an improved emitter process and blew the voltages way out of spec.

Giants yeah, some with egos bigger than their talent. :-) Bob Widlar (true genius designer–and way larger than life) was a ‘consulting designer’ at the time. The guy who handled the 335 (dunno if he invented it, or if it was passed on to him) was a decent guy and SF fan. Being a production engineer in that group was not a fun experience. Once the 74-75 recession ended I took off-along with a bunch of others. I heard it had 125% turnover in the product engineering group in 18 months. Management style (by screaming) changed to normal when key folks split to form Linear Technologies.

• #8 by Ed on 2012-12-12 - 15:36

when key folks split

That’s part of what management gurus call “creative destruction”, right? [wince]

4. #9 by Red County Pete on 2012-12-12 - 16:57

FWIW, the 1.2V bandgap reference is the LM113. Only package is the metal can. (No plastics, apparently, and it looks like it’s about obsolete.)

Oh yeah, the screaming managers were the ones who left for LT. I gather the pay there was good if you had a thick skin and lousy hearing.

• #10 by Ed on 2012-12-12 - 19:38

it looks like it’s about obsolete

Must be true: can’t even find one on eBay!