DHT22 with ESP32 ESP-IDF and Display Readings on OLED

In this tutorial, we will learn about DHT22, which is a widely used sensor to obtain current temperature and humidity readings using ESP32 and ESP-IDF. This guide will include a brief description of the sensor, connection with ESP32 board and then setting up a project in VS Code with ESP-IDF extension to acquire the sensor data and display it both on the console and SSD1306 OLED.

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

DHT22 Sensor Introduction

DHT22 sensor (AM2302) is a simple, low priced temperature and humidity sensor that uses capacitive humidity sensor and a thermistor to calculate the sensor data in the surrounding atmosphere. This includes the relative humidity and the ambient temperature. It is a pre-calibrated sensor that outputs a calibrated digital output with a 1-wire protocol. Therefore, they are easily connected with the microcontrollers eg. ESP32 development boards using a single GPIO pin.

The diagram below shows the pinout of the DHT22 sensor that is commonly used.

DHT22 Sensor Pinout

A typical DHT sensor contains four pins out of which two are the power supply pins (VCC and GND) and another is the Data pin while the third pin is not used. Note that the pin number is read from left to right while holding the sensor from the front end.

DHT22 PinDescription
1 (VCC)This is the power supply pin which is connected with 3.3-5.0V. 
2 (Data Out)The second pin is the Data Out pin which gives the digital output. It outputs the value of measured temperature and humidity in the form of serial data
3 (NC)The third pin is not connected
4 (GND)This is the ground pin

If using the DHT22 sensor, the Data out pin is connected with a GPIO pin of the microcontroller through a pull-up resistor. The pull-up resistor is responsible for ensuring the data pin stays HIGH in order to establish a successful communication between the sensor and the microcontroller.

However, a DHT22 module contains only three pins: VCC, Data and GND. In this case, the second pin which is the Data pin is internally connected with a 10k ohm pull-up resistor so there is no need to add an external resistor when connecting it with an ESP32 GPIO pin.

DHT22 Module
DHT22 Sensor Module

Interfacing DHT22 Sensor with ESP32

We will need the following components to connect our ESP32 board with the DHT22 sensor.

  1. ESP32 board
  2. DHT22 sensor
  3. 10k ohm resistor (not required if using the DHT22 sensor module)
  4. Breadboard
  5. Connecting Wires

The connections between the devices are specified in the table below:

DHT22 PinESP32
1 (Vcc)The VCC pin will be connected with 3.3V or Vin pin of ESP32 board.
2 (Data Out)Any GPIO pin of the ESP32 board along with a 10k ohm pull-up resistor. We are using GPIO27 to connect with pin 2 along with the pull-up resistor.
3 (NC)This pin is not used.
4 (GND)Common ground with ESP32.

Connect the DHT22 sensor with the ESP32 development board as shown in the schematic diagram below:

ESP32 with DHT22 sensor connection diagram

Measure DHT22 Readings with ESP32 and ESP-IDF

We will build and create a project in VS Code with ESP-IDF extension. The code will read temperature and humidity readings by using the DHT22 library. We will use DHT22 library provided by Andrey-m on GitHub.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory. We have named our project ‘DHT22.’ For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

  • First, let’s add the necessary header files for the libraries required for this project. Create a new folder called ‘components/DHT22’ and add the following files listed below:
ESP-IDF DHT22 Libraries

CMakeLists.txt

idf_component_register(SRCS "DHT22.c"
                    INCLUDE_DIRS "include")

DHT22.h

/* 
	DHT22 temperature sensor driver
*/

#ifndef DHT22_H_  
#define DHT22_H_

#define DHT_OK 0
#define DHT_CHECKSUM_ERROR -1
#define DHT_TIMEOUT_ERROR -2

// == function prototypes =======================================

void 	setDHTgpio(int gpio);
void 	errorHandler(int response);
int 	readDHT();
float 	getHumidity();
float 	getTemperature();
int 	getSignalLevel( int usTimeOut, bool state );

#endif

DHT22.c

