The DHT sensor family (primarily the DHT11 and DHT22/AM2302) outputs a digital single-bus serial signal, requires a 4.7kΩ pull-up resistor on the data line, and needs zero user calibration to yield physical units. Unlike analog thermistors that require you to calculate resistance and apply the Steinhart-Hart equation, the DHT series handles all analog-to-digital conversion internally. You either read the pre-scaled digital bytes correctly, or you get a timeout error. This guide breaks down the exact bitwise math, hardware requirements, and ESP32-specific timing quirks needed to get reliable readings on the bench.
The Sensing Principle: How DHT11 and DHT22 Actually Work
The DHT series measures ambient conditions using two distinct internal components: a surface-mounted NTC thermistor for temperature and a polymer-based capacitive humidity sensor. The humidity element consists of two electrodes with a moisture-holding polymer dielectric between them; as ambient relative humidity changes, the dielectric constant shifts, altering the capacitance. An internal 8-bit microcontroller samples these analog variations, applies factory-programmed calibration coefficients stored in OTP (One-Time Programmable) memory, and packages the compensated data into a 40-bit digital burst.
Because the analog-to-digital conversion and linearization happen entirely inside the sensor's plastic housing, the host microcontroller only ever receives pre-scaled digital bytes, completely isolating the MCU from the analog sensing domain. This means the output is strictly digital—a time-sensitive serial protocol—and should never be connected to an analog-to-digital converter (ADC) pin. The host MCU simply acts as a master clock, pulling the data line low to initiate a read, then releasing it to listen for the sensor's 40-bit response.
Hardware Wiring and Pinout Specifications
A common mistake among beginners is conflating the DHT's 3-pin breakout modules with analog sensors because they only use three wires. The output is entirely digital. Below is the standard pinout for the bare 4-pin DHT22 (AM2302) and DHT11 components. If you are using a 3-pin PCB module, Pin 3 is omitted, and the pull-up resistor is usually populated on the board.
| Pin | Function | ESP32 / Arduino Connection | Specifications & Notes |
|---|---|---|---|
| 1 | VDD | 3V3 or 5V | Supply range: 3.3V to 5.5V DC. Max current draw: ~2.5mA during conversion. |
| 2 | DATA | GPIO 4 (or any digital pin) | Requires a 4.7kΩ pull-up resistor to VDD. Do not use ADC-only pins. |
| 3 | NC | No Connection | Unused on standard 4-pin packages. Leave floating. |
| 4 | GND | GND | Common ground reference. Must share ground with the MCU. |
The single-bus protocol relies on an open-drain architecture. The sensor pulls the line low to transmit a '0' or '1', but it cannot drive the line high. The 4.7kΩ pull-up resistor is mandatory to return the line to VDD. If your wires exceed 1 meter, drop the resistor to 2.2kΩ to overcome wire capacitance and sharpen the rising edge of the signal.
Decoding the Output: Raw Signal Math and Scaling
When the host MCU triggers a read, the DHT sensor responds with a 40-bit data stream structured as follows: [8-bit Humidity Integer] [8-bit Humidity Decimal] [8-bit Temp Integer] [8-bit Temp Decimal] [8-bit Checksum].
While libraries like Adafruit's DHT abstract this away, understanding the raw-to-unit math is critical for debugging corrupted payloads or writing bare-metal drivers. Here is the exact bitwise math to convert the raw bytes into physical units for the DHT22 / AM2302 (the DHT11 uses a simpler, less precise byte mapping).
Humidity Calculation
The first two bytes represent relative humidity in tenths of a percent. You combine them into a 16-bit integer and divide by 10.0.
uint16_t raw_humidity = (data[0] << 8) | data[1];
float humidity_percent_rh = raw_humidity / 10.0;
Temperature Calculation
Temperature uses the next two bytes. The most significant bit (MSB) of the third byte acts as a sign indicator (1 = negative, 0 = positive). You mask out the sign bit, combine the remaining 15 bits, apply the sign, and divide by 10.0.
uint16_t raw_temp = ((data[2] & 0x7F) << 8) | data[3];
float temp_celsius = raw_temp / 10.0;
if (data[2] & 0x80) {
temp_celsius = -temp_celsius; // Apply negative sign
}
Checksum Verification
To ensure data integrity over the noisy single-wire bus, the sensor sends an 8-bit checksum. If the sum of the first four bytes (masked to 8 bits) does not equal the fifth byte, the payload is corrupt and must be discarded.
uint8_t checksum = data[0] + data[1] + data[2] + data[3];
if (checksum != data[4]) {
// Trigger read error, discard data
}
Calibration Note: No user scaling or calibration coefficients are required. The division by 10.0 is simply a decimal shift, not a calibration curve. The internal OTP memory handles all non-linear compensation.
ESP32 Implementation and Common Interference Fixes
Interfacing a DHT sensor with an ESP32 introduces specific challenges not present on an Arduino Uno. The ESP32's FreeRTOS operating system and WiFi radio can cause interrupt latency, leading to missed bits in the DHT's strict microsecond-timed protocol. Furthermore, when the ESP32 transmits WiFi data, it can draw peak currents exceeding 250mA, causing a momentary brownout on the 3.3V rail that resets the DHT sensor mid-read.
To solve this, use the DHTesp library rather than the standard Adafruit library. DHTesp is optimized for ESP32, correctly managing interrupt disabling without triggering the hardware watchdog timer.
#include <DHTesp.h>
// Define the GPIO pin connected to the DHT Data pin
const int DHT_PIN = 4;
DHTesp dht;
void setup() {
Serial.begin(115200);
// Initialize the sensor for DHT22 (use DHTesp::DHT11 for the blue sensor)
dht.setup(DHT_PIN, DHTesp::DHT22);
Serial.println("DHT22 ESP32 Setup Complete. Waiting for sensor stabilization...");
delay(2000); // DHT sensors require a 1-2 second startup delay
}
void loop() {
// DHT22 requires a minimum 2-second interval between reads
delay(dht.getMinimumSamplingPeriod());
float humidity = dht.getHumidity();
float temperature = dht.getTemperature();
// Check for timeout or checksum errors using library status
if (dht.getStatus() != DHTesp::OK) {
Serial.print("Sensor Error: ");
Serial.println(dht.getStatusString());
} else {
Serial.printf("Temp: %.1f C | Humidity: %.1f %%\n", temperature, humidity);
}
}
Mitigating Common Interference Sources
- WiFi Brownouts: If your ESP32 randomly returns
NaNwhen WiFi connects, power the DHT22 from theVIN(5V) pin instead of3V3, and use a level shifter or a voltage divider on the data line to protect the ESP32's 3.3V GPIO. Alternatively, add a 100µF decoupling capacitor across the DHT's VDD and GND pins. - EMI from Relays/Mains: The high-impedance data line acts as an antenna. Keep DHT wiring away from AC mains traces and switching relay coils. Use twisted pair wire for runs longer than 50cm.
- Bus Contention: Never place more than one DHT sensor on the same GPIO pin. Unlike true Dallas 1-Wire devices (like the DS18B20), the DHT protocol lacks a unique 64-bit ROM serial number, making multi-drop addressing impossible.
For deeper hardware specifications and timing diagrams, refer to the Adafruit DHT Sensor Guide and the official Espressif ESP32 GPIO Documentation regarding interrupt latency limits.
Frequently Asked Questions
Why is my DHT sensor returning NaN or -999 on the ESP32?
This is almost always caused by interrupt latency or a power brownout. The DHT protocol requires the MCU to read GPIO state changes with microsecond precision. If the ESP32 is handling WiFi interrupts or running heavy FreeRTOS tasks, it misses the timing window, resulting in a checksum failure or timeout (which libraries report as NaN). Ensure you are polling the sensor no faster than once every 2 seconds, use the DHTesp library, and verify your 3.3V rail isn't dipping when the WiFi radio fires.
Can I power a DHT22 directly from an ESP32 3.3V pin?
Yes, the DHT22 operates natively from 3.3V to 5.5V. However, the ESP32's onboard 3.3V voltage regulator (often an AMS1117-3.3) has a strict current limit. While the DHT22 only draws ~2.5mA during a read, if you have other 3.3V peripherals (like an I2C OLED display) attached, you may exceed the regulator's thermal limits. If the ESP32 feels hot to the touch, power the sensor from the 5V VIN pin and use a resistor divider (e.g., 2.2kΩ and 3.3kΩ) on the data line to step the 5V logic down to 3.3V.
How accurate is the DHT22 compared to the BME280?
The DHT22 is rated for ±2% RH and ±0.5°C accuracy, but in real-world bench testing, it often drifts to ±4% RH in high-humidity environments and suffers from slow response times (up to 10 seconds to register a sudden change). The Bosch BME280 uses an I2C/SPI interface, offers ±3% RH and ±1.0°C accuracy, but includes barometric pressure and responds in under 1 second. If you are building a weather station or a PID-controlled incubator, upgrade to the BME280 or SHT31. The DHT22 is best reserved for basic HVAC monitoring and educational projects.
Do I still need a pull-up resistor if using a 3-pin DHT breakout module?
No. If you bought a pre-assembled 3-pin PCB module (usually featuring a small surface-mount resistor labeled '103' or '472' near the pins), the 4.7kΩ or 10kΩ pull-up resistor is already soldered onto the board. Adding an external pull-up resistor on your breadboard will create a parallel resistance, potentially pulling the line high too aggressively and violating the sensor's open-drain voltage thresholds. Always inspect the PCB traces before adding redundant components.






