How to Build an ESP32 Air Quality Monitor with BME280

Live temperature, humidity, and pressure readings with a comfort label on OLED

ESP32Smart HomeBeginner25 minutes2 components

Updated

How to Build an ESP32 Air Quality Monitor with BME280
For illustrative purposes only
On this page

What you'll build

This guide walks you through building an indoor air quality monitor with an ESP32, a BME280 temperature/humidity/pressure sensor, and a compact SSD1306 OLED display. The BME280 reads the room environment every two seconds and the OLED shows live temperature in degrees Celsius, relative humidity as a percentage, barometric pressure in hectopascals, and a plain-language comfort label that tells you whether conditions are comfortable, too cold, too hot, too humid, or too dry. Both the sensor and the display share the same I2C bus, so only four signal wires connect the entire project to the ESP32.

The BME280 is one of the most practical sensors for makers because it combines three environmental measurements in a single low-cost breakout board. You will learn how to initialise it over I2C, configure its oversampling and filtering registers for stable indoor readings, and convert raw register data into human-readable values using the Adafruit BME280 library. The project also demonstrates how to share an I2C bus between two peripherals at different addresses, draw formatted multi-line output on a 128x64 OLED, and structure a non-blocking sensor loop that keeps the display responsive without tying up the main loop with delays.

When you are finished you will have a desk-sized environment monitor that updates in real time and is useful from the moment you power it on. The code is straightforward to extend: add Wi-Fi to push readings to a dashboard, wire in an RGB LED that changes color with the comfort label, or log data to an SD card for overnight trends. If you already built the weather station that fetches forecast data from an API, this project is a natural companion -- one shows what the weather will be outside, the other shows what conditions are like right now in the room.

Wiring diagram

Wiring diagram

Interactive wiring diagram

Components needed

ComponentTypeQtyBuy
BME280 Sensorsensor1€15.70
SSD1306 OLEDdisplay1€4.30

Prices and availability are indicative and may have been updated by the supplier. Schematik may earn a commission from purchases made through affiliate links.

Assembly

1

Wire the I2C bus

Connect the BME280 and OLED to the ESP32 shared I2C bus. Both sensors use the same SDA (GPIO 21) and SCL (GPIO 22) lines.

2

Flash and verify

Upload the sketch and open Serial Monitor at 115200 baud. You should see temperature, humidity, and pressure readings every two seconds. The OLED will show the values and a comfort label.

Pin assignments

PinConnectionType
3V3aqm-bme280-1 VCCPOWER
GNDaqm-bme280-1 GNDGROUND
GPIO 21aqm-bme280-1 SDAI2C
GPIO 22aqm-bme280-1 SCLI2C
3V3aqm-oled-1 VCCPOWER
GNDaqm-oled-1 GNDGROUND
GPIO 21aqm-oled-1 SDAI2C
GPIO 22aqm-oled-1 SCLI2C

Code

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

#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

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

unsigned long lastReadMs = 0;
const unsigned long READ_INTERVAL_MS = 2000;

float temperature = 0;
float humidity = 0;
float pressure = 0;

String comfortLabel(float temp, float hum) {
  if (temp < 18) return "Cold";
  if (temp > 28) return "Hot";
  if (hum > 70) return "Humid";
  if (hum < 30) return "Dry";
  return "Comfortable";
}

void drawScreen() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Air Quality Monitor");
  display.drawLine(0, 10, SCREEN_WIDTH, 10, SSD1306_WHITE);

  display.setCursor(0, 14);
  display.print("Temp:  ");
  display.print(temperature, 1);
  display.println(" C");

  display.setCursor(0, 26);
  display.print("Humid: ");
  display.print(humidity, 1);
  display.println(" %");

  display.setCursor(0, 38);
  display.print("Press: ");
  display.print(pressure, 0);
  display.println(" hPa");

  display.drawLine(0, 50, SCREEN_WIDTH, 50, SSD1306_WHITE);
  display.setCursor(0, 54);
  display.print("Status: ");
  display.println(comfortLabel(temperature, humidity));

  display.display();
}

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

  Wire.begin(OLED_SDA, OLED_SCL);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED init failed");
    while (true) delay(1000);
  }

  if (!bme.begin(0x76)) {
    Serial.println("BME280 not found at 0x76, trying 0x77");
    if (!bme.begin(0x77)) {
      Serial.println("BME280 init failed");
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 0);
      display.println("BME280 not found!");
      display.println("Check wiring.");
      display.display();
      while (true) delay(1000);
    }
  }

  bme.setSampling(Adafruit_BME280::MODE_NORMAL,
    Adafruit_BME280::SAMPLING_X2,
    Adafruit_BME280::SAMPLING_X16,
    Adafruit_BME280::SAMPLING_X1,
    Adafruit_BME280::FILTER_X16,
    Adafruit_BME280::STANDBY_MS_500);

  Serial.println("Air Quality Monitor ready");
}

void loop() {
  if (millis() - lastReadMs >= READ_INTERVAL_MS) {
    temperature = bme.readTemperature();
    humidity = bme.readHumidity();
    pressure = bme.readPressure() / 100.0;
    lastReadMs = millis();

    Serial.print("T: ");
    Serial.print(temperature, 1);
    Serial.print(" C  H: ");
    Serial.print(humidity, 1);
    Serial.print(" %  P: ");
    Serial.print(pressure, 0);
    Serial.println(" hPa");
  }

  drawScreen();
  delay(100);
}

// Run this and build other cool things at schematik.io
Libraries: Adafruit SSD1306, Adafruit GFX Library, Adafruit BME280 Library, Adafruit Unified Sensor