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
}
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
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.
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
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;
}
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).