Guide to Interfacing One-Wire Sensor with ESP32 Using ESP-IDF: DHT11 Example

Protonest IoT
7 min readOct 9, 2024

--

Interfacing sensors with microcontrollers is a fundamental aspect of embedded systems development.

Among various sensors, the DHT11 stands out as a popular choice for measuring temperature and humidity due to its simplicity and cost-effectiveness.

This guide walks you through creating firmware for the DHT11 sensor using the ESP32 microcontroller and the ESP-IDF framework. By the end of this article, you’ll understand the underlying principles of one-wire communication protocols and be equipped to interface similar sensors with your projects.

What is the DHT11?

The DHT11 is a basic, low-cost digital temperature and humidity sensor. It utilizes a capacitive humidity sensor and a thermistor to measure the surrounding air, providing results in digital format via a single-wire protocol.

Key Features of DHT11

  • Temperature Range: 0–50°C with ±2°C accuracy.
  • Humidity Range: 20–90% RH with ±5% RH accuracy.
  • Power Supply: 3.3V to 5V.
  • Communication: Single-wire

For an in-depth understanding of the DHT11 sensor specifications and communication protocol, refer to the Link.

Hardware Requirements

Setting Up the ESP-IDF Environment

Follow our article to set the ESP IDF.

https://medium.com/p/8fff7913dcef

Circuit Diagram

Follow the following circuit diagram,

Note: If the module you are using is not having a pullup resistor, please add it externally. Place a 10kΩ resistor between VCC and DATA to maintain a high logic level when the line is idle.

DHT11 Communication Protocol

The DHT11 uses a proprietary single-wire protocol for communication. Understanding this protocol is essential for accurate data retrieval.

  • Start Signal: The microcontroller (ESP32) initiates communication by pulling the DATA line low for at least 18 milliseconds.
  • Sensor Response,

Low Signal: The DHT11 pulls the DATA line low for 80 microseconds.

High Signal: The DHT11 then pulls the DATA line high for 80 microseconds, signaling readiness to transmit data.

  • Data Transmission

The DHT11 sends 40 bits of data in the following order,

8 bits: Humidity integer part.
8 bits: Humidity decimal part.
8 bits: Temperature integer part.
8 bits: Temperature decimal part.
8 bits: Checksum (sum of the previous four bytes).
  • Bit Encoding

Each bit transmission starts with a 50-microsecond low signal.

The length of the subsequent high signal determines the bit value,

   26-28µs High: Represents a binary “0”
70µs High: Represents a binary “1”

Note: The protocol relies heavily on precise timing. Any deviations can result in misinterpretation of data or communication failures.

Firmware Development

Project Structure

Organize your project directory as follows,

Creating “dht11.h”

This header file declares the functions and structures necessary for interacting with the DHT11 sensor.

#ifndef DHT11_H
#define DHT11_H

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

typedef struct {
float temperature;
float humidity;
bool valid;
} dht11_data_t;

bool dht11_init(gpio_num_t gpio);
bool dht11_read(dht11_data_t *data);

#endif

Implementing “dht11.c”

#include "dht11.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include <string.h>
#include "esp_rom_sys.h" // Added for esp_rom_delay_us

static const char *TAG = "DHT11";
static gpio_num_t DHT_GPIO;
// Removed unused variables
static const int MAX_COUNT = 1000;

bool dht11_init(gpio_num_t gpio) {
DHT_GPIO = gpio;
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << DHT_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE, // Enable internal pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
return true;
}