/*------------------------------------------------------------------------------
	DHT22 temperature & humidity sensor AM2302 (DHT22) driver for ESP32
	Jun 2017:	Ricardo Timmermann, new for DHT22  	
	Code Based on Adafruit Industries and Sam Johnston and Coffe & Beer. Please help
	to improve this code. 
	
	This example code is in the Public Domain (or CC0 licensed, at your option.)
	Unless required by applicable law or agreed to in writing, this
	software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
	CONDITIONS OF ANY KIND, either express or implied.
	PLEASE KEEP THIS CODE IN LESS THAN 0XFF LINES. EACH LINE MAY CONTAIN ONE BUG !!!
---------------------------------------------------------------------------------*/

#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE

#include <stdio.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/gpio.h"

#include "DHT22.h"

// == global defines =============================================

static const char* TAG = "DHT";

int DHTgpio = 4;				// my default DHT pin = 4
float humidity = 0.;
float temperature = 0.;

// == set the DHT used pin=========================================

void setDHTgpio( int gpio )
{
	DHTgpio = gpio;
}

// == get temp & hum =============================================

float getHumidity() { return humidity; }
float getTemperature() { return temperature; }

// == error handler ===============================================

void errorHandler(int response)
{
	switch(response) {
	
		case DHT_TIMEOUT_ERROR :
			ESP_LOGE( TAG, "Sensor Timeout\n" );
			break;

		case DHT_CHECKSUM_ERROR:
			ESP_LOGE( TAG, "CheckSum error\n" );
			break;

		case DHT_OK:
			break;

		default :
			ESP_LOGE( TAG, "Unknown error\n" );
	}
}

/*-------------------------------------------------------------------------------
;
;	get next state 
;
;	I don't like this logic. It needs some interrupt blocking / priority
;	to ensure it runs in realtime.
;
;--------------------------------------------------------------------------------*/

int getSignalLevel( int usTimeOut, bool state )
{

	int uSec = 0;
	while( gpio_get_level(DHTgpio)==state ) {

		if( uSec > usTimeOut ) 
			return -1;
		
		++uSec;
		ets_delay_us(1);		// uSec delay
	}
	
	return uSec;
}

/*----------------------------------------------------------------------------
;
;	read DHT22 sensor
copy/paste from AM2302/DHT22 Docu:
DATA: Hum = 16 bits, Temp = 16 Bits, check-sum = 8 Bits
Example: MCU has received 40 bits data from AM2302 as
0000 0010 1000 1100 0000 0001 0101 1111 1110 1110
16 bits RH data + 16 bits T data + check sum
1) we convert 16 bits RH data from binary system to decimal system, 0000 0010 1000 1100 → 652
Binary system Decimal system: RH=652/10=65.2%RH
2) we convert 16 bits T data from binary system to decimal system, 0000 0001 0101 1111 → 351
Binary system Decimal system: T=351/10=35.1°C
When highest bit of temperature is 1, it means the temperature is below 0 degree Celsius. 
Example: 1000 0000 0110 0101, T= minus 10.1°C: 16 bits T data
3) Check Sum=0000 0010+1000 1100+0000 0001+0101 1111=1110 1110 Check-sum=the last 8 bits of Sum=11101110
Signal & Timings:
The interval of whole process must be beyond 2 seconds.
To request data from DHT:
1) Sent low pulse for > 1~10 ms (MILI SEC)
2) Sent high pulse for > 20~40 us (Micros).
3) When DHT detects the start signal, it will pull low the bus 80us as response signal, 
   then the DHT pulls up 80us for preparation to send data.
4) When DHT is sending data to MCU, every bit's transmission begin with low-voltage-level that last 50us, 
   the following high-voltage-level signal's length decide the bit is "1" or "0".
	0: 26~28 us
	1: 70 us
;----------------------------------------------------------------------------*/

#define MAXdhtData 5	// to complete 40 = 5*8 Bits

