If you are selecting an arduino temp humidity sensor for a new build, skip the obsolete DHT11 and choose between the BME280 (for I2C precision) and the DHT22/AM2302 (for cheap OneWire legacy projects). The BME280 offers vastly superior accuracy, faster read times, and includes barometric pressure, making it the definitive choice for 2026 environmental monitoring. However, the DHT22 remains useful when you are out of I2C addresses or need a simple, single-wire digital read.
This guide provides the exact wiring, pin mappings, and compilable C++ code to run both sensors simultaneously on an Arduino Nano v3, along with a bench-tested debugging framework for the most common I2C and OneWire failures.
Sensor Specifications: DHT22 vs BME280 vs Alternatives
Before wiring your breadboard, you need to understand the physical and protocol limitations of these sensors. The table below maps the real-world bench specifications for the four most common environmental sensors in the maker ecosystem.
| Sensor Model | Protocol | Temp Accuracy | Humidity Accuracy | Min Read Interval | Typical Price (2026) |
|---|---|---|---|---|---|
| BME280 (Bosch) | I2C / SPI | ±1.0°C | ±3% RH | 1 second | $4.00 - $9.00 |
| DHT22 (AM2302) | OneWire (Custom) | ±0.5°C | ±2% RH | 2 seconds | $2.50 - $5.00 |
| AHT20 (Aosong) | I2C | ±0.3°C | ±2% RH | 2 seconds | $1.50 - $3.00 |
| DHT11 (Legacy) | OneWire (Custom) | ±2.0°C | ±5% RH | 1 second | $1.00 - $2.00 |
Note: The DHT11 is included only for reference. Its 8-bit resolution and poor accuracy make it unsuitable for any serious environmental logging. Always use the DHT22 or BME280.
Hardware Wiring & Pin Mapping
This build targets the Arduino Nano v3 (ATmega328P), but the pinout is identical for the Arduino Uno R3. We are wiring a BME280 via I2C and a DHT22 via its custom single-bus protocol.
Parts List
- Microcontroller: Arduino Nano v3 (ATmega328P, 5V logic)
- I2C Sensor: BME280 Breakout (Adafruit 2652 or generic 5V-tolerant module with onboard 3.3V LDO and pull-ups)
- OneWire Sensor: DHT22 / AM2302 (White plastic housing, 4-pin)
- Resistor: 10kΩ through-hole (for DHT22 data line pull-up)
- Hardware: Half-size breadboard, male-to-male jumper wires
Pin Mapping Table
| Sensor Pin | Arduino Nano v3 Pin | Notes |
|---|---|---|
| BME280 VIN | 5V | Use 3.3V if your module lacks an LDO |
| BME280 GND | GND | Common ground required |
| BME280 SCL | A5 | I2C Clock (Nano/Uno specific) |
| BME280 SDA | A4 | I2C Data (Nano/Uno specific) |
| DHT22 VCC (Pin 1) | 5V | Left-most pin on sensor face |
| DHT22 DATA (Pin 2) | D4 | Requires 10kΩ pull-up to 5V |
| DHT22 NC (Pin 3) | Not Connected | Leave floating |
| DHT22 GND (Pin 4) | GND | Right-most pin on sensor face |
Compilable Code with Error Handling
The following C++ sketch initializes both sensors, handles I2C address variations for the BME280, and includes strict isnan() error checking for the DHT22. You will need to install the Adafruit BME280 Library, Adafruit Unified Sensor, and the DHT sensor library via the Arduino Library Manager.
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <DHT.h>
// --- PIN DEFINITIONS ---
#define DHT_PIN 4
#define DHT_TYPE DHT22
// BME280 I2C Addresses (0x76 is common on generic modules, 0x77 on Adafruit)
#define BME_ADDR_PRIMARY 0x76
#define BME_ADDR_SECONDARY 0x77
// --- OBJECT INSTANTIATION ---
Adafruit_BME280 bme;
DHT dht(DHT_PIN, DHT_TYPE);
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
Serial.println(F("Environmental Sensor Boot Sequence..."));
// Initialize DHT22
dht.begin();
Serial.println(F("DHT22 initialized on Pin 4."));
// Initialize BME280 with address fallback
Wire.begin();
unsigned status = bme.begin(BME_ADDR_PRIMARY);
if (!status) {
Serial.println(F("Primary I2C addr failed, trying secondary..."));
status = bme.begin(BME_ADDR_SECONDARY);
if (!status) {
Serial.println(F("ERROR: Failed to find BME280 chip. Check wiring."));
while (1) { delay(10); } // Halt execution
}
}
// Set BME280 sampling to 'Weather' preset for low power/high stability
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temp
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
Serial.println(F("BME280 initialized successfully."));
Serial.println(F("---------------------------"));
}
void loop() {
// --- BME280 READ ---
// Must call takeForcedMeasurement() in MODE_FORCED before reading
bme.takeForcedMeasurement();
float bme_temp = bme.readTemperature();
float bme_hum = bme.readHumidity();
Serial.print(F("BME280 -> Temp: ")); Serial.print(bme_temp); Serial.print(F(" C | Hum: ")); Serial.print(bme_hum); Serial.println(F(" %"));
// --- DHT22 READ ---
float dht_hum = dht.readHumidity();
float dht_temp = dht.readTemperature();
// Error Handling for DHT22
if (isnan(dht_hum) || isnan(dht_temp)) {
Serial.println(F("ERROR: DHT timeout error or checksum fail. Read aborted."));
} else {
Serial.print(F("DHT22 -> Temp: ")); Serial.print(dht_temp); Serial.print(F(" C | Hum: ")); Serial.print(dht_hum); Serial.println(F(" %"));
}
Serial.println(F("---------------------------"));
// DHT22 requires a minimum 2-second delay between reads
delay(2500);
}
Debugging: Fixing 'NaN' and Timeout Errors
When working with environmental sensors, you will inevitably encounter serial monitor errors. Below are the exact error strings generated by the libraries and the ranked causes for each.
Error 1: Failed to find BME280 chip
This means the Arduino's I2C bus cannot acknowledge the sensor. The Wire library timed out waiting for a response.
- Incorrect I2C Address: Bosch designed the BME280 with two possible addresses:
0x76and0x77. Adafruit uses 0x77; most generic AliExpress/Amazon modules use 0x76. The code above handles this via fallback, but if both fail, the chip isn't talking. - Missing Pull-up Resistors: I2C requires pull-up resistors on SDA and SCL. If your generic breakout board lacks them, and the Arduino Nano's internal pull-ups are too weak for the capacitance of your jumper wires, the bus will hang. Add external 4.7kΩ resistors to 3.3V.
- Logic Level Mismatch: If you are using a 3.3V BME280 module on a 5V Arduino Nano without a logic level converter, the 5V SDA/SCL signals might be back-feeding through the chip's protection diodes, locking up the I2C state machine.
Error 2: DHT timeout error (or Serial outputting NaN)
The DHT22 uses a custom, timing-critical single-bus protocol. The Arduino must bit-bang the data line with microsecond precision. If it misses a bit, the checksum fails, and the library returns NaN (Not a Number).
- Check the Delay: The DHT22 physically requires 2 seconds to sample the environment. If your
loop()runs faster than 2000ms, the sensor will return stale data or timeout. Ensuredelay(2500)is present. - Check the Pull-up Resistor: The DATA pin must have a 10kΩ resistor connected between it and VCC (5V). Without it, the line floats, and the Arduino reads garbage noise.
- Check for Interrupt Conflicts: The DHT library disables interrupts during the read sequence. If you have other libraries running (like
SoftwareSerialor fast PWM timers), they will corrupt the DHT timing. Move the DHT read to a quiet part of your loop.
Extending and Simplifying the Build
How to Extend This Project
Once you have stable serial output, the next logical step is to make the data actionable.
- Add an OLED Display: Wire an SSD1306 128x64 I2C OLED to the same A4/A5 pins. Use the
U8g2library to render the temperature. Because the BME280 and SSD1306 use different I2C addresses (0x76 and 0x3C), they will share the bus without conflict. - Upgrade to ESP32 for MQTT: If you want to push this data to Home Assistant, swap the Arduino Nano for an ESP32-WROOM-32. The pin mapping for I2C changes (SDA=GPIO21, SCL=GPIO22), but the C++ code remains 95% identical. Add the
PubSubClientlibrary to publish JSON payloads to your local broker.
How to Simplify the Build
If you are running into I2C address conflicts (e.g., you already have a sensor using 0x76 and 0x77) or you simply don't want to deal with the barometric pressure data, simplify by switching to the AHT20.
The AHT20 is an I2C temp/humidity sensor that defaults to address 0x38. It is cheaper than the BME280, highly accurate, and uses the standard Wire library without the complex oversampling configuration required by the Bosch chip. You can find the AHT20 integration details in the Adafruit AHT20 guide.
For more advanced I2C bus troubleshooting and pull-up resistor calculations, refer to the official Arduino Wire/I2C documentation. Understanding the physical layer of your sensors is the difference between a project that works on the bench and one that survives in the field.






