When building an Arduino weather station, the sensor you choose dictates your data quality. Skip the DHT11 or DHT22; they lack barometric pressure, suffer from slow response times, and rely on a finicky single-bus protocol. The Bosch BME280 is the bench standard for hobbyist meteorology, offering temperature, relative humidity, and barometric pressure over a single, robust I2C bus.

This guide targets the Arduino Nano v3 (ATmega328P) paired with the GY-BME280 (6-pin variant with onboard 3.3V LDO). We will cover exact wiring, production-ready code using millis() for non-blocking reads, and how to debug the most common I2C initialization failures.

Project Spec Sheet & Difficulty Rating

Difficulty: Intermediate (Requires basic I2C knowledge and breadboarding)
Time to Build: 45 minutes
Estimated Cost: $12 - $18 USD
Target Board Variant: Arduino Nano v3 (ATmega328P, 16MHz, 5V logic)
Core Libraries: Wire.h, Adafruit_BME280.h, Adafruit_Sensor.h

Exact Parts List & Wiring Pinout

To ensure 5V logic compatibility without frying the sensor, we are using the 6-pin GY-BME280 breakout. Unlike raw BME280 chips (which operate at 1.8V logic and require level shifters), this specific 6-pin breakout includes an onboard 3.3V LDO regulator and 4.7kΩ I2C pull-up resistors tied to 3.3V.

Required Components

  • Microcontroller: Arduino Nano v3 (ATmega328P) or genuine Arduino Nano Every.
  • Sensor: GY-BME280 Breakout Board (6-pin variant: VCC, GND, SCL, SDA, CSB, SDO). Note: Avoid the 5-pin variants as they often lack the LDO and operate at raw 1.8V logic.
  • Wiring: 4x male-to-male jumper wires (minimum 22 AWG solid core for breadboards).
  • Prototyping: Half-size solderless breadboard.

Pin Mapping Table

GY-BME280 Pin Arduino Nano v3 Pin Function / Notes
VCC5VPowers the onboard LDO. Do not exceed 6V.
GNDGNDCommon ground reference.
SCLA5I2C Clock line.
SDAA4I2C Data line.
SDONot ConnectedLeave floating for I2C address 0x76. Tie to GND for 0x77.
CSBNot ConnectedLeave floating (pulled high internally) to force I2C mode.

Step-by-Step Assembly & I2C Verification

Callout Tip: Before uploading the main weather station code, always run the standard Arduino I2C_Scanner example sketch. The GY-BME280 (with SDO floating) should report 0x76. If it reports 0x77, your specific board has SDO pulled high internally, and you will need to change the address in the code below.
  1. Power Down: Ensure the Arduino Nano is unplugged from your PC before making I2C connections to prevent logic latch-up.
  2. Wire the I2C Bus: Connect A4 to SDA and A5 to SCL. Keep these wires under 30cm (12 inches) to prevent capacitance from degrading the I2C signal edges.
  3. Wire Power: Connect 5V to VCC and GND to GND. Verify with a multimeter that you have 4.8V - 5.2V at the breadboard rails.
  4. Verify Communication: Upload the I2C_Scanner sketch. Open Serial Monitor at 115200 baud. Confirm the device is found at 0x76.
  5. Upload Main Code: Proceed to the complete code block below.

Complete Arduino Weather Station Code

This code uses the Adafruit BME280 library. It avoids delay() in the main loop, using millis() instead so you can add LCD screens or WiFi transmission later without blocking the processor. Install the Adafruit BME280 Library and Adafruit Unified Sensor via the Arduino Library Manager before compiling.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Pin definitions for I2C (Hardware I2C on Nano v3)
#define I2C_SDA A4
#define I2C_SCL A5
#define BME_I2C_ADDRESS 0x76 // Change to 0x77 if I2C scanner reports it

// Sea level pressure for accurate altitude calculation
// Update this based on your local NOAA METAR report
#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;

unsigned long lastReadTime = 0;
const unsigned long readInterval = 2000; // Read every 2 seconds

void setup() {
  Serial.begin(115200);
  while(!Serial); // Wait for serial monitor to open (optional)
  
  Serial.println(F("Arduino Weather Station - BME280 Test"));

  // Initialize I2C with explicit pins for clarity
  Wire.begin(I2C_SDA, I2C_SCL);

  // Initialize BME280 with error handling
  if (!bme.begin(BME_I2C_ADDRESS, &Wire)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1) {
      // Halt execution if sensor is missing to prevent garbage data logging
      delay(100);
    }
  }
  
  Serial.println(F("BME280 initialized successfully."));
  
  // Configure sensor sampling rates for weather monitoring (low power, high accuracy)
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
                  Adafruit_BME280::SAMPLING_X1, // Temperature
                  Adafruit_BME280::SAMPLING_X1, // Pressure
                  Adafruit_BME280::SAMPLING_X1, // Humidity
                  Adafruit_BME280::FILTER_OFF);
}