int readDHT()
{
int uSec = 0;

uint8_t dhtData[MAXdhtData];
uint8_t byteInx = 0;
uint8_t bitInx = 7;

	for (int k = 0; k<MAXdhtData; k++) 
		dhtData[k] = 0;

	// == Send start signal to DHT sensor ===========

	gpio_set_direction( DHTgpio, GPIO_MODE_OUTPUT );

	// pull down for 3 ms for a smooth and nice wake up 
	gpio_set_level( DHTgpio, 0 );
	ets_delay_us( 3000 );			

	// pull up for 25 us for a gentile asking for data
	gpio_set_level( DHTgpio, 1 );
	ets_delay_us( 25 );

	gpio_set_direction( DHTgpio, GPIO_MODE_INPUT );		// change to input mode
  
	// == DHT will keep the line low for 80 us and then high for 80us ====

	uSec = getSignalLevel( 85, 0 );
//	ESP_LOGI( TAG, "Response = %d", uSec );
	if( uSec<0 ) return DHT_TIMEOUT_ERROR; 

	// -- 80us up ------------------------

	uSec = getSignalLevel( 85, 1 );
//	ESP_LOGI( TAG, "Response = %d", uSec );
	if( uSec<0 ) return DHT_TIMEOUT_ERROR;

	// == No errors, read the 40 data bits ================
  
	for( int k = 0; k < 40; k++ ) {

		// -- starts new data transmission with >50us low signal

		uSec = getSignalLevel( 56, 0 );
		if( uSec<0 ) return DHT_TIMEOUT_ERROR;

		// -- check to see if after >70us rx data is a 0 or a 1

		uSec = getSignalLevel( 75, 1 );
		if( uSec<0 ) return DHT_TIMEOUT_ERROR;

		// add the current read to the output data
		// since all dhtData array where set to 0 at the start, 
		// only look for "1" (>28us us)
	
		if (uSec > 40) {
			dhtData[ byteInx ] |= (1 << bitInx);
			}
	
		// index to next byte

		if (bitInx == 0) { bitInx = 7; ++byteInx; }
		else bitInx--;
	}

	// == get humidity from Data[0] and Data[1] ==========================

	humidity = dhtData[0];
	humidity *= 0x100;					// >> 8
	humidity += dhtData[1];
	humidity /= 10;						// get the decimal

	// == get temp from Data[2] and Data[3]
	
	temperature = dhtData[2] & 0x7F;	
	temperature *= 0x100;				// >> 8
	temperature += dhtData[3];
	temperature /= 10;

	if( dhtData[2] & 0x80 ) 			// negative temp, brrr it's freezing
		temperature *= -1;


	// == verify if checksum is ok ===========================================
	// Checksum is the sum of Data 8 bits masked out 0xFF
	
	if (dhtData[4] == ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) 
		return DHT_OK;

	else 
		return DHT_CHECKSUM_ERROR;
}
  • Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in that file and save it.

ESP32 DHT22 Display Readings Code

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "rom/ets_sys.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "sdkconfig.h"

#include "DHT22.h"

void DHT_reader_task(void *pvParameter)
{
		setDHTgpio(GPIO_NUM_27);

	while(1) {
	
		printf("DHT Sensor Readings\n" );
		int ret = readDHT();
		
		errorHandler(ret);

		printf("Humidity %.2f %%\n", getHumidity());
		printf("Temperature %.2f degC\n\n", getTemperature());
		
		vTaskDelay(2000 / portTICK_RATE_MS);
	}
}

void app_main()
{
	nvs_flash_init();
        vTaskDelay(2000 / portTICK_RATE_MS);
	xTaskCreate(&DHT_reader_task, "DHT_reader_task", 2048, NULL, 5, NULL );
}

How the Code Works?

Firstly, we will start by including the necessary libraries for this project. This includes the gpio driver, DHT22 library and FreeRTOS libraries. The driver/gpio.h will be used as we are using a GPIO pin of ESP32 to access the digital value of the sensor. The FreeRTOS libraries will be used to create delays and tasks. Moreover, the DHT22.h APIs will be used to access the humidity and temperature readings.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "rom/ets_sys.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "sdkconfig.h"

#include "DHT22.h"

Inside the DHT_reader_task() function, first the GPIO pin connected with the data pin is set using setDHTgpio() function. This function takes in a single parameter which is the ESP32 GPIO pin number that will output the sensor data.

Then inside an infinite while loop, we will start reading from the DHT22 sensor after every 2 seconds using readDHT(). The errorHandler() function checks for the response after reading from DHT22. To obtain the humidity reading in percentage, getHumidity() function is used. This returns the humidity reading as a float data type. Likewise, to obtain the temperature reading in degree Celsius, getTemperature() function is used. This also returns the temperature reading as a float data type. Both the sensor readings are continuously read after a delay of 2 seconds. Note that the DHT22 sensor’s sampling rate is 0.5Hz which means that it outputs new readings after every 2 seconds. Hence, the time delay is of importance.

