If you are building a humidity sensor Arduino project, your first and most critical decision is which sensor module to buy. The ubiquitous DHT11 and DHT22 sensors are famous for throwing NaN (Not a Number) errors and failing when Wi-Fi interrupts fire on ESP boards. For any build requiring reliable, continuous data logging, the decision-forward choice is the Sensirion SHT31-D. It uses the I2C protocol, eliminating the microsecond-level timing vulnerabilities of the DHT series, and provides ±2% RH accuracy.
This guide walks through the hardware decision matrix, provides exact wiring for the Arduino Uno R3, delivers robust compilable code with error handling, and details the bench-level debugging steps for when your I2C bus throws NACK errors.
The Humidity Sensor Arduino Decision Matrix
Before wiring anything, use this decision tree to select the right sensor for your specific environmental constraints. We evaluate the four most common hobbyist modules based on protocol, accuracy, and interrupt sensitivity.
| Sensor Module | Protocol | RH Accuracy | Interrupt Safe? | Typical Price (2026) |
|---|---|---|---|---|
| DHT11 | Custom 1-Wire | ±5% | No (Fails with Wi-Fi) | $1.50 |
| DHT22 (AM2302) | Custom 1-Wire | ±2% | No (Fails with Wi-Fi) | $4.50 |
| AHT20 | I2C | ±2% | Yes | $2.50 |
| SHT31-D | I2C | ±2% (High Stability) | Yes | $14.95 |
Parts List and Pin Mapping for the SHT31-D
This build targets the classic Arduino Uno R3 (ATmega328P) operating at 5V. The Adafruit SHT31-D breakout includes an onboard 3.3V LDO regulator and I2C level shifters, making it directly compatible with 5V logic without frying the silicon.
Required Hardware
- Microcontroller: Arduino Uno R3 (or compatible ATmega328P clone)
- Sensor: Adafruit SHT31-D Temperature & Humidity Breakout (PID 2857)
- Wiring: 4x Male-to-Male jumper wires (keep under 30cm to avoid I2C capacitance issues)
- Breadboard: Standard 830-point solderless breadboard
Pin Mapping Table
| SHT31-D Breakout Pin | Arduino Uno R3 Pin | Function / Notes |
|---|---|---|
| VIN | 5V | Powers the onboard LDO and level shifters |
| GND | GND | Common ground reference |
| SDA | A4 | I2C Data (Includes onboard 10k pull-ups) |
| SCL | A5 | I2C Clock |
| ADR | Not Connected | Leave floating for default I2C address 0x44 |
Step-by-Step Wiring and Compilable Code
Wiring Procedure
- De-energize the board: Ensure the Arduino Uno is unplugged from USB before making I2C connections.
- Power the breakout: Connect the breakout
VINto the Arduino5Vpin, andGNDtoGND. - Route the I2C bus: Connect
SDAtoA4andSCLtoA5. Keep these wires parallel and under 30cm (12 inches) to prevent bus capacitance from exceeding the 400pF I2C limit. - Verify Address: Ensure the
ADR(Address) pad on the breakout is not bridged with solder. This keeps the default I2C address at0x44.
Compilable Arduino Code
You will need the Adafruit SHT31 and Adafruit Unified Sensor libraries installed via the Arduino Library Manager. This code includes robust initialization checks and read-state error handling to prevent the sketch from hanging if the sensor drops off the bus.
#include <Wire.h>
#include "Adafruit_SHT31.h"
// Target: Arduino Uno R3 (ATmega328P)
// I2C Pins: SDA = A4, SCL = A5
// Initialize sensor object
Adafruit_SHT31 sht31 = Adafruit_SHT31();
// Track last successful read to prevent serial spam on failure
unsigned long lastReadTime = 0;
const unsigned long READ_INTERVAL = 2000; // Read every 2 seconds
void setup() {
Serial.begin(115200);
// Wait for serial port to connect (useful for Leonardo/Micro, harmless on Uno)
while (!Serial) delay(10);
Serial.println("SHT31-D I2C Humidity Sensor Test");
// Initialize I2C bus
Wire.begin();
// Attempt to initialize the sensor at default address 0x44
if (!sht31.begin(0x44)) {
Serial.println("CRITICAL ERROR: Couldn't find SHT31-D at 0x44");
Serial.println("Check I2C wiring, pull-up resistors, and ADR pin state.");
// Halt execution to prevent infinite error loops
while (1) {
delay(1000);
}
}
Serial.println("Sensor initialized successfully.");
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - lastReadTime >= READ_INTERVAL) {
lastReadTime = currentMillis;
// Read temperature and humidity
float tempC = sht31.readTemperature();
float humidity = sht31.readHumidity();
// Error Handling: Check for NaN (Not a Number) returns
if (isnan(tempC) || isnan(humidity)) {
Serial.println("ERROR: Failed to read SHT31 data. I2C NACK received.");
// Optional: Attempt I2C bus recovery by re-initializing Wire
Wire.end();
delay(50);
Wire.begin();
} else {
// Calculate Fahrenheit for US users
float tempF = (tempC * 1.8) + 32;
Serial.print("Temp: ");
Serial.print(tempC);
Serial.print(" C (");
Serial.print(tempF);
Serial.print(" F) | Humidity: ");
Serial.print(humidity);
Serial.println(" %");
}
}
}
Debugging: Fixing Timeouts and "NaN" Errors
When working with environmental sensors, the serial monitor will inevitably throw errors. Here is how to diagnose the exact failure modes based on the error strings returned by the code or the hardware behavior.
Error 1: "ERROR: Failed to read SHT31 data. I2C NACK received."
This means the Arduino sent a clock pulse, but the SHT31-D did not acknowledge (NACK) the transaction. The sensor is physically connected but logically invisible.
- Cause A (Most Likely): Missing or weak pull-up resistors. The Adafruit breakout includes 10kΩ pull-ups. If you are using a bare SHT31 chip or a cheap clone board without pull-ups, the I2C lines will float. Fix: Solder 4.7kΩ resistors between SDA-VCC and SCL-VCC.
- Cause B: Bus Capacitance Overload. If your jumper wires exceed 30cm, the parasitic capacitance of the wire exceeds the 400pF I2C spec, rounding off the square clock waves into unreadable slopes. Fix: Shorten wires, or drop the I2C clock speed by adding
Wire.setClock(50000);afterWire.begin();.
Error 2: Legacy DHT Sensors returning "NaN"
If you ignored the decision matrix and are using a DHT22, you will frequently see NaN in your serial output. The DHT protocol requires the MCU to time pulse widths with microsecond precision. If a hardware interrupt (like a timer or Wi-Fi stack) fires during the 40-bit read window, the timing is ruined, and the library returns NaN.
- Cause A: Wi-Fi Interrupt Starvation. On ESP8266/ESP32 boards, background RF calibration interrupts block the DHT read. Fix: Switch to an I2C sensor (SHT31/AHT20), or use the
yield()function aggressively between reads. - Cause B: Insufficient Power Delivery. The DHT22 draws up to 1.5mA during conversion. If powered from a weak 3.3V LDO on a clone ESP board, voltage sags cause bit-flips. Fix: Power the DHT22 from the 5V pin (if it has an onboard LDO) or add a 100nF decoupling capacitor directly across the VCC and GND pins of the sensor.
The First 3 Things to Check When Any Sensor Fails
- Run an I2C Scanner: Upload the standard Arduino "I2C Scanner" sketch. If the SHT31 doesn't show up at
0x44(or0x45), you have a physical wiring or pull-up issue, not a code issue. - Measure VCC at the Breakout: Use a multimeter to probe the VIN and GND pins on the breadboard. You should read 4.8V to 5.2V. If it reads 3.3V, you are underpowering the onboard level shifters.
- Check for Address Collisions: If you have multiple I2C devices on the bus (e.g., an OLED display and the SHT31), ensure they don't share the same hex address. Use the ADR pad to shift the SHT31 to
0x45if necessary.
Extending and Simplifying Your Build
Once your baseline humidity sensor Arduino circuit is logging reliably to the serial monitor, you can scale the project for real-world deployment.
Extension: Adding SD Card Data Logging
To log data for greenhouse or server-room monitoring, add a MicroSD card breakout. Wire the SD breakout using the SPI bus (Pins 10-13 on the Uno R3), leaving the I2C bus (A4/A5) exclusively for the SHT31-D. Use the SdFat library rather than the stock SD library for better memory management and faster write times, which prevents I2C read delays while the SPI bus is writing to the card.
Simplification: Migrating to ESP32 for MQTT
If your end goal is pushing humidity data to Home Assistant via MQTT, drop the Arduino Uno R3 and migrate to an ESP32-DevKitC V4. The ESP32 natively supports 3.3V logic, meaning you can bypass the level-shifters and wire a bare SHT31 chip or 3.3V breakout directly to GPIO 21 (SDA) and GPIO 22 (SCL). Use the PubSubClient library to publish the humidity and temperature floats as JSON payloads to your local Mosquitto broker.
For deeper technical specifications on I2C timing and sensor calibration, refer to the Adafruit SHT31-D Guide and the official Arduino Wire Library Documentation. Understanding the physical layer of your I2C bus is the difference between a sensor that works on the bench and one that survives in the field.