void loop() {
  unsigned long currentTime = millis();
  
  if (currentTime - lastReadTime >= readInterval) {
    lastReadTime = currentTime;
    
    // Must call takeForcedMeasurement() in MODE_FORCED
    bme.takeForcedMeasurement();
    
    float tempC = bme.readTemperature();
    float pressureHpa = bme.readPressure() / 100.0F;
    float humidity = bme.readHumidity();
    float altitudeM = bme.readAltitude(SEALEVELPRESSURE_HPA);
    
    // Output formatted CSV data for easy Serial Plotter or Python logging
    Serial.print(tempC, 2);
    Serial.print(",");
    Serial.print(pressureHpa, 2);
    Serial.print(",");
    Serial.print(humidity, 2);
    Serial.print(",");
    Serial.println(altitudeM, 2);
  }
}

Debugging: "Could not find a valid BME280 sensor" Error

If your Serial Monitor outputs the exact string Could not find a valid BME280 sensor, check wiring!, the Arduino Wire library failed to receive an ACKnowledge (ACK) bit from the sensor. Here are the ranked causes and fixes:

  1. Wrong I2C Address (Most Common): Bosch manufactures the BME280 with two possible default addresses: 0x76 and 0x77. Run the I2C Scanner sketch. If it returns 0x77, change #define BME_I2C_ADDRESS 0x76 to 0x77 in the code above.
  2. SDA/SCL Swapped: Unlike UART, I2C is not cross-wired. SDA must go to SDA (A4), and SCL to SCL (A5). Swap them and reset the board.
  3. Missing Pull-Up Resistors: If you are using a raw BME280 module instead of the GY-BME280 breakout, the I2C lines are floating. You must add 4.7kΩ resistors between SDA and 3.3V, and SCL and 3.3V.
  4. Logic Level Overvoltage: If you wired 5V directly to a raw BME280 chip (without an LDO breakout), the 1.8V logic core is likely fried. The chip will draw excessive current and fail to respond. Check the sensor temperature with your finger; if it's hot, replace it and use a proper breakout board.

How to Extend or Simplify the Build

Depending on your end goal, you can scale this Arduino weather station up or down.

Simplifying the Build

  • Drop Altitude: If you only care about indoor climate, remove the readAltitude() function and the SEALEVELPRESSURE_HPA macro. Altitude calculations require manual calibration against local barometric pressure, which drifts daily.
  • Switch to Blocking Delays: If this is a standalone project with no LCD or buttons, replace the millis() logic with a simple delay(2000) to reduce code complexity.

Extending the Build

  • Add Wireless Telemetry: Swap the Arduino Nano for an ESP32 DevKit V1. The I2C pins will change (default ESP32 I2C is GPIO 21 for SDA, GPIO 22 for SCL). Use the PubSubClient library to publish the CSV data to an MQTT broker like Mosquitto for Home Assistant integration.
  • Add Wind and Rain: Integrate a reed-switch anemometer and a tipping-bucket rain gauge. These require external hardware debouncing (100nF capacitors) and should be read using hardware interrupts (attachInterrupt()) rather than polling.
  • Solar Power: For remote deployment, power the Nano via a 5V USB power bank connected to a 6V 3W solar panel through a TP4056 Li-Ion charge controller. Put the BME280 into MODE_SLEEP between reads to drop system current to under 2mA.

Arduino Weather Station FAQ

Can I use an Arduino Uno R3 instead of a Nano for this weather station?

Yes. The code and wiring are 100% compatible with the Arduino Uno R3. The ATmega328P pinout for hardware I2C is identical (A4 is SDA, A5 is SCL). The only difference is physical footprint and power consumption; the Uno's USB-to-Serial chip draws an extra 10-15mA, making it less ideal for battery-powered outdoor deployments.

Why is my BME280 temperature reading 2°C higher than my room thermometer?

The BME280 is highly accurate (±1.0°C), but it measures the temperature of its own silicon die. If the sensor is mounted near a voltage regulator, enclosed in a tight 3D-printed case without ventilation, or exposed to direct sunlight, it will read ambient heat plus self-heating. Mount the sensor in a radiation shield (like a Stevenson screen) with passive airflow to get true ambient readings.

How do I make this Arduino weather station wireless with an ESP8266 or ESP32?

Replace the Nano with an ESP32. Wire the BME280 SDA to GPIO 21 and SCL to GPIO 22. Install the WiFi.h and PubSubClient libraries. Format your sensor readings into a JSON payload (e.g., {"temp": 22.5, "hum": 45}) and publish it to an MQTT topic. Ensure you add a 10µF decoupling capacitor across the BME280 VCC and GND pins, as the ESP32's WiFi radio creates significant voltage ripple during transmission.

What are the first three things to check when the Arduino weather station fails to boot or read data?

If the system hangs or outputs garbage data, follow this triage sequence:
1. Verify I2C Address: Run the I2C Scanner sketch to confirm the sensor is still responding at 0x76 or 0x77.
2. Check VCC Voltage: Use a multimeter to measure voltage directly at the sensor's VCC and GND pins. It must be between 3.0V and 5.5V. A loose breadboard contact often drops this below 2.8V, causing brownouts.
3. Inspect SDA/SCL Continuity: With the power off, use the continuity setting on your multimeter to probe from the Arduino A4/A5 pins directly to the sensor breakout pads to rule out broken jumper wires.