09 · Reading a Sensor (DHT11 Temperature)¶
Not flashed to hardware
Reasoned through against the documented public API of Adafruit's
widely-used DHT sensor library (the de facto standard Arduino
library for DHT11/DHT22 sensors, built on their single-wire digital
protocol) — DHT::begin(), DHT::readTemperature(),
DHT::readHumidity(), and the isnan() failure-check pattern the
library's own examples document. Not compiled or flashed to physical
hardware in this environment. The DHT11 in particular is documented by
its manufacturer as slow (about 1 reading/second maximum) and coarse
(±2°C, ±5% RH accuracy) — treat it as "good enough to learn sensor
integration with," not as a precision instrument.
The DHT11 sensor and its library dependency¶
The DHT11 is a combined temperature/humidity sensor that communicates over
a single digital data pin using its own bit-banged timing protocol — not
I2C, not SPI, not a simple analog voltage. Because that protocol involves
precise microsecond-level timing, you don't hand-roll it yourself; you use
a library that already implements it. Adafruit's DHT sensor library
is the standard choice and depends on Adafruit's Unified Sensor
library as well.
Installing the libraries¶
- Sketch → Include Library → Manage Libraries…
- Search
DHT sensor library(by Adafruit) and click Install. When prompted to also install its declared dependency, Adafruit Unified Sensor, accept — the DHT library will not compile without it.
Wiring¶
A typical 3-pin DHT11 breakout module: VCC → 3.3V, GND → GND, OUT
(sometimes labeled DATA or S) → a digital GPIO pin of your choice. Bare
4-pin DHT11 modules (not the breakout-board version) additionally need a
10 kΩ pull-up resistor between DATA and VCC — most breakout boards
already include this resistor onboard, so check your specific module.
Basic reading sketch¶
// dht11-basic-read.ino
#include <DHT.h>
#if defined(ESP8266)
const int DHT_PIN = D3; // GPIO0 on NodeMCU boards
#elif defined(ESP32)
const int DHT_PIN = 27;
#endif
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
// The DHT11 is documented as needing at least ~1 second between reads;
// reading faster than that returns stale or invalid data.
delay(2000);
float humidity = dht.readHumidity();
float tempC = dht.readTemperature(); // Celsius by default
float tempF = dht.readTemperature(true); // pass true for Fahrenheit
// The library's own documented convention: a failed read (bad checksum,
// timing glitch, or nothing connected) returns NaN, not zero -- always
// check with isnan() rather than trusting a raw numeric value.
if (isnan(humidity) || isnan(tempC)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
Serial.printf("Humidity: %.1f%% Temp: %.1fC (%.1fF)\n",
humidity, tempC, tempF);
}
Why isnan() matters here specifically¶
Unlike a digital pin (which always reads a clean HIGH or LOW) or an ADC
(which always returns some number even if meaningless), the DHT
library's single-wire timing protocol can fail outright — a dropped bit, a
checksum mismatch, or nothing plugged in at all — and it documents NaN
("Not a Number," a special floating-point value) as its explicit signal for
"this read failed, do not trust it." Skipping the isnan() check and using
a failed NaN reading directly (e.g. logging it, or averaging it into
other readings) silently corrupts everything downstream — this exact
oversight is one of the most common bugs in beginner DHT-based projects
found in online forum troubleshooting threads.
Computing a derived value: heat index¶
The library also documents a computeHeatIndex() helper that combines
temperature and humidity into a single "feels like" number — a good
demonstration of doing something with two related sensor readings together
rather than just logging them independently:
// dht11-heat-index.ino
#include <DHT.h>
#if defined(ESP8266)
const int DHT_PIN = D3;
#elif defined(ESP32)
const int DHT_PIN = 27;
#endif
DHT dht(DHT_PIN, DHT11);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
delay(2000);
float humidity = dht.readHumidity();
float tempC = dht.readTemperature();
if (isnan(humidity) || isnan(tempC)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
// false = compute in Celsius (matches tempC's unit)
float heatIndexC = dht.computeHeatIndex(tempC, humidity, false);
Serial.printf("Temp: %.1fC Humidity: %.1f%% Feels like: %.1fC\n",
tempC, humidity, heatIndexC);
}
A retry-on-failure pattern¶
Since single reads can fail intermittently even with good wiring, a small retry loop before giving up makes a sketch noticeably more robust without much added complexity:
// dht11-read-with-retry.ino
#include <DHT.h>
#if defined(ESP8266)
const int DHT_PIN = D3;
#elif defined(ESP32)
const int DHT_PIN = 27;
#endif
DHT dht(DHT_PIN, DHT11);
bool readTemperatureWithRetry(float &outTempC, int maxAttempts = 3) {
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
float t = dht.readTemperature();
if (!isnan(t)) {
outTempC = t;
return true;
}
Serial.printf("DHT read attempt %d failed, retrying...\n", attempt);
delay(2000); // respect the sensor's minimum read interval before retrying
}
return false;
}
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
float tempC;
if (readTemperatureWithRetry(tempC)) {
Serial.printf("Temperature: %.1fC\n", tempC);
} else {
Serial.println("DHT sensor read failed after all retries.");
}
delay(5000);
}
How It Actually Works¶
The DHT11 has no SPI/I2C bus — it uses a proprietary single-wire, software-bit-banged protocol built entirely on timing. The MCU starts a transaction by pulling the data line LOW for at least 18ms (the DHT11's own power-on reset threshold) then releasing it HIGH; the sensor responds by pulling the line LOW for ~80µs, HIGH for ~80µs, then transmits 40 bits (5 bytes: humidity integer, humidity decimal, temp integer, temp decimal, checksum) by varying the duration of each HIGH pulse after a fixed ~50µs LOW — a short ~26-28µs HIGH encodes a 0 bit, a long ~70µs HIGH encodes a 1 bit. The Arduino DHT library reads this by busy-polling digitalRead() in a tight loop and measuring elapsed microseconds with micros() (itself backed by a free-running hardware timer/cycle counter), which is why DHT reads must run with interrupts effectively uninterrupted — a Wi-Fi radio interrupt firing mid-transaction can shift the timing enough to misread a bit, which is the real mechanism behind DHT11's notorious flakiness on ESP8266/ESP32 versus a bare AVR.
The checksum byte (sum of the four data bytes, truncated to 8 bits) is your only integrity check on a bus with no CRC/parity at the hardware level — the library discards the whole reading rather than "correcting" it because a single misread bit anywhere in the 40 shifts the physical meaning of every subsequent bit, since sensor and MCU never resynchronize mid-frame.
(These examples were written and reasoned through at the register/protocol level but were not flashed to a physical board for this pass — verify timing-sensitive details against your exact chip datasheet before relying on them in production.)
Exercise¶
- Install the Adafruit
DHT sensor libraryand itsUnified Sensordependency, wire a DHT11 module to your chosen pin, and run the basic reading sketch. - Confirm the humidity/temperature values printed look plausible for your room (roughly 15–30°C and 20–70% RH indoors, in most climates).
- Disconnect the sensor's data wire briefly while the sketch runs and
confirm you see "Failed to read from DHT sensor!" rather than a garbage
numeric value — this proves the
isnan()guard is working. - Add the heat-index computation and log all three values (temperature, humidity, heat index) on one line.
- Swap in the retry-with-backoff version and, with the sensor properly connected, confirm it still reads successfully on the first attempt in the normal case (retries should only appear if you intentionally jostle the wiring).