A few AVR microcontrollers have internal on-chip temperature sensors. The sensor is a diode that produces a temperature dependent voltage. This voltage is measured with the ADC. According to Atmel the typical accuracy of temperature measurements over the full temperature range of the AVR is ±10°C but after calibration the accuracy can be ±1°C over 20°C to 55°C using one-point calibration.
The calibration can be done using one-point calibration and compensation or two-point calibration and compensation. One-point calibration is the easiest - subtract the ambient
temperature from the AVR sensor temperature. In the case of two-point calibration you need to have a controlled temperature environment and use a special equation. For more details visit the Atmel's application note AVR122: Calibration of the AVR's Internal Temperature Reference.
When calibrating the temperature, find the offset error using the value when you first start it because after that it will increase (by 2°C in my case). If the CPU clock is more than 1MHz the ADC Noise Reduction Mode should be used for more accurate results.
Contents
- Library Usage
- Code example for non-UPDI devices
- Code example for UPDI devices
- How to measure internal AVR temperature sensor
- Download
Library Usage
Read temperature
This function makes use of the provided adc library to read the internal on-chip temperature sensor. The ADC needs to be configured before using this function. ADC voltage reference will be set to 1.1V inside the function.
The documentation for the ADC library can be found here: https://www.programming-electronics-diy.xyz/2018/05/analog-to-digital-converter-adc-library.html
float avrOnchipTemp(uint8_t adc_channel, int8_t offset_cal)
Parameters
adc_channel
ADC channel for internal temperature sensor. This can be found in the
datasheet for your particular microcontroller, in the ADC chapter where the
registers are described. Here are ADC channel numbers for the
temperature sensor for some AVR microcontrollers:
- ATmega328P, ATmega328PB, ATmega88PB, ATmega48PB, ATmega168PB: 8
- ATtiny25, ATtiny45, ATtiny85: 31 (ADC4)
- ATtiny202, ATtiny402: 30 (0x1E in hexadecimal)
offset_cal
This value is expressed in degrees Celsius and it will be added to the measured temperature. If negative it will be subtracted. Use a value of 0 if no offset calibration is needed.
Return: the measured temperature in degrees Celsius.
Code example for non-UPDI devices
// See https://www.programming-electronics-diy.xyz/2024/01/defining-fcpu.html #ifndef F_CPU #warning "F_CPU not defined. Define it in project properties." #elif F_CPU != 16000000 #warning "Wrong F_CPU frequency!" #endif #include "avrTemp.h" #include "uart.h" int main(void){ // Local variables float temperatureC = 0.0; // USART Initialization UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT); // ADC initialization // 16000000 / 128 = 125kHz ADC clock adcSetup(ADC_PRESCALER_128); // 10-bit ADC resolution adcResolution(ADC_10bit); // Enable/Disable ADC Noise Reduction Sleep Mode adcUseNoiseReduction(ADC_NOISE_REDUCTION_ENABLED); while(1){ /* Read internal temperature sensor every 1s */ UART_sendString(&uart0, "Temp = "); // Wait for UART transmission to complete // when ADC Sleep Mode is used UART_isSending(&uart0, F_CPU, 115200); // Read ADC channel 8 which is the internal temperature sensor temperatureC = avrOnchipTemp(8, -7); UART_sendFloat(&uart0, temperatureC, 2); UART_send(&uart0, '\n'); _delay_ms(1000); } }
Code example for UPDI devices
// Not necessary in this project but useful. // See https://www.programming-electronics-diy.xyz/2024/01/defining-fcpu.html #ifndef F_CPU #warning "F_CPU not defined. Define it in project properties." #elif F_CPU != 3333333 #warning "Wrong F_CPU frequency!" #endif #include "avrTemp.h" #include "uart.h" int main(void){ // Local variables float temperatureC = 0.0; // Set UART TX pin as output PORTA.DIR |= PIN6_bm; // USART Initialization UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT); // ADC initialization adcSetup(ADC_PRESCALER_32); // 10-bit ADC resolution adcResolution(ADC_10bit); // Initialization delay adcSetInitDelay(ADC_DLY32); // Sampling length adcSetSampleLength(4); // Reduced size of sampling capacitance. Recommended for reference voltages higher than 1V. adcSelectSampleCapacitance(ADC_SAMP_CAP_5pf); // Number of samples in a burst that will be averaged (optional). // Useful for noise filtering. All done in hardware. //adcSetSampleAccumulation(ADC_SAMP_ACC_8, ADC_SAMP_DLY_0, ADC_SAMP_DLY_AUTO_DISABLED); while(1){ /* Read internal temperature sensor every 1s */ UART_sendString(&uart0, "Temp: "); // Read ADC channel 0x1E which is the internal temperature sensor temperatureC = avrOnchipTemp(0x1E, 0); UART_sendFloat(&uart0, temperatureC, 2); UART_send(&uart0, '\n'); _delay_ms(1000); } }
How to measure internal AVR temperature sensor
The library implements two ways of measuring and calculating the temperature
depending on whether the microcontroller is UPDI type or not. In both cases,
the voltage reference is configured for 1.1V internal reference as per
datasheet recommendation and a 1ms delay is added for voltage stabilization in
case another reference voltage was used before.
non-UPDI
The following code is used by the library:
uint16_t adc_val = 0; adcStartConversion(adc_channel); // Start a new conversion while(!adcIsReady()); // Wait for conversion to complete adc_val = adcReadValue(); // Get ADC value // Result is in Kelvin - subtract 273 to convert to Celsius return (adc_val - 273) + offset_cal;
The ADC value, or in other words, the measured voltage, represents the
temperature in Kelvin. One Kelvin equals one degree Celsius. The major
difference is that when water freezes, a Celsius thermometer will read 0°C,
and a Kelvin thermometer will read 273.15 K. The scales differ by 273.15. For
this reason 273 is subtracted from measured value to convert it to Celsius.
UPDI devices
The code for UPDI devices such as ATtiny402 is given in the datasheet except the part in the return line:
uint16_t accum_samples = adcAccSize(); adcStartConversion(adc_channel); // Start a new conversion while(!adcIsReady()); // Wait for conversion to complete adc_val = adcReadValue(); // Get ADC value int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row int32_t temp = (adc_val / accum_samples) - sigrow_offset; // Divide ADC result by number of accumulated samples temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) temp += 0x80; // Add 1/2 to get correct rounding on division below temp >>= ADC_ANALOG_RESOLUTION; // Divide result to get Kelvin return ((temp - 32) * (5.0 / 9.0)) + offset_cal; // Convert Fahrenheit to Celsius
Variable 'accum_samples' holds the number of accumulated samples in a burst in case the ADC accumulator is used. This can be configured using the adcSetSampleAccumulation() to filter out potential noise in the system.
What i like about this newer AVR devices, is that they have this two calibration registers for offset and gain with compensation values written in the factory.
The last line is just the formula for converting Fahrenheit to Celsius.
The datasheet recommends the followings:
4. In ADCn.CTRLD Select INITDLY ≥ 32 µs × fCLK_ADC
5. In ADCn.SAMPCTRL Select SAMPLEN ≥ 32 µs × fCLK_ADC
6. In ADCn.CTRLC Select SAMPCAP = 5 pF
The values for Initialization Delay and Sample Length depends on the ADC clock. The formula for calculating them can be found at https://www.programming-electronics-diy.xyz/2018/05/analog-to-digital-converter-adc-library.html#Calculating_INITDLY_and_SAMPLEN.
The sampling capacitor can easily be selected as shown in the usage example.
Download
Changelog and license can be found at the beginning of the header files | ||
avrTemp v1.1 | ||
avrTemp.h | ||
avrTemp.c | ||
Other Links | ||
ADC library project page | Useful serial terminal to use with UART | |
UART library project page | Useful to display temperature on a serial terminal. | |
Changelog | ||
v1.1 |
31-01-2024: - added support for modern AVR (UPDI devices). Tested on ATiny402. |
No comments:
Post a Comment