The Verdict: Which Weather Sensors for Arduino Should You Buy?
If you are building an environmental monitor, the market is flooded with cheap temperature and humidity modules. Most hobbyists start with a DHT11 or DHT22, hit a wall with timing errors and poor accuracy, and eventually tear the breadboard apart to start over. To save you that weekend, here is the direct answer: buy the Bosch BME280 on an I2C breakout board. It provides temperature, humidity, and barometric pressure over a robust I2C bus, completely avoiding the microsecond-timing nightmares of single-wire protocols.
Use the decision tree below to confirm this is the right pick for your specific constraints, or to find the exact alternative if your budget or use case demands it.
| Sensor Module | Protocol | Accuracy (Temp / RH) | Best Use Case | Major Gotcha |
|---|---|---|---|---|
| Bosch BME280 | I2C / SPI | ±1.0°C / ±3% RH | Indoor/outdoor stations, altitude tracking, reliable data logging. | Raw 3.3V modules lack pull-ups; requires a proper breakout board. |
| Aosong DHT22 (AM2302) | Single-Wire | ±0.5°C / ±2% RH | Low-budget greenhouses where polling once per second is acceptable. | Blocking code; strict 2-second polling limit; high failure rate on ESP32/RTOS. |
| ASAIR AHT20 | I2C | ±0.3°C / ±2% RH | Strictly indoor climate control where pressure data isn't needed. | No barometric pressure; I2C address (0x38) often conflicts with other displays. |
| Sensirion SHT31-D | I2C | ±0.3°C / ±2% RH | High-precision lab environments, medical storage monitoring. | Overkill for hobby weather stations; 2x the cost of a BME280. |
| Decision Path Default | Final Pick: Adafruit BME280 I2C/SPI Breakout (Product ID 2652). Includes 3.3V regulator, 4.7kΩ pull-ups, and 5V logic tolerance. | |||
Parts List & Spec Sheet
This build targets the Arduino Nano v3 (ATmega328P). We use the Nano instead of the Uno R3 to keep the physical footprint small for enclosure mounting, while retaining the exact same ATmega328P architecture and 5V logic levels.
Required Components
- Microcontroller: Arduino Nano v3 (ATmega328P, 16MHz, 5V logic)
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID 2652)
- Wiring: 22 AWG solid-core hook-up wire (4 colors: Red, Black, Blue, Yellow)
- Prototyping: Half-size solderless breadboard
- Software Libraries:
Adafruit BME280 LibraryandAdafruit Unified Sensor(install via Arduino Library Manager)
Pin Mapping Table
| BME280 Breakout Pin | Arduino Nano v3 Pin | Wire Color | Function / Notes |
|---|---|---|---|
| VIN | 5V | Red | Powers the onboard 3.3V regulator on the breakout. |
| GND | GND | Black | Common ground reference. |
| SCK / SCL | A5 | Blue | I2C Clock line. Breakout includes 4.7kΩ pull-up. |
| SDI / SDA | A4 | Yellow | I2C Data line. Breakout includes 4.7kΩ pull-up. |
For deeper electrical specifications on the I2C bus capacitance and pull-up requirements, refer to the official NXP I2C-bus specification.
Step-by-Step Wiring & Assembly
- De-energize the bus: Ensure the Arduino Nano is unplugged from USB before inserting it into the breadboard.
- Seat the Nano: Straddle the Nano across the center trench of the breadboard. Ensure pins on both sides are fully inserted.
- Wire Power: Connect Nano
5Vto the breadboard's positive (+) rail, and NanoGNDto the negative (-) rail. - Connect the Sensor: Route Red (5V) and Black (GND) from the power rails to the BME280
VINandGNDpins. - Route I2C Lines: Connect Nano
A5to BME280SCK/SCL, and NanoA4to BME280SDI/SDA. - Verify Address Jumper: Look at the back of the Adafruit breakout. By default, the I2C address is
0x77. If you are using multiple BME280s or have an address conflict, you will need to bridge the address jumper pad with a solder iron to change it to0x76. - Inspect Connections: Tug gently on each wire. Solderless breadboards suffer from intermittent contact; a loose SDA wire will result in a hung I2C bus.
Complete Arduino Code with Error Handling
The following code targets the Arduino Nano v3 (ATmega328P). It initializes the I2C bus, verifies the sensor's presence, and halts execution with a clear serial message if the hardware is missing. It also includes a runtime check for NaN (Not a Number) values, which can occur if the I2C bus experiences a brownout or noise spike during a read cycle.
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
// Hardware pin definitions (Arduino Nano v3 I2C pins)
#define I2C_SDA_PIN A4
#define I2C_SCL_PIN A5
// Standard sea level pressure for altitude calculation (hPa)
#define SEALEVELPRESSURE_HPA (1013.25)
// Instantiate the sensor object
Adafruit_BME280 bme;
// Track time for non-blocking polling (2-second interval)
unsigned long lastReadTime = 0;
const unsigned long readInterval = 2000;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10); // Wait for serial port to connect (needed for native USB boards)
}
Serial.println(F("BME280 Weather Sensor Initialization..."));
// Initialize I2C with explicit pin mapping for clarity
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
Wire.setClock(100000); // Set I2C to standard 100kHz mode
// Attempt to start the sensor on default I2C address (0x77)
// If using 0x76, change to: bme.begin(0x76)
if (!bme.begin(0x77)) {
Serial.println(F("ERROR: Could not find a valid BME280 sensor, check wiring!"));
Serial.println(F("Halting execution. Reset board to try again."));
Serial.flush();
while (1) {
// Blink built-in LED rapidly to indicate fatal hardware fault
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
}
Serial.println(F("BME280 initialized successfully."));
Serial.println(F("-----------------------------------"));
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - lastReadTime >= readInterval) {
lastReadTime = currentMillis;
// Read values into local variables
float tempC = bme.readTemperature();
float pressureHpa = bme.readPressure() / 100.0F;
float humidity = bme.readHumidity();
float altitudeM = bme.readAltitude(SEALEVELPRESSURE_HPA);
// Runtime error handling: Check for I2C bus read failures (NaN)
if (isnan(tempC) || isnan(pressureHpa) || isnan(humidity)) {
Serial.println(F("WARN: Sensor read failed (NaN). Check I2C pull-ups."));
return; // Skip this loop iteration
}
// Format and print data to Serial Monitor
Serial.print(F("Temp: ")); Serial.print(tempC); Serial.print(F(" C | "));
Serial.print(F("Hum: ")); Serial.print(humidity); Serial.print(F(" % | "));
Serial.print(F("Press: ")); Serial.print(pressureHpa); Serial.print(F(" hPa | "));
Serial.print(F("Alt: ")); Serial.print(altitudeM); Serial.println(F(" m"));
}
}
For more details on how the underlying Wire library handles I2C clock stretching and timeouts, review the official Arduino Wire reference.
Debugging: "Could not find a valid BME280 sensor"
If your serial monitor outputs the exact string ERROR: Could not find a valid BME280 sensor, check wiring! and the onboard LED starts rapid-blinking, the microcontroller cannot see the sensor on the I2C bus. Do not immediately assume the sensor is dead. Follow this ranked troubleshooting path:
1. I2C Address Mismatch (Most Common)
The BME280 supports two I2C addresses: 0x77 (default on Adafruit) and 0x76 (default on most generic Amazon/AliExpress breakouts). The code above looks for 0x77. If you are using a generic board, change bme.begin(0x77) to bme.begin(0x76) in the setup function.
Verify: Run the standard Arduino I2C_Scanner sketch. If it returns 0x76, update your main sketch accordingly.
2. Missing Pull-Up Resistors on SDA/SCL
I2C is an open-drain protocol. The lines must be pulled high to VCC via resistors. If you wired a raw BME280 chip or a cheap module lacking onboard resistors, the SDA and SCL lines will float, causing the bme.begin() handshake to fail.
Fix: Solder or breadboard two 4.7kΩ resistors between the SDA/SCL lines and the 3.3V or 5V rail. (Use 2.2kΩ if you are running the bus at 400kHz Fast Mode).
3. Logic Level Overdrive / Brownout
If you connected a strict 3.3V sensor directly to the Nano's 5V I2C pins, you may have damaged the sensor's internal I2C transceiver. Conversely, if the USB port supplying your Nano is sagging below 4.5V, the Nano's I2C peripheral may fail to generate valid high-level clock pulses.
Verify: Put a multimeter on the VIN pin of the sensor while the circuit is powered. It must read between 3.0V and 5.5V. Measure the SDA line at rest; it should sit steadily at the logic high voltage (either 3.3V or 5V, depending on the breakout's level shifters).
Extending and Simplifying the Build
Once your baseline BME280 I2C loop is stable, you will inevitably want to alter the scope of the project. Here is how to pivot based on your new constraints.
How to Simplify (If you just need a basic room thermometer)
If the BME280 is out of your budget and you only need rough indoor readings, swap the sensor for a DHT11.
Warning: The DHT11 has a dismal ±2.0°C / ±5% RH accuracy and uses a blocking, single-wire protocol. You must use the DHT.h library, and you cannot poll it faster than once every 1000ms, or the sensor will lock up. It is fine for a child's science fair project, but unacceptable for HVAC control or greenhouse automation.
How to Extend (Building a WiFi-connected weather station)
The Arduino Nano lacks native WiFi. To push this data to a dashboard like Home Assistant or ThingSpeak, you need to upgrade the microcontroller.
- Swap the Brain: Replace the Nano with an ESP32-WROOM-32 DevKit v1. The ESP32 is 3.3V logic native, meaning you can safely use raw, cheap BME280 modules without level shifters.
- Add Wind and Rain: The BME280 handles static air. To measure wind speed, wire a Davis Instruments 6410 Anemometer to an ESP32 GPIO pin configured with
attachInterrupt(). The anemometer acts as a simple reed switch; count the pulses per second to calculate MPH. - Implement MQTT: Use the
PubSubClientlibrary to publish the BME280 JSON payload to a local Mosquitto broker. This keeps your weather data off the public cloud and reduces latency to under 50ms for local smart-home automations.






