Weather Station
Intermediate40 minA small weather station for a room or a balcony: the BME280 sensor measures temperature, humidity and air pressure, and the Arduino works out the approximate altitude from the pressure. The readings show page by page on a 16x2 LCD; each press of the button turns to the next page, and the values refresh every two seconds. It teaches building an I2C line where two modules share the same two wires (SDA and SCL), working out altitude from pressure, and updating an LCD without flicker. Install "Adafruit BME280 Library" (with the extra libraries it asks for) and "LiquidCrystal I2C" from the Library Manager.
Build it step by step in the appWhat you need
1 × Arduino Uno R3
1 × Breadboard 830 Points (MB-102)
1 × BME280 Temperature, Humidity and Pressure Sensor
1 × 16x2 Character LCD Display (with I2C module)
1 × 12x12 mm Push Button
10 × Jumper Wire - Male/Male
4 × Jumper Wire - Male/Female
Wiring steps
- 1
Connect the power wires

Plug a red male-male jumper wire into the 5V pin on the Arduino's power header and connect its other end to the breadboard's top red (+) rail; the wire runs round under the breadboard. Plug a black wire into the GND pin on the Arduino's top row, next to pin 13, and connect its other end to the top blue (−) rail. This project uses only the top rails.
- Arduino 5V → the top red (+) rail (red wire)
- Arduino GND (top header) → the top blue (−) rail (black wire)
- 2
Add and wire the button

Plug the button over the gap in the middle of the breadboard, with its legs in columns 4 and 6. Connect the Arduino's pin 2 to the top half of column 4 with a green wire (BUTTON_PIN 2). Connect the top half of column 6 to the top (−) rail with a short black wire. Pressing the button joins pin 2 to GND; the code gives it its resistor with the Arduino's own pull-up, so none is needed.
- push button → across the middle gap, columns 4 and 6
- Arduino pin 2 → column 4, top half (green wire)
- column 6, top half → the top blue (−) rail (black wire)
- 3
Plug in and wire the BME280 sensor

Plug the BME280 module in with its pins in row F of the top half: SDA in column 9, SCL in 10, GND in 11 and VIN in 12; the module's body reaches towards the gap in the middle and its lettering is upside down to you. Connect the SDA pin next to AREF on the Arduino's top row to column 9 with a blue wire, and the SCL pin beside it to column 10 with a yellow wire. Connect column 11 to the top (−) rail with a short black wire and column 12 to the top (+) rail with a short red wire. An Uno R3 has SDA and SCL in two places: these two pins next to AREF are the same lines as A4 and A5. The four-pin module marked VIN runs on 5V; six-pin boards marked VCC instead of VIN are 3.3V boards, so connect their VCC to the 3.3V pin.
- BME280 sensor SDA → column 9, top half
- BME280 sensor SCL → column 10, top half
- BME280 sensor GND → column 11, top half
- BME280 sensor VIN → column 12, top half
- Arduino SDA (next to AREF) → column 9, top half (blue wire)
- Arduino SCL (next to AREF) → column 10, top half (yellow wire)
- column 11, top half → the top blue (−) rail (black wire)
- column 12, top half → the top red (+) rail (red wire)
- 4
Connect the LCD