void DHT_reader_task(void *pvParameter)
{
		setDHTgpio(GPIO_NUM_27);

	while(1) {
	
		printf("DHT Sensor Readings\n" );
		int ret = readDHT();
		
		errorHandler(ret);

		printf("Humidity %.2f %%\n", getHumidity());
		printf("Temperature %.2f degC\n\n", getTemperature());
		
		vTaskDelay(2000 / portTICK_RATE_MS);
	}
}

Inside the app_main() function, we will first initialize NVS by calling the nvs_flash_init() function. Then use xTaskCreate() to create the DHT22 reader task that will obtain DHT22 sensor readings and print them on the console after every 3 seconds.

void app_main()
{
	nvs_flash_init();
	vTaskDelay(1000 / portTICK_RATE_MS);
	xTaskCreate(&DHT_reader_task, "DHT_reader_task", 2048, NULL, 5, NULL );
}

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, you can view the temperature and humidity readings on the console, updating after every 2 seconds.

ESP-IDF DHT22 Readings on Console

ESP32 ESP-IDF Display DHT22 Readings on OLED

Now let us create another project where we will program our ESP32 board to acquire DHT22 sensor readings and display them on a 0.96 SSD1306 OLED display.

Create Project

Follow all the same steps to create a similar project as we did before. This time, however, we have to add library for the OLED as well.

  • First, let’s add the necessary header files for the libraries required for this project. After adding the DHT22 libraries let us proceed to add ssd1306 libraries. Under the components folder, create a new folder called ‘ssd1306’ and add all the files present at this link. This includes all the files listed below:
ESP-IDF OLED Libraries

OLED MenuConfig Settings ESP-IDF

Let’s first head over to the menuconfig. Open the ESP-IDF SDK Configuration Editor. Scroll down and open the SSD1306 Configuration. Here we can set the SSD1306 configuration parameter according to our needs. This includes the UART interface, panel type, SCL GPIO pin, SDA GPIO pin, and the reset GPIO pin. Here you can view that by default, ESP-IDF is using the interface as I2C, panel type as 128×64, SCL GPIO pin as 22, SDA GPIO pin as 21 and Reset GPIO pin as 33. You can alter these parameters according to your OLED display and then click the Save button found at the top. We will leave the configuration settings as default as they match our module.

ESP32 OLED using ESP-IDF TextDemo project SDK configuration editor
  • Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in that file and save it.

ESP32 DHT22 Display Readings on OLED Code

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "rom/ets_sys.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "driver/i2c.h"
#include "esp_err.h"

#include "DHT22.h"
#include "ssd1306.h"

void DHT_Oled_task(void *pvParameter)
{
		setDHTgpio(GPIO_NUM_27);

                SSD1306_t dev;
		i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);
		
		ssd1306_init(&dev, 128, 64);
		ssd1306_clear_screen(&dev, false);
		ssd1306_contrast(&dev, 0xff);

	while(1) {
		int ret = readDHT();
		
		errorHandler(ret);

        float hum = getHumidity();
	char humidity[8];
        sprintf(humidity, "%.2f %%", hum);
     
        float temp = getTemperature();
	char temperature[12];
        sprintf(temperature, "%.2f degC", temp);

        ssd1306_display_text(&dev, 0, humidity, 8, false);
	ssd1306_display_text(&dev, 2, temperature, 12, false);
		
	vTaskDelay(2000 / portTICK_RATE_MS);
	}
}

void app_main()
{
	nvs_flash_init();
        vTaskDelay(2000 / portTICK_RATE_MS);
	xTaskCreate(&DHT_Oled_task, "DHT_Oled_task", 2048*2, NULL, 5, NULL );
}

How the Code Works?

Most of the code is the same as in the previous project where we accessed sensor readings of DHT22 after every 2 seconds. This time however, instead of displaying the readings on the ESP-IDF terminal, our aim is to display them on an OLED. So let us look over the parts where we are incorporating the OLED. The rest of the functions are the same to acquire the DHT22 temperature and humidity readings.

Inside the DHT_Oled_task() we will first initialize the I2C interface. The I2C interface will be configured by calling the i2c_master_init() function and passing the address of the SSD1306_t structure, SDA pin, SCL pin and Reset pin as parameters inside it. This will configure the ESP32 master to listen to slaves on the I2C bus.

