Connecting I2C sensors to a Raspberry Pi requires wiring the SDA and SCL lines to GPIO 2 and GPIO 3, enabling the I2C interface in the OS, and polling the sensor address via Python. For this guide, we are targeting the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (64-bit, Bookworm or newer) and interfacing it with a Bosch BME280 temperature, humidity, and pressure sensor.

Difficulty Rating: 2/5 (Beginner-Intermediate)
Time Required: 20 minutes for hardware, 15 minutes for software setup.

Hardware Spec Sheet & Parts List

The Raspberry Pi 5 has stricter power requirements and slightly different peripheral routing than the Pi 4. Using the exact components below prevents brownouts and logic-level mismatches.

Component Exact Variant / Model Approx. Cost (2026) Notes
Microcontroller Raspberry Pi 5 (8GB RAM) $80.00 Requires active cooling for sustained sensor-logging workloads.
Power Supply Official 27W USB-C PD Power Supply $12.00 Crucial: Pi 5 requires 5V/5A. Standard 3A phone chargers will throttle USB current.
Sensor Module Adafruit BME280 I2C/SPI Breakout (PID 2652) $19.95 Includes onboard 3.3V regulator and I2C pull-ups. Default address: 0x77.
Wiring Premium Female/Female Dupont Jumper Wires $6.00 Keep I2C runs under 30cm (12 inches) to avoid capacitance issues.

Pin Mapping & Physical Wiring Steps

The Raspberry Pi 5 exposes multiple I2C buses, but I2C1 (GPIO 2 and GPIO 3) is the default user-accessible bus with onboard 1.8kΩ pull-up resistors enabled by the firmware.

Raspberry Pi 5 Pin (Physical) GPIO / Function BME280 Breakout Pin Wire Color (Suggested)
Pin 13V3 PowerVIN (or 3Vo)Red
Pin 6Ground (GND)GNDBlack
Pin 3GPIO 2 (SDA1)SDABlue
Pin 5GPIO 3 (SCL1)SCLYellow
  1. De-energize the board: Unplug the USB-C power cable from the Raspberry Pi 5 before connecting any GPIO wires.
  2. Connect Power: Route the 3.3V pin from the Pi to the VIN pin on the BME280. The Adafruit breakout has an onboard LDO, so feeding it 3.3V is perfectly safe and bypasses the LDO dropout overhead.
  3. Connect Ground: Link Pi Pin 6 to the sensor GND. A shared ground is mandatory for I2C logic referencing.
  4. Connect Data Lines: Connect Pi Pin 3 (SDA) to sensor SDA, and Pi Pin 5 (SCL) to sensor SCL. Do not swap these; I2C will silently fail if reversed.
  5. Verify physical connections: Gently tug each Dupont connector to ensure the crimp is seated fully in the plastic housing.

Python Implementation: Reading the BME280

This code targets the Raspberry Pi 5 (8GB) running 64-bit Raspberry Pi OS. We use the Adafruit CircuitPython library ecosystem, which provides robust hardware abstraction and explicit pin definitions.

First, install the required library via the terminal:

pip3 install adafruit-circuitpython-bme280

Create a file named read_bme280.py and paste the following complete, compilable code:

import time
import board
import busio
import adafruit_bme280

# --- PIN DEFINITIONS ---
# Explicitly define the hardware I2C1 pins for Raspberry Pi
I2C_SDA = board.SDA  # Physical Pin 3 (GPIO 2)
I2C_SCL = board.SCL  # Physical Pin 5 (GPIO 3)

# Initialize the I2C bus with a standard 100kHz clock speed
i2c_bus = busio.I2C(I2C_SCL, I2C_SDA, frequency=100000)

def initialize_sensor():
    """Attempts to find and initialize the BME280 sensor on the I2C bus."""
    try:
        # The Adafruit BME280 breakout defaults to 0x77. 
        # If using a generic eBay/AliExpress clone, it might be 0x76.
        sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c_bus, address=0x77)
        
        # Configure oversampling and IIR filter for stable indoor readings
        sensor.oversampling_temperature = 2
        sensor.oversampling_pressure = 2
        sensor.oversampling_humidity = 2
        sensor.iir_filter_coefficient = 4
        
        print("BME280 sensor initialized successfully.")
        return sensor
    except ValueError as ve:
        print(f"Configuration Error: {ve}")
        print("Check that the I2C address (0x77 or 0x76) matches your specific breakout board.")
        return None
    except OSError as oe:
        print(f"Hardware I/O Error: {oe}")
        print("The Pi cannot communicate with the sensor. Check wiring and I2C enablement.")
        return None

def main():
    sensor = initialize_sensor()
    if not sensor:
        return

    print("Reading environmental data... (Press Ctrl+C to stop)")
    try:
        while True:
            temp_c = sensor.temperature
            humidity = sensor.relative_humidity
            pressure_hpa = sensor.pressure
            
            # Convert to common US/Imperial units for display
            temp_f = (temp_c * 9/5) + 32
            pressure_inhg = pressure_hpa * 0.02953
            
            print(f"Temp: {temp_c:.1f}C ({temp_f:.1f}F) | "
                  f"Humidity: {humidity:.1f}% | "
                  f"Pressure: {pressure_hpa:.1f} hPa ({pressure_inhg:.2f} inHg)")
            
            time.sleep(2.0)
            
    except KeyboardInterrupt:
        print("\nData logging stopped by user.")

