Reading the Chip Temperature

Both the ATmega328 and ATmega4809 based Arduino boards have a built in temperature sensor that can be used to measure the temperature of the part, and roughly the ambient temperature. Note that the ATmega2560 in the Arduino Mega does not have this capability.

In an ATmega328 based Arduino, such as the Uno or Nano, the following function will configure the part for temperature readings, perform the reading, and return the temperature in degrees Celsius.

int getTemperature(void) {
ADMUX = (3 << REFS0) | (8 << MUX0); // 1.1 volt reference and input=temperature
delay(10); // wait for things to settle down
ADCSRA = (1 << ADEN) | (1 << ADSC) | (7 << ADPS0); // Set ADC clock, enable ADC, start conversion
while (ADCSRA & (1 << ADSC)) ; // wait for completion
return (ADCW * 100L - 32431) / 122; // Get measurement and convert to degrees Celsius
}

Because the ATmega328 does not have calibration data within the part, the temperature will be approximate.

In an ATmega4809 based Arduino, such as the Nano Every, there are calibration values stored in the part in EEPROM. It performs much more accurate measurements. It is also much faster. Note that the Arduino library maintains an ADC clock period that matches that of the Uno, however the part will run much faster, which is done here.

int getTemperature(void) {
VREF.CTRLA = VREF_ADC0REFSEL_1V1_gc; // 1.1 volt reference
ADC0.CTRLC = ADC_SAMPCAP_bm + 3; // VREF reference, correct clock divisor
ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; // Select temperature sensor
ADC0.CTRLD = 2 * (1 << ADC_INITDLY_gp); // Initial delay of 32us 
ADC0.SAMPCTRL = 31; // Maximum length sample time (32us is desired) 
ADC0.COMMAND = 1; // Start the conversion 
while ((ADC0.INTFLAGS & ADC_RESRDY_bm) == 0) ; // wait for completion 
// The following code is based on the ATmega4809 data sheet
int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row 
uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row 
uint16_t adc_reading = ADC0.RES; // ADC conversion result with 1.1 V internal reference 
uint32_t temp = adc_reading - sigrow_offset; 
temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) 
temp += 0x80; // Add 1/2 to get correct rounding on division below 
temp >>= 8; // Divide result to get Kelvin
uint16_t temperature_in_K = temp;
return temperature_in_K - 273; // Return Celsius temperature
}

If analogRead is going to be used, some register values will need to be reset. First use the analogReference function to select the desired reference then set the values of ADC0.CTRD and ATD0.SAMPCTRL to zero. At this point you can use analogRead again (and it will be faster!).

Just looking at the analogRead speed, it takes 111µs to execute with a 16MHz clock speed. Executing ADC0.CTRLC = ADC_SAMPCAP_bm + 3 in the setup function (and then calling analogReference) will speed it up to 15µs.