Sunday, February 19, 2017

Library for reading internal temperature sensor on AVR microcontrollers

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.


Library for reading internal temperature sensor on AVR microcontrollers

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

 

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.

Friday, February 10, 2017

DHT11 Library Temperature and Humidity Sensor

 Interfacing DHT11 sensor with an AVR Microcontroller


This post describes how to interface a DHT11 temperature and humidity sensor with an AVR microcontroller and how to read and display the data on an LCD using the DHT11 library.

DTH11 is a low cost hobby digital sensor used to measure temperature and relative humidity. Digital means that the sensor incorporates a 8 bit microcontroller inside that takes care of ADC measurements for you. With an analog sensor you would have to set up an ADC and measure the sensor resistance directly and interpret the data. The DHT11 sensor uses a proprietary 1-wire protocol which is described down bellow.
The DHT11 sensor comes in a single row 4-pin package, breadboard friendly, and operates from 3.5 to 5.5V. It can measure temperature from 0-50 °C with an accuracy of ±2°C and relative humidity ranging from 20-95% with an accuracy of  ±5%. During measurement it draws 0.3mA and in standby 60uA.
The sampling rate is 0.5 Hz in some datasheets and 1 Hz on others; this means it is not recommended to read the sensor more than once every second or every two seconds. The recommended sample rate is 5 seconds to prevent the display changing to often.

DHT11 pinout

DHT11 Pinout

Wiring up the DHT11 sensor

Wiring up DHT11
Pin 1 is connected to power supply VCC.
Pin 2 is the DATA LINE that communicates with the MCU and in this example is connected to pin PC5. However you can connect it to any digital pin on the microcontroller. A 4.7k resistor is used to pull up the pin according to the datasheet.
Pin 3 is not connected. You can let it flapping in the breeze.
Pin 4 is connected to ground.

DHT11 serial 1-wire bi-directional communication protocol

DHT11 communication protocol

DHT11 communication protocol (click to enlarge)

In the idle state DHT11 sensor data line must be kept high by the pull-up resistor.
  • The reading starts when the microcontroller pulls the bus LOW for at least 18 milliseconds and then waits for about 20 - 40 microseconds for the sensor to pull the bus LOW. During this time the MCU pin is in reading mode and the bus line is HIGH as can be seen in the above diagram.
  • The DHT11 sensor then respond by pulling the line LOW for 80 us and then HIGH for 80 us.
  • From now the sensor starts transmitting 40 bits (5 bytes) of data. BIT 0 is transmitted by pulling the line LOW for 50 us and then HIGH for 26 - 28 us. BIT 1 is transmitted by pulling the line LOW for 50 us and then HIGH for 70 us.
  • After all 40 bits are sent, the sensor pulls the line bus LOW for 50 us and then the line is pulled HIGH by the pull-up resistor until the microcontroller initiates a new reading.

 

  • DHT11 data format:

The bits are sent starting with MSB (Most Significant Bit).
40 bits = 8bit humidity integer data + 8bit humidity decimal data + 8 bit temperature integer data + 8bit decimal temperature data + 8 bit parity bit.
Because DHT11 temperature accuracy is ±2°C and humidity is ±5% the decimal bits will always be 0.
The checksum is calculated by adding the first 4 bytes and the sum of them must be equal to the 5'th byte - the parity bit.

DHT11 sensor library for AVR microcontrollers

This library is easy to implement and use and incorporates safety checks to prevent the MCU remaining stuck in a while loop while waiting for sensor responses, in case the sensor breaks and remain in a HIGH or LOW state.
First download and copy the library in your project and then open it to specify the location of the pin where the sensor is connected.

In the setup section you have the following settings:
// Depending on the port and pin you are using replace the x with the port letter, e.g. C, D, B, and n with the pin number
#define SENSOR_DDR              DDRx
#define SENSOR_PORT             PORTx
#define SENSOR_PIN              PINx
#define SENSOR_PIN_BIT          PCn

// SAMPLE_DELAY in milliseconds. Default is 2 seconds
#define SAMPLE_DELAY            2000

// This is used by the "DHT11ReadDataAvg" function to take an average measurement and indicates how many samples to take
#define DHT_NR_OF_SAMPLES       8

// It is recommended to calibrate the sensor by using other two temperature measuring devices and put the offset here (in degrees celsius. If positive, will be added to final result, if negative, will be subtracted
#define DHT_TEMP_ERROR_OFFSET   0

// Comment this line out if you want to add the delay in your code
#define ADD_MINIMUM_DELAY

In the main file include the DTH11 library:
#include "DHT11sensor v1.0.h"

  •  Read the temperature or humidity:

To read the temperature or humidity first use the function "DHT11ReadData()". If the reading was successfully the function will return 1, if there was an error the return error code will be 0 and if there is a checksum error the returned code is -1. Based on this error codes you can take proper actions.

  • Display the temperature or humidity on LCD:

DHT11DisplayTemperature() will display the temperature on an LCD followed by the Celsius sign.
DHT11DisplayHumidity() will display humidity on an LCD followed by the % sign.
To be able to use the display functions you need to download and add the LCD library from here OnLCDLib v1.3.h (link opens in a new tab).

  • Measuring and displaying an average of temperature and humidity:

DHT11ReadDataAvg() this function will take n number of readings, n being defined by DHT_NR_OF_SAMPLES and average them. After each reading a delay will be added and the duration depends on SAMPLE_DELAY . So if the delay is 2 seconds  and number of samples is 8 the total average reading will be 16 seconds.
If you use DHT11ReadDataAvg you don't have to use DHT11ReadData function.

Both DHT11ReadDataAvg and DHT11ReadData functions dumps the received data in DHT11Data array. So if you use UART you can access this array to send the data instead of displaying on LCD. Humidity value is in DHT11Data[0] and the temperature is in DHT11Data[2].

Example on how to use the DHT11 library in a main.c project
/****************************************
 INCLUDES
*****************************************/
#include <avr/io.h>
#include <util/delay.h>
#include "DHT11sensor v1.0.h"


/****************************************
 MAIN FUNCTION
*****************************************/

int main(void){
    // Initialise the LCD
    LCDSetup(LCD_CURSOR_NONE);

    int8_t DHTreturnCode;
 
    while(1){
        DHTreturnCode = DHT11ReadData();

        if(DHTreturnCode == 1){
            LCDHome();
            DHT11DisplayTemperature();
            LCDGotoXY(1,2);
            DHT11DisplayHumidity();
        }else{
            if(DHTreturnCode == -1){
                LCDHome();
                LCDWriteString("Checksum Error");
            }else{
                LCDHome();
                LCDWriteString("Unknown Error");
            }
        }
    }
}

Tip: don't put the sensor near a voltage regulator or other heat sources.

Download DHT11 Library

DHT11sensor v1.0.h