Using the Internal Temperature Sensor

I was reading the ATmega328p datasheet this week while I was bored in class (specifically the A2D section) and came across a neat little fact: the 328p (and Im assuming the 8p and 168p aswell) have a built-in temperature sensor.

The datasheet says that its accuracy is +/- 10°, which isnt too good. However, this can be ‘fixed’ by averaging a bunch of samples - I chose ~1000.

All you need to do is paste this code into your IDE window, upload it, and fire up the serial monitor. No modifications to any existing files required!

Potential applications? One good one I can think of is compensation for values within the micros’ program, which could change based on temperature (say crystal frequency or a conversion result, for example).

Im sure its been done before, but why not. Here’s the code:

void setup() 
{
  Serial.begin(9600);
  ADMUX = 0xC8; // turn on internal reference, right-shift ADC buffer, ADC channel = internal temp sensor
  delay(10);  // wait a sec for the analog reference to stabilize
}

void loop() 
{
  Serial.println(averageTemperature()); // so we can debug
  delay(500); // just to slow things down a bit
}

int readTemperature() 
{
  ADCSRA |= _BV(ADSC); // start the conversion
  while (bit_is_set(ADCSRA, ADSC)); // ADSC is cleared when the conversion finishes
  return (ADCL | (ADCH << 8)) - 342; // combine bytes & correct for temp offset (approximate)} 
}

float averageTemperature()
{
  readTemperature(); // discard first sample (never hurts to be safe)

  float averageTemp; // create a float to hold running average
  for (int i = 1; i < 1000; i++) // start at 1 so we dont divide by 0
    averageTemp += ((readTemperature() - averageTemp)/(float)i); // get next sample, calculate running average

  return averageTemp; // return average temperature reading
}
1 Like

I was just reading about this yesterday. In some AVR chips, ADC8 is an internal temperature sensor (like the ATMega). Someone should make a temperature library to make this process easier, for some AVR-C is hard to understand. It took me a while to figure out how the bitshift function worked :stuck_out_tongue:

:slight_smile: That's awesome. Definitely a script I will keep around for use later.

EDIT:
Just a little note, I modified it slightly to show Fahrenheit instead of Celsius.

  Serial.print(1.8*averageTemperature()+32); // so we can debug
  Serial.println("f");

Also, it's not very accurate to outside temperature (While it's a warm night, it's not the 90 degrees Fahrenheit that it's claiming. I would guess it's going to be hotter because it is inside the chip, as it's rougly 70 degrees right now where I am.)

This topic comes up from time to time and has been posted around here before. Keep in mind that the die temperature will have as much to do with how much current is being drawn from all the I/O pins if not more so then the external temperature. If the I/O current draw is constant then I guess it could be used as a temperature sensor with some calibration or compensation applied, otherwise it’s really not a very useful feature.

Lefty

Depends on how you use it. A somewhat useful feature might be to use it for temperature compensation of the RC oscillator. Of course in the Arduino context this is not that useful :slight_smile:

Udo

Hmm, if the die temperature correlates to current draw, I wonder if its possible to approximate current draw by analyzing the temperature.

Just a little note, I modified it slightly to show Fahrenheit instead of Celsius.

Oh, you silly Yanks and your measurement system :wink: Water freezing at 32°?! Blasphemy!

I have to admit that you Canuks probably are more experts about freezing temperatures and all then us Californians. Aah :wink:

Lefty

Our system is really stupid- but there are also universal systems that are really inefficient. We should get rid of our calendar and time units (that might be difficult) and also replace out our rotational system (degrees) with a "turn" system (a milliturn is 1/1000 of a turn, pretty simple). Our units are based off of old conventions, at some point I think that all units should be based off of quantum units (planck distance, planck energy, etc) but many orders higher. More related to the subject, I just got some temp sensors from maxim and BOY are they accurate! They go down to the third decimal point (but to be fair, the third decimal point only has two possibilities).

Hi,
I’ve put together a library around SpikedCola’s code.
I’d publish it under Playground’s Code samples, but it says “insufficient privileges”, so I’ll paste it here.

InternalTemp.h

/*
 * InternalTemp.h
 *
 * Library to read the ATMega internal temperature sensor.
 * Based on code posted by SpikedCola on Arduino forums
 * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1274403811
 * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?action=viewprofile;username=SpikedCola
 *
 *
 * Usage example:

//// Sketch start ////

#include <LiquidCrystal.h>                // comment out this line if you don't have an lcd
#include <InternalTemp.h>

// NOTE: this setup is specific for nuelectronics' Arduino LCD-shield
// http://www.nuelectronics.com
// pin assignments are different from LiquidCrystal's default ones
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);      // comment out this line if you don't have an lcd

const int LCD_NUM_COLS = 16;              // comment out this line if you don't have an lcd
const int LCD_NUM_ROWS =  2;              // comment out this line if you don't have an lcd

// temperature reader object
InternalTemp temp;


void setup() {
  Serial.begin(9600);
  lcd.begin(LCD_NUM_COLS, LCD_NUM_ROWS);  // comment out this line if you don't have an lcd
  temp.begin();
}


void loop() {
  long now;
  long elapsed;
  
  now = millis();
  float t = temp.read();
  elapsed = millis() - now;
  
  Serial.print(t);
  Serial.print(" C");
  Serial.print(" Elapsed:");
  Serial.println(elapsed);
  
  // comment out these lines if you don't have an lcd
  lcd.clear();
  lcd.print(t);
  lcd.print(" C");
  lcd.setCursor(0, 1);
  lcd.print("Elapsed:");
  lcd.print(elapsed);
  // end lcd code
  
  delay(1000); // just to slow things down a bit
}

//// Sketch end ////

 */