Push the female ends of male-female wires onto the four pins of the I2C module on the back of the LCD. Connect GND to the top (−) rail, VCC to the top (+) rail, SDA to the top half of column 18 and SCL to the top half of column 19. Then connect the Arduino's A4 pin to the top half of column 18 with a blue male-male wire, and A5 to the top half of column 19 with a yellow one. On I2C many modules share the same two wires: the code tells them apart by their addresses (the LCD 0x27, the BME280 0x76).
- LCD GND → the top blue (−) rail (black wire)
- LCD VCC → the top red (+) rail (red wire)
- LCD SDA → column 18, top half (blue wire)
- LCD SCL → column 19, top half (yellow wire)
- Arduino A4 → column 18, top half (blue wire)
- Arduino A5 → column 19, top half (yellow wire)
Arduino sketch
// Install from the Library Manager:
// "Adafruit BME280 Library" (with the
// extra libraries it asks for) and
// "LiquidCrystal I2C"
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <LiquidCrystal_I2C.h>
// The button that turns the page
const int BUTTON_PIN = 2;
// Time between two readings (ms)
const unsigned long READ_EVERY = 2000;
// Sea-level pressure where you are (hPa):
// put in the pressure from the weather
// forecast for a truer altitude
const float SEA_LEVEL_HPA = 1013.25;
// I2C address: 0x27 on most, 0x3F on some
LiquidCrystal_I2C lcd(0x27, 16, 2);
Adafruit_BME280 bme;
// Pages: temperature, humidity, pressure,
// altitude
const int PAGES = 4;
int page = 0;
float temperature = 0;
float humidity = 0;
float pressure = 0;
float altitude = 0;
unsigned long lastRead = 0;
bool lastButton = HIGH;
unsigned long lastChange = 0;
// Writes a row and blanks the rest of it
// (no flicker)
void printLine(int row, const char *text) {
lcd.setCursor(0, row);
int n = 0;
while (text[n] && n < 16) lcd.print(text[n++]);
while (n++ < 16) lcd.print(' ');
}
// The sensor's id: 0x60 on a BME280,
// 0x58 on a BMP280, 0 if none answers
byte chipId(byte address) {
Wire.beginTransmission(address);
Wire.write(0xD0);
if (Wire.endTransmission() != 0) return 0;
Wire.requestFrom((int)address, 1);
return Wire.available() ? Wire.read() : 0;
}
// Says why the sensor was not found
void showSensorError() {
byte id = chipId(0x76);
if (id == 0) id = chipId(0x77);
if (id == 0x58) {
printLine(0, "This is a BMP280");
printLine(1, "No humidity");
Serial.println("The sensor is a BMP280: it has no humidity, a BME280 is needed.");
} else {
printLine(0, "No sensor");
printLine(1, "Check the wires");
Serial.println("BME280 not found: check the SDA, SCL, VIN and GND wires.");
}
}
// Address 0x76 or 0x77, by the module
bool startSensor() {
return bme.begin(0x76) || bme.begin(0x77);
}
void readSensor() {
bme.takeForcedMeasurement();
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = bme.readPressure() / 100.0;
// Pressure falls as you go up (near sea
// level 1 hPa every ~8 m): the altitude
altitude = 44330.0 * (1.0 - pow(pressure / SEA_LEVEL_HPA, 0.1903));
Serial.print("Temperature: ");
Serial.print(temperature, 1);
Serial.print(" °C Humidity: ");
Serial.print(humidity, 0);
Serial.print("% Pressure: ");
Serial.print(pressure, 1);
Serial.print(" hPa Altitude: ");
Serial.print(altitude, 0);
Serial.println(" m");
}
void showPage() {
// Top row: the page's name and number
const char *titles[PAGES] = {"Temperature", "Humidity", "Pressure", "Altitude"};
char top[17];
snprintf(top, sizeof(top), "%-12s%d/%d", titles[page], page + 1, PAGES);
printLine(0, top);
// Bottom row: the value and its unit
char value[10];
char bottom[17];
if (page == 0) {
dtostrf(temperature, 1, 1, value);
// \xDF is the display's degree sign
snprintf(bottom, sizeof(bottom), "%s \xDF" "C", value);
} else if (page == 1) {
dtostrf(humidity, 1, 0, value);
snprintf(bottom, sizeof(bottom), "%s%%", value);
} else if (page == 2) {
dtostrf(pressure, 1, 1, value);
snprintf(bottom, sizeof(bottom), "%s hPa", value);
} else {
dtostrf(altitude, 1, 0, value);
snprintf(bottom, sizeof(bottom), "about %s m", value);
}
printLine(1, bottom);
}
void setup() {
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT_PULLUP);
lcd.init();
lcd.backlight();
// No sensor: keeps trying until one is
// found, so fixing a wire starts it
while (!startSensor()) {
showSensorError();
delay(2000);
}
// Weather-station setting: one reading
// when asked, asleep in between, so it
// does not warm up and read high
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1,
Adafruit_BME280::SAMPLING_X1,
Adafruit_BME280::SAMPLING_X1,
Adafruit_BME280::FILTER_OFF);
readSensor();
showPage();
lastRead = millis();
}
void loop() {
// A press turns to the next page
bool button = digitalRead(BUTTON_PIN);
if (button != lastButton && millis() - lastChange > 30) {
lastChange = millis();
lastButton = button;
if (button == LOW) {
page = (page + 1) % PAGES;
showPage();
}
}
if (millis() - lastRead >= READ_EVERY) {
lastRead = millis();
readSensor();
showPage();
}
}
What the Serial Monitor shows
Temperature: 23.4 °C Humidity: 45% Pressure: 1008.6 hPa Altitude: 39 m Temperature: 23.4 °C Humidity: 45% Pressure: 1008.6 hPa Altitude: 39 m Temperature: 23.5 °C Humidity: 46% Pressure: 1008.5 hPa Altitude: 40 m
What you will see
Once the code is uploaded the LCD shows the first page: "Temperature 1/4" on the top row and the temperature (e.g. 23.4 °C) below. Each press of the button turns to the next page: humidity (e.g. 45%), pressure (e.g. 1008.6 hPa) and approximate altitude; after the fourth it starts again. The values refresh every two seconds and are also printed in the Serial Monitor. Hold the sensor in your fingers or breathe on it gently and watch the temperature and humidity change. The altitude comes from the air pressure and can drift by tens of metres as the weather changes; for a truer figure, put the sea-level pressure where you are (the "pressure" in a weather forecast) into SEA_LEVEL_HPA in the code. If the display says "No sensor", check the SDA, SCL, VIN and GND wires; it starts by itself once they are right. If it says "This is a BMP280", your sensor is a BMP280: it looks the same but has no humidity. If the backlight is on but there is no text, turn the blue trimmer on the back of the I2C module to set the contrast; if there is still nothing, change 0x27 in the code to 0x3F.
In the app, tick off every wire, match the parts to your inventory and pick up where you left off.
Build it step by step in the app