When searching for reliable, easy projects for Raspberry Pi boards, the I2C environmental monitor stands out as the definitive starting point. Unlike 1-Wire sensors that suffer from timing glitches under Linux, or analog sensors that require external ADCs, an I2C digital sensor like the Bosch BME280 communicates cleanly via the hardware I2C bus. This project teaches you fundamental bus protocols, pin multiplexing, and Python hardware abstraction, making it the perfect bridge between basic scripting and real-world embedded systems.
This guide specifically targets the Raspberry Pi 5 (4GB and 8GB variants) running Raspberry Pi OS (Bookworm, 64-bit). The Pi 5 introduces the RP1 southbridge chip, which handles peripheral routing differently than the BCM2711 on the Pi 4, though the user-space I2C mapping remains consistent.
Why the BME280 is the Best Starting Point
Before ordering parts, it is critical to select the right sensor. Many beginners start with the DHT22, only to abandon it due to missed reads and lack of barometric pressure data. The BME280 integrates temperature, humidity, and pressure into a single 2.5 x 2.5 mm LGA package, communicating over I2C or SPI.
| Sensor Model | Interface | Temp Accuracy | Humidity Accuracy | Pressure / Gas | Typical Price |
|---|---|---|---|---|---|
| BME280 | I2C / SPI | ±1.0°C | ±3% RH | ±1 hPa | $4.50 |
| DHT22 (AM2302) | 1-Wire (Custom) | ±0.5°C | ±2% RH | None | $6.00 |
| AHT20 | I2C | ±0.3°C | ±2% RH | None | $2.50 |
| BME680 | I2C / SPI | ±1.0°C | ±3% RH | ±1 hPa + VOC | $12.00 |
| SHT31 | I2C | ±0.3°C | ±2% RH | None | $8.00 |
The BME280 wins for general-purpose builds because it offers the best balance of multi-metric data, hardware I2C reliability, and cost. For a deep dive into the sensor's internal piezoresistive pressure and capacitive humidity sensing elements, refer to the official Bosch BME280 datasheet.
Hardware Specs, Parts List, and Pi 5 Pin Mapping
To build this circuit, you need exact components. Do not substitute 5V logic sensors without a level shifter; the Raspberry Pi 5 GPIO header is strictly 3.3V tolerant. Feeding 5V into the SDA or SCL lines will permanently damage the RP1 southbridge chip.
Required Parts
- Microcontroller: Raspberry Pi 5 (4GB or 8GB variant) with active cooler
- Sensor: Adafruit BME280 Breakout (Product ID: 2652) or generic GY-BME280 module
- Wiring: 4x Female-to-Female jumper wires (28 AWG silicone preferred for flexibility)
- Power: Official Raspberry Pi 27W USB-C Power Supply
Pin Mapping Table
The Raspberry Pi 5 exposes I2C Bus 1 on the primary GPIO header. Here is the exact wiring map:
| Raspberry Pi 5 GPIO | Pi Header Pin # | BME280 Breakout Pin | Wire Color (Standard) |
|---|---|---|---|
| 3.3V Power | Pin 1 | VIN (or VCC) | Red |
| GPIO 2 (SDA1) | Pin 3 | SDI (or SDA) | Yellow |
| GPIO 3 (SCL1) | Pin 5 | SCK (or SCL) | Orange |
| Ground | Pin 6 | GND | Black |
Step-by-Step Wiring and I2C Configuration
- De-energize the board: Unplug the USB-C power cable from the Raspberry Pi 5 before connecting any GPIO wires.
- Connect the I2C lines: Wire Pin 3 to SDA and Pin 5 to SCL as per the table above.
- Connect Power and Ground: Wire Pin 1 to VCC and Pin 6 to GND.
- Boot and configure: Power on the Pi, open a terminal, and run
sudo raspi-config. - Enable I2C: Navigate to Interface Options > I2C and select Yes to enable the ARM I2C interface.
- Reboot: Run
sudo rebootto apply the device tree overlays. - Verify hardware connection: After reboot, run
sudo i2cdetect -y 1. You should see76(generic boards) or77(Adafruit boards) in the grid output.
Complete Python Code with Error Handling
This script uses the Adafruit CircuitPython BME280 library, which abstracts the complex Bosch calibration registers into clean Python objects. Before running, install the dependencies:
pip3 install adafruit-circuitpython-bme280
The code below targets the Raspberry Pi 5 I2C bus, defines the pins explicitly in comments for maintainability, and includes robust error handling for common I2C bus faults.
import time
import board
import busio
import adafruit_bme280
# Pin definitions for Raspberry Pi 5 (I2C Bus 1)
# Hardware SDA = GPIO 2 (Header Pin 3)
# Hardware SCL = GPIO 3 (Header Pin 5)
i2c = busio.I2C(board.SCL, board.SDA)
# Initialize the sensor with error handling for address mismatches
# Generic GY-BME280 modules typically use 0x76; Adafruit uses 0x77
try:
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)
except ValueError as e:
print(f"[FATAL] Sensor not found on I2C bus. Check wiring and address. Error: {e}")
exit(1)
# Set local sea level pressure for accurate altitude calculations
# Update this value based on your local weather station data
sensor.sea_level_pressure = 1013.25
print("BME280 Sensor Initialized. Starting data loop...")
while True:
try:
temp_c = sensor.temperature
humidity = sensor.relative_humidity
pressure = sensor.pressure
altitude = sensor.altitude
print(f"Temp: {temp_c:.2f} C | "
f"Hum: {humidity:.2f} % | "
f"Press: {pressure:.2f} hPa | "
f"Alt: {altitude:.2f} m")
# The BME280 requires a minimum delay between reads to prevent self-heating
time.sleep(2.0)
except OSError as e:
# Catches I2C bus communication drops without crashing the script
print(f"[WARN] I2C Communication Error: {e}. Retrying in 5s...")
time.sleep(5)
except KeyboardInterrupt:
print("\nData logging stopped by user.")
break
Debugging I2C Failures: Exact Errors and Ranked Causes
When working with I2C on the Raspberry Pi 5, the OS will throw specific OSError exceptions when the physical layer fails. Here is how to debug the two most common errors.
Error 1: OSError: [Errno 121] Remote I/O error
This means the Pi sent a clock pulse and address, but the sensor sent a NACK (Not Acknowledged) or the bus is physically shorted.
The first three things to check:
- Run
sudo i2cdetect -y 1: If the grid is entirely empty (only dashes), your sensor is unpowered, or the SDA/SCL wires are swapped. If you seeUU, another kernel driver has claimed the device. - Verify the I2C Address: Look at the back of your BME280 breakout. If the address pads are bridged to
0x77but your Python code specifies0x76, you will get Errno 121. Change theaddress=parameter in the code to match. - Check Pull-Up Resistors: I2C is an open-drain protocol requiring pull-up resistors to 3.3V. Adafruit boards include these. Some ultra-cheap generic GY-BME280 boards omit them. If
i2cdetectshows intermittent addresses, solder 4.7kΩ resistors between SDA-VCC and SCL-VCC.
Error 2: OSError: [Errno 110] Connection timed out
This occurs when the SDA line is stuck LOW, usually due to a bus collision or a sensor that crashed mid-transmission and is holding the data line down.
Ranked Causes and Fixes:
- Missing Common Ground: The Pi and the sensor must share a ground reference. Without Pin 6 connected, the logic levels float, causing the RP1 chip to misread the SDA line state. Re-seat the black GND wire.
- Capacitance Overload: If you are using jumper wires longer than 12 inches (30 cm), the bus capacitance exceeds the I2C spec (400 pF), degrading the square wave into a sawtooth. Shorten the wires or reduce the I2C clock speed to 10kHz in
/boot/firmware/config.txtusingdtparam=i2c_baudrate=10000. - Sensor Brownout: The BME280 entered a fault state due to a voltage dip. Power cycle the Raspberry Pi completely (unplug from the wall, not just a soft reboot) to drain residual charge from the sensor's decoupling capacitors.
For more on configuring the Pi 5's device tree and I2C baud rates, consult the official Raspberry Pi configuration documentation.
How to Extend or Simplify the Build
Once your baseline script is running reliably, you can scale the project up or down depending on your deployment needs.
Simplify: Headless CSV Data Logger
If you are deploying this in an attic or greenhouse without a monitor, strip out the print() statements and append the data to a local CSV file. Add the csv module to your imports and open a file in append mode ('a'). Use a cron job (crontab -e) to run the script at @reboot so it survives power outages without requiring systemd service configuration.
Extend: Add an OLED Display and MQTT
To make this a standalone desktop weather station:
- Add a Display: Wire an SSD1306 128x64 I2C OLED display to the exact same SDA and SCL pins. I2C supports multiple devices on one bus as long as addresses don't conflict (the SSD1306 uses
0x3C). Use theadafruit-circuitpython-ssd1306library to render the text. - Add Network Telemetry: Install
paho-mqttand publish the JSON payload to a local Mosquitto broker. This allows you to ingest the Pi 5's sensor data into Home Assistant or Node-RED without running heavy REST APIs on the Pi itself.
By mastering the I2C bus and the BME280, you build the foundational skills required for almost all advanced embedded sensor networks. The transition from reading a single weather sensor to managing a fleet of distributed environmental nodes is just a matter of scaling the bus and the code.






