If you are building environmental monitors, greenhouse controllers, or HVAC diagnostics, the classic DHT11 and DHT22 sensors will eventually frustrate you with dropped reads and timing locks. For a modern, reliable temp humidity sensor Arduino project, the Sensirion SHT31-D is the benchmark. It uses the I2C protocol, features a true hardware CRC (Cyclic Redundancy Check) for data validation, and offers ±0.2°C temperature accuracy.
This guide walks through wiring the SHT31 to an Arduino Uno R4 Minima, provides production-ready code with error handling, and details the exact debugging steps when your I2C bus throws faults.
Choosing the Right Temp Humidity Sensor for Arduino
Before wiring anything, it is critical to understand why we are moving away from single-bus sensors. The DHT series relies on strict microsecond timing to read data. If an interrupt fires on your Arduino (like a Wi-Fi stack ticking on an ESP32, or a servo timer), the DHT read fails. I2C sensors like the SHT31 and AHT20 offload the timing to a hardware peripheral, freeing your main loop.
| Sensor Model | Protocol | Temp Accuracy | RH Accuracy | Response Time (Tau) | Typical Price (2026) |
|---|---|---|---|---|---|
| DHT11 | Single-Bus | ±2.0°C | ±5% RH | ~15s | $2 - $4 |
| DHT22 (AM2302) | Single-Bus | ±0.5°C | ±2% RH | ~8s | $6 - $9 |
| AHT20 | I2C | ±0.3°C | ±2% RH | ~4s | $4 - $6 |
| Sensirion SHT31-D | I2C | ±0.2°C | ±2% RH | ~2s | $12 - $16 |
Parts List and Pin Mapping for SHT31 on Uno R4
This build targets the Arduino Uno R4 Minima. The R4 architecture features a Renesas RA4M1 ARM Cortex-M4 microcontroller, which handles I2C clock stretching natively—something older AVR chips struggled with on the SHT3x series.
Bill of Materials
- MCU: Arduino Uno R4 Minima (approx. $28.00)
- Sensor: Adafruit SHT31-D Temperature & Humidity Breakout (approx. $14.50) or generic SHT31 module with onboard pull-ups.
- Wiring: 4x stranded silicone jumper wires (26 AWG).
- Prototyping: Half-size breadboard.
Pin Mapping Table
The Adafruit SHT31 breakout includes 10kΩ I2C pull-up resistors to 3.3V. If you are using a bare, ultra-cheap generic SHT31 module from AliExpress, you must add external 4.7kΩ pull-up resistors between SDA/SCL and 3.3V, or the bus will float and fail to initialize.
| SHT31 Breakout Pin | Arduino Uno R4 Minima Pin | Wire Color (Standard) | Notes |
|---|---|---|---|
| VIN | 5V | Red | Breakout regulates down to 3.3V internally. |
| GND | GND | Black | Common ground is mandatory for I2C reference. |
| SCL | SCL (D19 / Header) | Green | I2C Clock line. |
| SDA | SDA (D18 / Header) | Yellow | I2C Data line. |
| ADDR | Leave Unconnected (or GND) | N/A | Ties address to 0x44. Tie to 5V for 0x45. |
Compilable I2C Code with CRC Error Handling
The following C++ code is written for the Arduino IDE (2.x or legacy 1.8.x). It utilizes the Adafruit_SHT31 library, which abstracts the I2C register reads and handles the CRC-8 validation natively. If the CRC fails (data corrupted on the wire), the library returns NaN (Not a Number) rather than garbage data.
Prerequisites: Install the Adafruit SHT31 Library and the Adafruit BusIO dependency via the Arduino Library Manager before compiling.
#include <Wire.h>
#include "Adafruit_SHT31.h"
// --- PIN & CONFIGURATION DEFINITIONS ---
// Arduino Uno R4 Minima I2C pins are fixed to SDA (A4/D18) and SCL (A5/D19)
#define I2C_SDA_PIN A4
#define I2C_SCL_PIN A5
#define SENSOR_I2C_ADDR 0x44 // Default address when ADDR pin is unconnected/GND
// Update interval in milliseconds (2 seconds is safe for SHT31 high repeatability)
#define READ_INTERVAL_MS 2000
unsigned long lastReadTime = 0;
// Initialize the sensor object
Adafruit_SHT31 sht31 = Adafruit_SHT31();
void setup() {
Serial.begin(115200);
// Wait for serial port to connect (useful for native USB boards like Uno R4)
while (!Serial) {
delay(10);
}
Serial.println("SHT31 Temp & Humidity Sensor - I2C Initialization");
// Explicitly pass the Wire object and pins to ensure correct I2C bus routing
Wire.setSCL(I2C_SCL_PIN);
Wire.setSDA(I2C_SDA_PIN);
if (!sht31.begin(SENSOR_I2C_ADDR)) {
Serial.println("CRITICAL ERROR: Couldn't find SHT31 sensor at address 0x44");
Serial.println("Check I2C wiring, pull-up resistors, and the ADDR pin state.");
while (1) {
// Halt execution to prevent bus hammering
delay(1000);
}
}
Serial.println("Sensor initialized successfully. Starting readings...");
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - lastReadTime >= READ_INTERVAL_MS) {
lastReadTime = currentMillis;
float tempC = sht31.readTemperature();
float relHum = sht31.readHumidity();
// Error Handling: The library returns NaN if the I2C read or CRC check fails
if (isnan(tempC) || isnan(relHum)) {
Serial.println("ERROR: Failed to read sensor data! Check I2C bus integrity.");
// Optional: Implement a software I2C bus reset here if faults persist
// Wire.end(); delay(50); Wire.begin(); sht31.begin(SENSOR_I2C_ADDR);
} else {
// Convert to Fahrenheit for US-based HVAC applications
float tempF = tempC * 1.8 + 32;
Serial.print("Temp: ");
Serial.print(tempC, 2); Serial.print(" °C | ");
Serial.print(tempF, 2); Serial.print(" °F || ");
Serial.print("Humidity: ");
Serial.print(relHum, 2); Serial.println(" % RH");
}
}
}
Debugging: "Failed to Read" and I2C Faults
When working with I2C environmental sensors, the physical layer is almost always the culprit. If your Serial Monitor outputs "CRITICAL ERROR: Couldn't find SHT31 sensor at address 0x44" or repeatedly prints "ERROR: Failed to read sensor data! Check I2C bus integrity." (yielding NaN values), follow this ranked troubleshooting sequence.
The First Three Things to Check
- Run an I2C Scanner Sketch: Before blaming the sensor, flash a standard I2C Scanner script (File > Examples > Wire > I2CScanner). If the scanner returns
"No I2C devices found", your SDA/SCL lines are swapped, broken, or missing pull-up resistors. If it returns0x44, the hardware bus is healthy, and the issue is likely library-related or a timing fault. - Measure VCC Under Load: Use a multimeter to probe the VIN and GND pins directly on the sensor breakout while the circuit is powered. Cheap USB cables suffer from voltage drop. If your Arduino 5V pin is only delivering 4.2V, the onboard 3.3V LDO on the sensor breakout will drop out, causing the SHT31 to brownout and fail its internal CRC checks.
- Inspect the ADDR Pin State: The SHT31 has two possible I2C addresses:
0x44(ADDR tied to GND or floating) and0x45(ADDR tied to VCC). If your breakout board has a physical jumper or trace that defaults the ADDR pin high, the code above will fail to initialize. Cut the trace or change#define SENSOR_I2C_ADDR 0x44to0x45.
Ranked Causes for Intermittent NaN Errors
If the sensor initializes but randomly drops reads (returning NaN in the Serial Monitor), rank your troubleshooting by these common faults:
- Cause 1: I2C Clock Stretching Timeout. The SHT31 holds the SCL line low while it performs the internal analog-to-digital conversion (up to 15ms in high-repeatability mode). If your Wire library's I2C timeout is set too low (common in older ESP8266 cores), it will abort the read. Fix: The Uno R4 handles this natively, but if porting to ESP32, ensure your I2C timeout is > 50ms.
- Cause 2: Capacitance on the I2C Bus. If you are using long, unshielded ribbon cables (>30cm) between the Arduino and the sensor, the bus capacitance exceeds the I2C spec (400pF), rounding off the square wave edges. Fix: Lower the I2C clock speed to 100kHz using
Wire.setClock(100000);in your setup function. - Cause 3: Thermal Self-Heating. If you poll the sensor every 100ms, the internal heater and I2C logic will raise the die temperature, skewing your readings by 0.5°C or more. Fix: Stick to the 2000ms polling interval defined in the code above.
Extending the Build: Wi-Fi Logging and Power Optimization
Once you have verified stable readings on the bench, you will likely want to deploy this node into the field. Here is how to extend or simplify the architecture based on your end goal.
Upgrading to Wi-Fi (ESP32 Swap)
The Uno R4 Minima is excellent for local displays and actuator control, but it lacks native Wi-Fi. To push this data to an MQTT broker or a Home Assistant instance, swap the MCU for an ESP32-C3 SuperMini or an Arduino Nano ESP32.
When migrating the code to the ESP32 Arduino Core, remember that the ESP32's I2C pins are not fixed. You must explicitly define them in the setup function:
Wire.begin(21, 22); // SDA on GPIO 21, SCL on GPIO 22
Simplifying for Low-Power Battery Nodes
If you are powering this temp humidity sensor Arduino build from a 3.7V LiPo cell via a solar charge controller, power budget is everything. The SHT31 draws roughly 1mA during a measurement, but the onboard LDO and I2C pull-ups on commercial breakouts will draw a continuous 2-5mA quiescent current.
To achieve true microamp deep-sleep currents:
- Remove the power LED from the breakout board using a hot air rework station or flush cutters.
- Do not use the
VINpin. Instead, power the3V3pin directly from your LiPo battery (bypassing the LDO entirely). - Use the ESP32's deep sleep features, powering the sensor via a GPIO-controlled MOSFET so the sensor is completely depowered between the 15-minute reading intervals.
For deeper technical specifications on the sensor's internal timing and heater registers, refer to the official Sensirion SHT31-DIS datasheet. For pinout diagrams and schematic references for the microcontroller used in this guide, consult the Arduino Uno R4 Minima documentation. If you are using the Adafruit ecosystem, their SHT31 breakout guide provides excellent Fritzing diagrams and Python/C++ library nuances.






