DIY Weather Station Intermediate

Build a weather station using BME280, BH1750, and an OLED display

🔗Goal

Build a compact weather station that reads temperature, humidity, and barometric pressure from a BME280 sensor and displays all three values on a small OLED screen. This project runs entirely offline — no WiFi needed. Just plug in the ESP32 and you have a live, updating weather display.

Here is how the system works at a high level:

graph LR
    A[BME280 Sensor] -->|I2C| B[ESP32]
    B -->|I2C| C[OLED SSD1306]

Both the BME280 and the OLED display use the I2C protocol, so they share the same two data lines (SDA and SCL). This keeps wiring simple — only four connections per device.

🔗Prerequisites

You will need the following components:

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
BME280 sensor module1I2C breakout board (not BMP280 — that one lacks humidity)AliExpress | Amazon.de .co.uk .com
SSD1306 OLED display (0.96")1I2C interfaceAliExpress | Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires~8Male-to-male and male-to-femaleAliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

You will also need the following Arduino libraries. Install them via Sketch > Include Library > Manage Libraries:

LibraryAuthorPurpose
Adafruit BME280 LibraryAdafruitBME280 sensor driver
Adafruit Unified SensorAdafruitDependency for the BME280 library
Adafruit SSD1306AdafruitOLED display driver
Adafruit GFX LibraryAdafruitGraphics primitives (text, shapes)

See our BME280 guide for more details on the sensor, including altitude calculation and I2C address configuration.

See our OLED SSD1306 guide for more on display setup, custom fonts, and drawing graphics.

🔗Tutorial

🔗Step 1: Wiring

Both the BME280 and the SSD1306 OLED connect to the same I2C bus. On most ESP32 boards, the default I2C pins are GPIO 21 (SDA) and GPIO 22 (SCL).

ComponentPinESP32 Pin
BME280VIN / VCC3.3V
BME280GNDGND
BME280SDAGPIO 21
BME280SCLGPIO 22
SSD1306 OLEDVCC3.3V
SSD1306 OLEDGNDGND
SSD1306 OLEDSDAGPIO 21
SSD1306 OLEDSCLGPIO 22

Both devices share the same SDA and SCL lines — this is normal for I2C. Each device has a unique address (BME280 is typically 0x76 or 0x77, and the SSD1306 is typically 0x3C).

Pin labels and GPIO numbers vary between ESP32 boards. Always check your board's pinout diagram and datasheet.

🔗Step 2: Verify I2C addresses

If either device is not detected, the most common reason is an I2C address mismatch. Most BME280 modules default to 0x76, but some use 0x77 depending on how the SDO pin is wired on the breakout board. You can use the I2C scanner sketch from the Arduino examples to confirm the addresses.

🔗Step 3: Upload the code

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

// ----- OLED configuration -----
#define SCREEN_WIDTH   128
#define SCREEN_HEIGHT  64
#define OLED_RESET     -1   // No reset pin
#define OLED_ADDRESS   0x3C

// ----- BME280 configuration -----
#define BME280_ADDRESS 0x76  // Change to 0x77 if your module uses that address

// ----- Objects -----
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_BME280 bme;

void setup() {
    Serial.begin(115200);

    // Initialize I2C (default SDA=21, SCL=22 on most ESP32 boards)
    Wire.begin();

    // Initialize OLED
    if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
        Serial.println("ERROR: SSD1306 OLED not found. Check wiring and address.");
        while (true) { delay(1000); }
    }
    display.clearDisplay();
    display.setTextColor(SSD1306_WHITE);
    display.display();
    Serial.println("OLED initialized.");

    // Initialize BME280
    if (!bme.begin(BME280_ADDRESS)) {
        Serial.println("ERROR: BME280 not found. Check wiring and address.");
        display.clearDisplay();
        display.setTextSize(1);
        display.setCursor(0, 0);
        display.println("BME280 not found!");
        display.println("Check wiring.");
        display.display();
        while (true) { delay(1000); }
    }
    Serial.println("BME280 initialized.");
}