if __name__ == "__main__":
    main()

Debugging: Fixing "OSError: [Errno 121] Remote I/O error"

When working with I2C sensors on Raspberry Pi boards, the most common failure mode is the OSError: [Errno 121] Remote I/O error. This exact string means the Linux kernel attempted an I2C transaction, but the sensor did not acknowledge (ACK) the address on the bus.

The First 3 Things to Check When I2C Fails:
  1. Run the bus scan: Execute sudo i2cdetect -y 1 in the terminal. If you don't see 77 or 76 in the grid, the Pi physically cannot see the sensor.
  2. Verify I2C enablement: Run sudo raspi-config nonint get_i2c. If it returns 1, I2C is disabled. Enable it via sudo raspi-config nonint do_i2c 0 and reboot.
  3. Check for swapped data lines: SDA and SCL are not interchangeable. Swap the blue and yellow wires at the breadboard if i2cdetect shows an empty grid.

If the first three checks pass, review these ranked secondary causes:

  1. Address Mismatch (40% of cases): Generic BME280 clones often tie the SDO pin to GND, shifting the address to 0x76. Change the address=0x77 parameter in the Python code to 0x76.
  2. Insufficient Pull-Up Resistance (30% of cases): The Pi 5 has internal pull-ups, but if your wires exceed 30cm, bus capacitance rises above the 400pF I2C spec limit. Add external 4.7kΩ pull-up resistors to the 3.3V rail.
  3. Power Starvation (20% of cases): If you are back-powering the sensor from a 3.3V GPIO pin while running other peripherals, the Pi's 3.3V LDO might be sagging. Power the sensor VIN from the 5V pin instead (the Adafruit breakout handles 5V input).
  4. Counterfeit Sensor IC (10% of cases): Cheap BMP280 (pressure/temp only) modules are frequently mislabeled as BME280 (includes humidity). A BMP280 will throw an I/O error when the library attempts to read the humidity calibration registers.

Extending and Simplifying the Sensor Build

Once you have a single sensor reading reliably, you will inevitably want to scale the project. Here is how to adapt the build for production or rapid prototyping.

How to Simplify the Build:
Ditch the Dupont wires. The Adafruit BME280 breakout features a STEMMA QT / Qwiic JST-SH 4-pin connector. By purchasing a STEMMA QT to Pi 5 GPIO cable, you eliminate breadboard contact resistance and wiring errors entirely. This reduces assembly time from 10 minutes to 10 seconds and drastically improves reliability in high-vibration environments.

How to Extend the Build:
I2C is a bus, meaning you can daisy-chain multiple different sensors (e.g., adding an SCD41 CO2 sensor and an OPT3001 light sensor) as long as their addresses don't collide. If you need to deploy multiple identical BME280 sensors (e.g., one for indoor, one for outdoor), you will hit an address collision since both default to 0x77. To solve this, insert a TCA9548A I2C Multiplexer between the Pi and the sensors. The multiplexer acts as a switchboard, allowing you to route the Pi's I2C bus to 8 separate channels, bypassing address limitations entirely.

Frequently Asked Questions

How many I2C sensors can a Raspberry Pi handle at once?

Theoretically, the I2C protocol supports up to 128 unique addresses on a single bus. In practice, the Raspberry Pi's I2C bus is limited by electrical capacitance. The I2C specification dictates a maximum bus capacitance of 400pF. With standard breakout boards and short wires, you can comfortably run 10 to 15 distinct sensors before signal degradation causes Remote I/O errors. For more sensors, use an I2C multiplexer or lower the bus frequency to 50kHz.

Why is my Raspberry Pi sensor reading drifting or showing exactly 85°C?

A locked reading of exactly 85°C (or sometimes 0°C) is the hallmark of an I2C bus that is physically connected but failing to transmit data packets correctly, causing the library to read the sensor's default power-on-reset register values. This is almost always caused by loose Dupont wire crimps or a missing common ground connection. Re-seat the wires and ensure the GND pin is shared directly between the Pi and the sensor.

Can I use 5V sensors with the Raspberry Pi 5 GPIO?

No, not directly. The Raspberry Pi 5 (like all previous models) uses 3.3V logic on its GPIO pins. Feeding a 5V logic signal into the Pi's SDA or SCL pins will permanently damage the SoC's GPIO pad. If your sensor only outputs 5V I2C levels (common with older industrial sensors or Arduino-specific modules), you must use a bidirectional logic level converter (like the BSS138-based Adafruit 4-channel shifter) between the sensor and the Pi.

Do I need to enable I2C every time I reboot the Raspberry Pi?

No. When you enable I2C via raspi-config, the tool modifies the /boot/firmware/config.txt file (on Pi 5 Bookworm OS) by adding or uncommenting the line dtparam=i2c_arm=on. This setting is persistent across reboots and OS updates. You only need to enable it once per fresh OS installation.