If you are building an environmental monitor, the best arduino humidity sensor for budget DIY projects is the DHT22 (AM2302), while the Sensirion SHT31-D is the definitive choice for precision I2C applications. Both measure relative humidity (RH) using capacitive polymer sensing, but they differ drastically in bus protocols, timing tolerances, and failure modes. This guide provides exact wiring, a non-blocking C++ sketch targeting the ATmega328P, and a deep-dive into debugging the infamous NaN errors that plague single-bus sensors.
Choosing Your Arduino Humidity Sensor: DHT22 vs SHT31 vs BME280
Capacitive humidity sensors work by absorbing water vapor into a polymer dielectric layer, which changes the capacitance between two electrodes. The microcontroller measures this capacitance shift and converts it to %RH. However, not all capacitive sensors are created equal. Below is a data-dense comparison to help you select the right module for your bench or field deployment.
| Sensor Module | Humidity Accuracy | Temp Accuracy | Interface Protocol | Avg. Price (2026) | Best Use Case |
|---|---|---|---|---|---|
| DHT11 | ±5% RH | ±2.0°C | Custom 1-Bus | $1.50 | Basic classroom demos |
| DHT22 (AM2302) | ±2% RH | ±0.5°C | Custom 1-Bus | $4.00 | Budget indoor weather stations |
| SHT31-D | ±2% RH | ±0.3°C | I2C (0x44/0x45) | $6.50 | Precision greenhouse / lab logging |
| BME280 | ±3% RH | ±1.0°C | I2C / SPI | $5.00 | Altitude + weather (includes barometric) |
Expert Insight: The DHT22 uses a proprietary single-bus protocol that requires strict microsecond timing. If your Arduino handles other interrupts (like software serial or heavy PWM), the DHT22 read will fail. The SHT31-D uses standard I2C, offloading timing constraints to the hardware TWI peripheral, making it vastly superior for complex sketches. Read the Sensirion SHT31-D Datasheet for deep CMOSens architecture details.
Hardware BOM and Pin Mapping
For this build, we are targeting the Arduino Uno R3 (or any ATmega328P-based board like the Nano v3). We will wire the DHT22 as the primary example, as its single-bus protocol requires explicit pull-up resistors and precise pin definitions that often trip up beginners.
Bill of Materials (BOM)
- MCU: Arduino Uno R3 (Rev3) or Arduino Nano v3 (ATmega328P)
- Sensor: DHT22 / AM2302 on a 3-pin breakout board (includes onboard pull-up)
- Resistor: 4.7kΩ through-hole (only required if using a bare 4-pin DHT22 without a breakout)
- Wiring: 22 AWG solid core jumper wires
- Library:
DHT sensor libraryby Adafruit (v1.4.6+ via Library Manager)
Pin Mapping Table
| DHT22 Breakout Pin | Arduino Uno R3 Pin | Wire Color (Typical) | Electrical Notes |
|---|---|---|---|
| VCC (or +) | 5V | Red | Accepts 3.3V to 5.5V. Use 5V for longer wire runs. |
| DATA (or OUT) | Digital Pin 2 | Yellow | Must have 4.7kΩ pull-up to VCC if not on breakout. |
| GND (or -) | GND | Black | Common ground with MCU. |
Step-by-Step Wiring Procedure
- De-energize the board: Unplug the USB cable from your Arduino Uno R3.
- Connect Power: Route a red jumper from the Arduino
5Vpin to the breadboard's positive rail, and a black jumper fromGNDto the negative rail. - Wire the Sensor Power: Connect the DHT22 breakout
VCCto the positive rail, andGNDto the negative rail. - Wire the Data Line: Connect a yellow jumper from the DHT22
DATApin directly to Arduino Digital Pin 2. - Verify Pull-Up Resistance: If you are using a bare 4-pin DHT22 (not a 3-pin breakout), insert a 4.7kΩ resistor between the
DATApin and theVCCpin. Breakout boards usually have a 10kΩ or 4.7kΩ SMD resistor pre-soldered; verify this with your multimeter in continuity/resistance mode. - Inspect and Power Up: Check for stray wire strands that could short Pin 2 to 5V. Plug the USB cable back in.
Compilable C++ Code with Error Handling
Beginners often use delay(2000) to poll the DHT22, which blocks the MCU from doing anything else (like updating a display or listening for serial commands). The sketch below uses a non-blocking millis() timer. It explicitly defines pins, handles the NaN (Not a Number) error state, and targets the Uno R3. For more on non-blocking timing, see the Arduino Time and Millis Documentation.
#include <DHT.h>
// --- PIN DEFINITIONS & CONFIG ---
#define DHTPIN 2 // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22 // Sensor type: DHT11, DHT22 (AM2302), or DHT21
#define POLL_INTERVAL 2500 // DHT22 requires minimum 2000ms between reads
// Instantiate the DHT object
DHT dht(DHTPIN, DHTTYPE);
unsigned long lastReadTime = 0;
void setup() {
Serial.begin(115200);
while (!Serial) {
; // Wait for serial port to connect (needed for native USB boards)
}
Serial.println(F("DHT22 Non-Blocking Sketch - ElectricalFlux"));
// Initialize the sensor (sets pin modes and waits for startup)
dht.begin();
}
void loop() {
unsigned long currentMillis = millis();
// Non-blocking timer check
if (currentMillis - lastReadTime >= POLL_INTERVAL) {
lastReadTime = currentMillis;
// Reading temperature or humidity takes about 250ms
float h = dht.readHumidity();
float t = dht.readTemperature(); // Celsius by default
float f = dht.readTemperature(true); // Fahrenheit
// --- ERROR HANDLING ---
// Check if any reads failed and exit early (to try again)
if (isnan(h) || isnan(t) || isnan(f)) {
Serial.print(F("ERROR: Failed to read from DHT sensor! Time: "));
Serial.println(currentMillis);
return; // Skip the rest of the loop and try again next interval
}
// Compute heat index (Fahrenheit)
float hif = dht.computeHeatIndex(f, h);
// --- OUTPUT DATA ---
Serial.print(F("Humidity: "));
Serial.print(h, 1);
Serial.print(F("% | Temp: "));
Serial.print(t, 1);
Serial.print(F("°C / "));
Serial.print(f, 1);
Serial.print(F("°F | Heat Index: "));
Serial.print(hif, 1);
Serial.println(F("°F"));
}
// You can run other non-blocking tasks here (e.g., blinking an LED, checking buttons)
}
Debugging: Fixing "NaN" and Timeout Errors
The most common failure mode when deploying an arduino humidity sensor is the serial monitor spamming ERROR: Failed to read from DHT sensor! or outputting nan for the float values. This happens when the MCU's microsecond timing is interrupted, or the bus voltage sags, causing the 40-bit data packet checksum to fail.
The First 3 Things to Check When It Fails
- Measure VCC at the Sensor Pins: Do not assume the Arduino 5V rail is exactly 5.0V. Use a multimeter to measure directly across the sensor's VCC and GND pins. If it reads below 4.8V (often caused by a high-resistance breadboard or a long, thin USB cable), the sensor's internal oscillator will drift, causing timing mismatches. Fix: Use a shorter, high-quality USB cable or an external 5V power supply.
- Verify the Pull-Up Resistor: The single-bus protocol requires the data line to be pulled HIGH when idle. If you are using a bare sensor without a breakout board, and forgot the 4.7kΩ resistor, the line will float. Fix: Measure resistance between DATA and VCC with power OFF. It should read ~4.7kΩ to 10kΩ.
- Check Polling Frequency: The DHT22 datasheet mandates a minimum 2-second polling interval. If your
millis()interval ordelay()is set to 1000ms, the sensor will ignore the start signal and return a timeout. Fix: EnsurePOLL_INTERVALis ≥ 2500ms.
Ranked Causes for the Exact Error String: Failed to read from DHT sensor!
| Rank | Root Cause | Technical Explanation | Solution |
|---|---|---|---|
| 1 | Interrupt Collision | The DHT library disables interrupts to read the 40 bits. If a high-priority interrupt (like SoftwareSerial or a fast timer) forces its way in, the bitstream is corrupted and the checksum fails, returning NaN. |
Move to an I2C sensor (SHT31) or minimize interrupt-heavy libraries in your sketch. |
| 2 | Missing Pull-Up Resistor | The open-drain output cannot pull the line HIGH fast enough to meet the 20-40µs timing windows without a physical resistor. | Solder a 4.7kΩ resistor between DATA and VCC. |
| 3 | Sensor Saturation / Condensation | If the sensor hits 100% RH or gets wet, the polymer dielectric saturates. It will read NaN or peg at 99.9% until dried. |
Bake the sensor at 60°C for 2 hours to evaporate trapped moisture. |
| 4 | Incorrect Sensor Type Defined | Defining DHTTYPE DHT11 when physically using a DHT22 changes the expected bit-length and decimal parsing in the library. |
Ensure #define DHTTYPE DHT22 matches your physical hardware. |
Extending and Simplifying the Build
Once you have stable readings, you will likely want to integrate this data into a broader IoT ecosystem or simplify the hardware for production.
How to Extend the Build (IoT & Logging)
- Add MQTT over WiFi: Swap the Arduino Uno R3 for an ESP32-DevKitC V4. The ESP32 handles the DHT22 timing interrupts much better due to its RTOS architecture (though you must add
yield()in your loop). Use thePubSubClientlibrary to publish the %RH and temperature payloads to a Mosquitto broker or Home Assistant. - Add Local Logging: Wire a 128x64 I2C OLED (SSD1306) to the A4/A5 pins. Because the OLED uses I2C and the DHT22 uses a custom 1-bus, they will not interfere with each other on the bus, making this a highly stable combination.
How to Simplify the Build (The I2C Upgrade)
If you are tired of debugging NaN errors and managing microsecond timing, simplify your build by replacing the DHT22 with a Sensirion SHT31-D.
By switching to I2C, you eliminate the need for pull-up resistor calculations, remove the strict 2-second polling bottleneck (the SHT31 can be polled on-demand), and free up the MCU's interrupt flags. You simply wire SDA to A4, SCL to A5, and use the Adafruit SHT31 library. The hardware cost increases by roughly $2.50, but the reduction in firmware debugging time pays for itself immediately on the bench.






