If you are figuring out how to use Raspberry Pi hardware for real-world data acquisition, the I2C (Inter-Integrated Circuit) bus is your most reliable starting point. Unlike analog-to-digital conversions that require extra chips, I2C allows you to daisy-chain digital sensors directly to the Pi’s GPIO header using just two wires. This guide targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Bookworm (64-bit), walking you through interfacing a BME280 environmental sensor, writing a fault-tolerant Python logger, and debugging the exact I2C errors that stall most first-time builds.
Difficulty Rating & Parts Spec Sheet
Time Required: 45 minutes
Target Board: Raspberry Pi 5 (8GB RAM) — Code and pinouts will also work on Pi 4, but Pi 5 uses the RP1 southbridge chip which changes underlying I2C clock stretching behavior.
| Component | Exact Variant / Model Number | Est. Price (2026) | Purpose |
|---|---|---|---|
| Single Board Computer | Raspberry Pi 5 (8GB) | $80.00 | Main compute and I2C master |
| Environmental Sensor | Adafruit BME280 I2C Breakout (PID: 2652) | $14.95 | Temp, humidity, barometric pressure |
| Power Supply | Official 27W USB-C PD Power Supply | $12.00 | Prevents brownouts during Pi 5 USB bursts |
| Wiring | Female-to-Female Jumper Wires (20cm) | $5.00 | GPIO to breadboard connections |
| Storage | SanDisk Extreme 32GB microSD (A2 rated) | $11.00 | OS and CSV log storage |
Pin Mapping & Physical Wiring
The Raspberry Pi 5 exposes its primary I2C bus (I2C0) on physical pins 3 and 5 of the 40-pin header. The Pi 5 includes onboard 1.8kΩ pull-up resistors to 3.3V on these specific pins, meaning you do not need external pull-up resistors for short wire runs (under 30cm).
| Raspberry Pi 5 Pin | GPIO / Function | BME280 Breakout Pin | Wire Color (Suggested) |
|---|---|---|---|
| Pin 1 | 3.3V Power | VIN (or 3Vo) | Red |
| Pin 3 | GPIO 2 (SDA) | SDI (or SDA) | Blue |
| Pin 5 | GPIO 3 (SCL) | SCK (or SCL) | Yellow |
| Pin 6 | Ground | GND | Black |
Setup Steps:
- Wire the physical connections exactly as mapped above. Double-check that VIN is connected to 3.3V (Pin 1), not 5V. Feeding 5V into the SDA/SCL pins of a 3.3V sensor will permanently destroy the Pi 5's RP1 southbridge GPIO bank.
- Boot the Pi and open a terminal. Enable the I2C interface by running
sudo raspi-config, navigating to Interface Options > I2C, and selecting Yes. - Reboot the Pi. Install the I2C tools and Python libraries:
sudo apt update && sudo apt install -y i2c-tools python3-pip
pip3 install --break-system-packages adafruit-circuitpython-bme280 - Verify the hardware connection by running
i2cdetect -y 1. You should see77(or76depending on the breakout's jumper pad) in the grid output. If the grid is empty, stop and check your wiring before running code.
Python Logging Script with Error Handling
Below is the complete, compilable Python script. It uses the Adafruit CircuitPython BME280 library via the Blinka compatibility layer. It includes explicit error handling for I2C timeouts and missing devices, logging data to a CSV file every 10 seconds.
import time
import board
import busio
import adafruit_bme280
import csv
import sys
from datetime import datetime
# Pin definitions mapped to Raspberry Pi 5 physical header
# board.SDA maps to Physical Pin 3 (GPIO 2)
# board.SCL maps to Physical Pin 5 (GPIO 3)
def initialize_sensor():
"""Initializes the I2C bus and BME280 sensor with error handling."""
try:
# Create I2C bus object using hardware SCL and SDA pins
i2c = busio.I2C(board.SCL, board.SDA)
# Default I2C address for Adafruit BME280 is 0x77
# Change to 0x76 if using a generic clone board
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
sensor.sea_level_pressure = 1013.25
print("Sensor initialized successfully.")
return sensor
except ValueError as e:
print(f"CRITICAL: Sensor not found on I2C bus. Check wiring and address.\nError: {e}")
sys.exit(1)
except Exception as e:
print(f"CRITICAL: Unexpected I2C initialization failure.\nError: {e}")
sys.exit(1)
def log_data(sensor, filename="env_log.csv"):
"""Reads sensor data and appends it to a CSV file."""
file_exists = False
try:
with open(filename, mode='r') as f:
file_exists = True
except FileNotFoundError:
file_exists = False
with open(filename, mode='a', newline='') as file:
writer = csv.writer(file)
if not file_exists:
writer.writerow(["Timestamp", "Temp_C", "Humidity_%", "Pressure_hPa", "Altitude_m"])
try:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
temp = round(sensor.temperature, 2)
humidity = round(sensor.humidity, 2)
pressure = round(sensor.pressure, 2)
altitude = round(sensor.altitude, 2)
writer.writerow([timestamp, temp, humidity, pressure, altitude])
print(f"[{timestamp}] Logged: {temp}C | {humidity}% | {pressure}hPa")
except OSError as e:
print(f"WARNING: I2C Read Error. Bus might be locked or disconnected.\nError: {e}")
except Exception as e:
print(f"WARNING: Unexpected read error.\nError: {e}")
if __name__ == "__main__":
bme_sensor = initialize_sensor()
print("Starting 10-second logging loop. Press Ctrl+C to stop.")
try:
while True:
log_data(bme_sensor)
time.sleep(10)
except KeyboardInterrupt:
print("\nLogging stopped by user.")
sys.exit(0)
Debugging: Fixing I2C Bus Faults
When working with I2C on the Raspberry Pi 5, you will eventually hit a bus fault. The most common crash you will see in the terminal is this exact error string:
Alternatively, during initialization, you might see: ValueError: No I2C device at address: 0x77.
The First Three Things to Check When It Fails:
- Run
i2cdetect -y 1again: If the grid is completely empty, your 3.3V or GND wire is loose, or the sensor is dead. If you seeUUinstead of a hex address, a kernel driver has already claimed the device, and your Python script cannot access it. You will need to blacklist the conflicting driver in/boot/firmware/config.txt. - Check for Clock Stretching Issues: The BME280 sometimes holds the SCL line low while it processes data (clock stretching). The Pi 5's RP1 chip handles this better than the Pi 4, but if you are using long wires (over 50cm), the capacitance on the line will corrupt the stretched clock signal, resulting in Errno 121. Keep I2C wires under 30cm, or add external 4.7kΩ pull-up resistors to 3.3V.
- Verify the I2C Address: Many cheap clone BME280 boards from Amazon or AliExpress have the SDO pin pulled low by default, changing the I2C address from
0x77to0x76. Ifi2cdetectshows76, update your Python script:Adafruit_BME280_I2C(i2c, address=0x76).
Extending and Simplifying the Build
How to Simplify: If you want to eliminate breadboards and jumper wires entirely, buy a BME280 breakout that features a STEMMA QT / Qwiic connector (such as the Adafruit STEMMA QT BME280, PID: 2652 or SparkFun Qwiic BME280, SEN-15440). You can then use a Pi-to-Qwiic shim board, allowing you to plug the sensor directly into the Pi 5 GPIO header with a single click, completely eliminating wiring errors.
How to Extend: To turn this local logger into a networked IoT node, extend the Python script to publish the CSV data via MQTT. Install the Paho MQTT library (pip3 install paho-mqtt) and add a publish function inside the log_data loop to send a JSON payload to a local Mosquitto broker or an ESP32 running a mesh network. For long-term storage, replace the CSV writer with the influxdb-client Python library to push time-series data directly into an InfluxDB database, which you can then visualize on a Grafana dashboard.
Frequently Asked Questions
How to use Raspberry Pi headless for remote sensor logging?
To use the Pi without a monitor, keyboard, or mouse, use the official Raspberry Pi Imager software on your desktop. Before flashing the OS to the microSD card, click the gear icon (or "Edit Settings") to pre-configure your Wi-Fi SSID, password, and enable SSH. Once the Pi boots on your network, you can SSH into it from your main computer using ssh username@raspberrypi.local, install your Python scripts, and use systemd or cron to run the logger automatically on boot.
How to use Raspberry Pi 5 with 5V sensors safely?
The Raspberry Pi 5 GPIO pins operate strictly at 3.3V logic levels. If you attempt to read a 5V I2C sensor or connect a 5V digital signal directly to a Pi 5 GPIO pin, you will fry the RP1 southbridge chip. To safely use 5V sensors, you must use a bi-directional logic level shifter (like the BSS138 MOSFET-based shifters). Wire the Pi's 3.3V to the LV (low voltage) side, the sensor's 5V to the HV (high voltage) side, and route the SDA/SCL lines through the shifter channels.
How to use Raspberry Pi to trigger relays based on sensor data?
You can extend the Python script to control physical hardware by adding a relay module. Wire a 5V relay module's VCC to Pin 2 (5V), GND to Pin 9, and the IN pin to a standard GPIO like GPIO 17 (Physical Pin 11). Use the gpiozero library in Python to define relay = OutputDevice(17). Inside your logging loop, add an if temp > 30.0: condition that calls relay.on() to trigger an exhaust fan, and relay.off() when the temperature drops back below your threshold.