bool dht11_read(dht11_data_t *data) {
uint8_t bits[5];
int i;

memset(bits, 0, sizeof(bits));

// Initialize pins
gpio_set_direction(DHT_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(DHT_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(18)); // 18 ms
gpio_set_level(DHT_GPIO, 1);
esp_rom_delay_us(40); // 40 us

// GPIO should be input now
gpio_set_direction(DHT_GPIO, GPIO_MODE_INPUT);

// Wait for DHT response low
int count = 0;
while (gpio_get_level(DHT_GPIO) == 0) {
count++;
if (count > MAX_COUNT) {
ESP_LOGE(TAG, "Timeout waiting for DHT response low");
return false;
}
esp_rom_delay_us(1);
}

// Wait for DHT response high
count = 0;
while (gpio_get_level(DHT_GPIO) == 1) {
count++;
if (count > MAX_COUNT) {
ESP_LOGE(TAG, "Timeout waiting for DHT response high");
return false;
}
esp_rom_delay_us(1);
}

// Read the 40 bits
for (i = 0; i < 40; i++) {
// Wait for the pin to go low (50µs)
count = 0;
while (gpio_get_level(DHT_GPIO) == 1) {
count++;
if (count > MAX_COUNT) {
ESP_LOGE(TAG, "Timeout waiting for bit start high");
return false;
}
esp_rom_delay_us(1);
}

// Measure how long the pin stays high to determine bit value
int high_time = 0;
while (gpio_get_level(DHT_GPIO) == 0) {
count++;
if (count > MAX_COUNT) {
ESP_LOGE(TAG, "Timeout waiting for bit start low");
return false;
}
esp_rom_delay_us(1);
}

high_time = 0;
while (gpio_get_level(DHT_GPIO) == 1) {
high_time++;
if (high_time > MAX_COUNT) {
ESP_LOGE(TAG, "Timeout waiting for bit high time");
return false;
}
esp_rom_delay_us(1);
}

// Determine if bit is '0' or '1'
if (high_time > 40) { // Threshold for '1'
bits[i / 8] |= (1 << (7 - (i % 8)));
}
}

// Verify checksum
uint8_t checksum = bits[0] + bits[1] + bits[2] + bits[3];
if (checksum != bits[4]) {
ESP_LOGE(TAG, "Checksum failed: %d != %d", checksum, bits[4]);
return false;
}

data->humidity = bits[0] + bits[1] / 10.0;
data->temperature = bits[2] + bits[3] / 10.0;
data->valid = true;

return true;
}

Developing “main.c”

The “main.c” file contains the application logic, including initializing the sensor and periodically reading data from it.

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

#define DHT_GPIO GPIO_NUM_4

void dht11_task(void *pvParameters) {
dht11_data_t data;
while (1) {
if (dht11_read(&data)) {
if (data.valid) {
ESP_LOGI("DHT11", "Temperature: %.1f°C, Humidity: %.1f%%", data.temperature, data.humidity);
}
} else {
ESP_LOGE("DHT11", "Failed to read from DHT11 sensor");
}
vTaskDelay(pdMS_TO_TICKS(2000)); // Wait 2 seconds between reads
}
}

void app_main(void) {
ESP_LOGI("MAIN", "Starting DHT11 example");
dht11_init(DHT_GPIO);
xTaskCreate(&dht11_task, "dht11_task", 2048, NULL, 5, NULL);
}

CMakeList.txt

idf_component_register(SRCS "dht11.c" "main.c"
INCLUDE_DIRS ".")

Now you can select the port, compile and flash the code to esp32 as per our previous article.

https://medium.com/p/8fff7913dcef

Now you will see following,

Detailed Code Explanation

Understanding each part of the code is essential for troubleshooting and adapting it to other sensors. Let’s analyze each file,

dht11.h

Includes

  • “freertos/FreeRTOS.h” and “freertos/task.h”: Necessary for FreeRTOS functionalities like tasks and delays.
  • “driver/gpio.h”: Provides GPIO-related functions and types.
  • “stdbool.h”: Enables the use of the `bool` type.

Structure ‘dht11_data_t’

  • Holds the temperature and humidity readings.
  • Includes a ‘valid’ flag to indicate successful data retrieval.

Function Declarations

  • ‘dht11_init’: Sets up the GPIO pin connected to the DHT11.
  • ‘dht11_read’: Handles the communication protocol to read data from the DHT11.

dht11.c

Static Variables

  • ‘TAG’: Used for logging to identify messages from the DHT11 module
  • “DHT_GPIO”: Stores the GPIO pin number connected to the DHT11.
  • “MAX_COUNT”: Prevents infinite loops by limiting the number of iterations while waiting for signal changes.

Function ‘dht11_init’

  • Configures the specified GPIO pin as an output.
  • Enables the internal pull-up resistor to stabilize the DATA line.
  • Disables pull-down resistors and interrupts on the GPIO pin.

Function ‘dht11_read’

Start Signal: Pulls the DATA line low for 18ms to initiate communication.

Response Handling,

  • Waits for the DHT11 to pull the line low and then high, signaling readiness to transmit data.

Data Transmission,

  • Iterates 40 times to read 40 bits of data.
  • Determines each bit’s value based on the duration of the high signal.

Checksum Verification,

  • Ensures data integrity by comparing the calculated checksum with the received checksum.

Data Population,

  • Extracts temperature and humidity values from the received bits.

main.c

‘DHT_GPIO’

  • Defines the GPIO pin number connected to the DHT11 DATA line.

Function ‘dht11_task’

  • Runs in a FreeRTOS task, continuously reading data from the DHT11 sensor every 2 seconds.
  • Logs the temperature and humidity readings or an error message if reading fails.

Function ‘app_main’

  • The entry point of the application.
  • Initializes the DHT11 sensor and creates the ‘dht11_task’ to handle periodic readings.

Extending to Other One-Wire Sensors

The principles demonstrated for the DHT11 can be adapted to interface with other one-wire sensors. Here’s how to generalize the approach.

  1. Understand the Sensor’s Communication Protocol

Different sensors may have variations in their communication protocols. Always refer to the sensor’s datasheet to understand.

  • Signal Initiation: How to start communication.
  • Response Signals: What signals indicate readiness or data transmission.
  • Data Encoding: How data bits are transmitted (e.g. timing-based, voltage-based).

2. Adjust Timing Parameters

Based on the protocol,

  • Delays: Modify ‘vTaskDelay’ and ‘esp_rom_delay_us’ durations to match the sensor’s requirements.
  • Thresholds: Adjust the ‘high_time’ thresholds to differentiate between bit values accurately.

3. Update Data Structures

Change the ‘dht11_data_t’ structure to accommodate different data types or additional measurements,

typedef struct {
float temperature;
float humidity;
float pressure; // Example: Additional sensor data
bool valid;
} sensor_data_t;

4. Implement Sensor-Specific Logic

  • Each sensor might have unique steps in its communication process. Adapt the ‘read’ function accordingly.
bool sensor_read(sensor_data_t *data) {
// Implement sensor-specific communication protocol
}

Interfacing the DHT11 sensor with the ESP32 using the ESP-IDF framework involves understanding both hardware connections and the sensor’s communication protocol.

This guide not only equips you with the knowledge to work with the DHT11 but also lays the foundation for interfacing with other one-wire sensors.

Hope you enjoyed the article. Please comment below or send us an email to info@protonest.co, if you face any issues when implementing.

Contact us for any consultations or projects related to IoT and embedded systems.

Email: info@protonest.co

Protonest for more details.

Protonest specializes in transforming IoT ideas into reality. We offer prototyping services from concept to completion. Our commitment ensures that your visionary IoT concepts become tangible, innovative, and advanced prototypes.

Our Website: https://www.protonest.co/

Cheers!

--

--

Protonest IoT
Protonest IoT

Written by Protonest IoT

We make your IoT ideas a reality

No responses yet