#ifndef INTERNALTEMP_H
#define INTERNALTEMP_H

#include <WProgram.h>

class InternalTemp {

public:
    // if not otherwise specified, calculate the temperature
    // as average over these many samples
    static const int NUMSAMPLES_DEFAULT = 1000;
    
    // temperature units
    enum TempUnit { CELSIUS, FAHRENHEIT };
    
    // constructor
    InternalTemp();
    
    // to be called in setup()
    // the number of samples must be >= 1
    // otherwise the default value will be used
    void begin(TempUnit u = CELSIUS, int numSamples = NUMSAMPLES_DEFAULT);
    
    // returns the nmber of samples used to calculate the temperature
    int getNumSamples() { return _numSamples; }
    
    // user can set number of samples at any time
    void setNumSamples(int n);
    
    // get the currently selected unit
    TempUnit getUnit() { return _unit; }
    
    // change the preferred unit for read()
    void setUnit(TempUnit u) { _unit = u; }
    
    // return temperature value using the selected unit
    float read();
    
    // round to the nearest integer
    int readInt() { return (int)(read()+0.5); }
    
    float readFahrenheit();
    float readCelsius();

private:
    int _numSamples;
    TempUnit _unit;
    
    int readSingle();
    float readAvg();
};

#endif

InternalTemp.cpp

#include "InternalTemp.h";


InternalTemp::InternalTemp() {
    _numSamples = 0;
}


void InternalTemp::begin(TempUnit u, int numSamples) {
    ADMUX = 0xC8; // turn on internal reference, right-shift ADC buffer, ADC channel = internal temp sensor
    delay(10);    // wait a sec for the analog reference to stabilize

    if (numSamples <= 0) {
        numSamples = NUMSAMPLES_DEFAULT;
    }
    
    _numSamples = numSamples;
    _unit = u;
}


int InternalTemp::readSingle() {
    // perform reading only if begin() was called
    if (_numSamples == 0) {
        return 0;
    }
    
    // start the conversion
    ADCSRA |= _BV(ADSC);
    
    // ADSC is cleared when the conversion finishes
    while (bit_is_set(ADCSRA, ADSC));
    
    // combine bytes & correct for temp offset (approximate)}
    return (ADCL | (ADCH << 8)) - 342;
}


float InternalTemp::readAvg() {

    // discard first sample (never hurts to be safe)
    readSingle();

    /*
    float averageTemp;
    for (int i = 0; i < _numSamples; i++) {
        averageTemp += readSingle();
    }
    averageTemp /= _numSamples;
    */
    
    float averageTemp;
    for (int i = 1; i < _numSamples; i++) {     // start at 1 so we dont divide by 0
        averageTemp += ((readSingle() - averageTemp)/(float)i);
    }
    
    return averageTemp;
}


float InternalTemp::read() {
    switch(_unit) {
        case CELSIUS:
            return readAvg();
        
        case FAHRENHEIT:
            return 1.8 * readAvg() + 32;
    }
}


float InternalTemp::readCelsius() {
    return readAvg();
}


float InternalTemp::readFahrenheit() {
    return 1.8 * readAvg() + 32;
}


void InternalTemp::setNumSamples(int n) {
    if (n <= 0) {
        return;
    }
    
    _numSamples = n;
}

Ahh, cool! Its neat to see something youve created be furthered to help others. Thanks, Marcello

Thank you.

Your code is probably going to help me in a project of mine :slight_smile:

Suggestions or additions are obviously very welcome (maybe starting from a better name...)

hello,
great work!

unfortunatly i got : 681°C...
o_O

what do you think about that?

Hi!
can you provide the entire sketch code ? It would make easier to help you.

i did;
after see part sketch send me 681.
i was used entire sketch (copy paste)
and same value.

I tested the code on a Freeduino board (ATMega328) and Arduino2009 (also ATMega328) and it worked.

Unless you provide further details, it's hard to tell what could be wrong...

hello,
great work!

unfortunatly i got : 681°C...
o_O

what do you think about that?

What chip are you using? I have a feeling that your chip doesnt have the temperature sensor built in.

If we take your number and add 342 (the offset I chose to give a rough temperature estimate), we get 1023 - 0x3FF. This means ADCL contains 0xFF and ADCH contains 0x03 - something about these numbers seems off to me, so the conclusion Ive come to is youre using an unsupported chip.

If anyone knows better, please correct me. It has been my understanding that only 8P chips (168P, 328P, etc) have internal temperature sensing capabilities (which come from the P, which denotes a picoPower chip).

sorry im a beginner.
its appear mine is a 168....

Water freezing at 32°?! Blasphemy!

Indeed. Water freezes at 273.15° !!
At one atmosphere anyway.
or as low as 203° under very very high pressures :wink:

Mowcius