void displayWeather(float temp, float humidity, float pressure) {
    display.clearDisplay();

    // Row 1: Temperature
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("Temp:");
    display.setTextSize(2);
    display.setCursor(0, 10);
    display.print(temp, 1);
    display.setTextSize(1);
    display.print(" ");
    display.print((char)247);  // Degree symbol
    display.print("C");

    // Row 2: Humidity
    display.setTextSize(1);
    display.setCursor(0, 28);
    display.print("Humidity:");
    display.setTextSize(2);
    display.setCursor(0, 38);
    display.print(humidity, 1);
    display.setTextSize(1);
    display.print(" %");

    // Row 3: Pressure (bottom of screen)
    display.setTextSize(1);
    display.setCursor(0, 56);
    display.print("Pressure: ");
    display.print(pressure, 1);
    display.print(" hPa");

    display.display();
}

void loop() {
    float temperature = bme.readTemperature();       // Celsius
    float humidity    = bme.readHumidity();           // Percent
    float pressure    = bme.readPressure() / 100.0F;  // Pa to hPa

    // Print to Serial Monitor
    Serial.print("Temperature: ");
    Serial.print(temperature, 1);
    Serial.print(" C  |  Humidity: ");
    Serial.print(humidity, 1);
    Serial.print(" %  |  Pressure: ");
    Serial.print(pressure, 1);
    Serial.println(" hPa");

    // Update the OLED
    displayWeather(temperature, humidity, pressure);

    delay(2000);  // Update every 2 seconds
}

🔗Step 4: Read the display

After uploading, the OLED should show three rows of information:

  • Temperature in large text (degrees Celsius)
  • Humidity in large text (percent)
  • Pressure in smaller text at the bottom (hectopascals)

The display updates every 2 seconds. You can also open the Serial Monitor at 115200 baud to see the same values printed as text.

🔗Step 5: Understanding the readings

The BME280 measures barometric (atmospheric) pressure. At sea level, standard pressure is $1013.25\,\text{hPa}$. Your local pressure will vary with altitude and weather conditions.

You can estimate altitude from the pressure reading using the barometric formula:

$$h = 44330 \times \left(1 - \left(\frac{P}{P_0}\right)^{0.1903}\right)$$

where $P$ is the measured pressure and $P_0 = 1013.25\,\text{hPa}$ is the reference sea-level pressure. The Adafruit BME280 library includes a readAltitude() function that does this calculation for you.

🔗Next Steps

This project works entirely offline, but you can extend it with WiFi capabilities:

  • Web dashboard: Add a web server (like the Room Temperature Monitor) to view readings from your phone
  • MQTT logging: Publish readings to an MQTT broker for long-term data logging (see the MQTT Temperature Logger)
  • Light sensor: Add a BH1750 light sensor to the I2C bus for a lux reading — see our BH1750 guide

🔗Common Issues and Solutions

ProblemCauseFix
OLED shows nothingWrong I2C address or wiring issueRun an I2C scanner sketch. Most SSD1306 modules use 0x3C, but some use 0x3D
"BME280 not found" in Serial MonitorWrong I2C addressTry changing BME280_ADDRESS from 0x76 to 0x77
Temperature reads a few degrees too highBME280 self-heatingThe BME280 generates a small amount of heat during operation. Allow 1-2 minutes for it to stabilize. Keep it away from other heat sources
Humidity reads 100% constantlySensor damaged by condensation or waterThe BME280 is not waterproof. If it was exposed to water, it may need time to dry out or may be permanently damaged
Pressure reading seems wrongThe reading is correct — local pressure differs from 1013.25 hPaAtmospheric pressure varies with altitude and weather. Use a known local reference to compare
Display text is garbled or overlappingScreen dimensions mismatchEnsure SCREEN_WIDTH is 128 and SCREEN_HEIGHT is 64 (or 32 for smaller displays)
Both devices not foundI2C wiring is disconnectedCheck that SDA goes to GPIO 21 and SCL goes to GPIO 22. Ensure both devices share a common GND