SSD1306_t dev;
i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);

After that, we will initialize the OLED display by calling ssd1306_init() function. This takes in three parameters. The first parameter is the address of the SSD1306_t structure, the second parameter is the width and the third parameter is the height of the display in pixels. We are using a 128×64 display hence the width is 128 and the height is 64.

ssd1306_init(&dev, 128, 64);

Let’s clear the screen of the OLED display by using ssd1306_clear_screen(). This function takes in two parameters. The first is the address of the SSD1306_t structure and the second parameter is invert which is a bool variable. It is set as false. This means we have a dark background and the text will be displayed in white.

ssd1306_clear_screen(&dev, false);

Secondly, the contrast of the screen is set using ssd1306_contrast(). This function takes in two parameters. The first is the address of the SSD1306_t structure and the second parameter is the contrast value which is an int variable. It is set as 0xff.

ssd1306_contrast(&dev, 0xff);

After that the DHT sensor is read and the humidity reading is accessed through getHumidity() and the temperature value is accessed through getTemperature(). These functions return the values in float data type. We will first convert the float data type to an array of characters, round off to two decimal places and add the units for each reading. Then display the sensor readings on the OLED.

	while(1) {
		int ret = readDHT();
		
		errorHandler(ret);

        float hum = getHumidity();
		char humidity[8];
        sprintf(humidity, "%.2f %%", hum);
     
        float temp = getTemperature();
		char temperature[12];
        sprintf(temperature, "%.2f degC", temp);

        ssd1306_display_text(&dev, 0, humidity, 8, false);
	ssd1306_display_text(&dev, 2, temperature, 12, false);
		
		vTaskDelay(2000 / portTICK_RATE_MS);
	}

To display each sensor reading on the OLED screen, we will call the function ssd1306_display_text. It takes in five parameters. The first parameter is the address of the SSD1306_t structure. The second parameter is the page number of the composition data which is set as ‘0’ for the humidity reading and ‘2’ for the temperature reading. The third parameter is the text that we want to display. It is the array of characters for each of the sensor reading. The fourth parameter is the length of the text. Lastly, the fifth parameter is invert which is a bool variable. It is set as false. This means we have a dark background and the text will be displayed in white. This function will be called separately for each sensor reading that will get updated after every two seconds.

ssd1306_display_text(&dev, 0, humidity, 8, false);
ssd1306_display_text(&dev, 2, temperature, 12, false);
		
vTaskDelay(2000 / portTICK_RATE_MS);

Inside the app_main() function, we will call xTaskCreate() to create the DHT Oled task that will obtain the DHT22 sensor readings and print them on the OLED after every two seconds.

void app_main()
{
    nvs_flash_init();
    vTaskDelay(2000 / portTICK_RATE_MS);
    xTaskCreate(&DHT_Oled_task, "DHT_Oled_task", 2048*2, NULL, 5, NULL );
}

Hardware Setup OLED with ESP32 and DHT22

Now let us setup our circuit by connecting the ESP32 development board with ab OLED and a DHT22 sensor.

An OLED display requires an operating voltage of 3.3-5V, hence we can connect the VCC pin of OLED either with the 3.3V pin or the Vin pin of the ESP32 module. The GND pins will be common. To connect the I2C pins, we will use the default I2C pins of the ESP32 board. By default in ESP32, GPIO22 is set up as SCL pin and GPIO21 is set up as SDA pin. Therefore, we will connect SDA pin of display with GPIO21 and SCL pin of display with GPIO22.

The tables below show the connections that we will be using to patch up our circuit.

OLED DisplayESP32
VCC3.3V
GNDGND
SCLGPIO22
SDAGPIO21
ESP32DHT22
VCCVCC
GNDGND
GPIO27Data

Assemble the circuit as shown in the schematic diagram below:

ESP32 with DHT22 sensor and OLED schematic diagram

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, you can view in the ESP-IDF terminal that the OLED got configured successfully.

ESP-IDF Display DHT22 Readings on OLED terminal

The OLED screen will start displaying the DHT22 sensor readings that include temperature in degree Celsius and humidity in percentage.

ESP-IDF Display DHT22 Readings on OLED

You may also like to read:

Leave a Comment