The Raspberry Pi 5 retains the standard 40-pin GPIO layout but fundamentally changes how connections are managed under the hood. The new RP1 southbridge chip now handles all GPIO, I2C, SPI, and UART routing, while the board upgrades power delivery to 5V/5A via USB-C PD and introduces a dedicated PCIe 2.0 FPC connector. For standard sensor connections, I2C1 (Pins 3/5) and SPI0 (Pins 19/21/23/24/26) remain your primary interfaces, strictly operating at 3.3V logic. Feeding 5V into these pins will permanently destroy the RP1 chip.

Raspberry Pi 5 Connection Spec Sheet

Before wiring any sensors, you need to understand the electrical limits of the Pi 5's RP1 southbridge. Unlike the Pi 4, where the main SoC handled GPIO, the RP1 chip has specific current limits and internal pull-up configurations. Below is the data-dense reference table for the most commonly used interfaces on the 40-pin header.

Interface BCM GPIO Physical Pin Pi 5 Specific Notes & Limits
I2C1 SDA GPIO 2 3 1.8kΩ internal pull-ups to 3.3V. Max bus capacitance 400pF.
I2C1 SCL GPIO 3 5 1.8kΩ internal pull-ups to 3.3V. Standard mode (100kHz) default.
SPI0 MOSI GPIO 10 19 3.3V logic strictly. RP1 SPI clock can reach 125MHz, but limit to 10MHz for breadboards.
SPI0 MISO GPIO 9 21 3.3V logic strictly. 5V tolerance is non-existent on Pi 5 RP1.
UART0 TX GPIO 14 8 Defaults to Bluetooth on some OS configs; disable BT in config.txt to free for header.
PCIe CLK/REQ N/A FPC Ribbon 1-lane PCIe 2.0. Requires dtparam=pciex1 in config.txt for Gen 3 speeds.
Power Supply Gotcha: The Pi 5 requires a 27W USB-C PD (5V/5A) power supply to unlock full peripheral current limits. If you use a standard 5V/3A supply, the firmware automatically restricts the GPIO and USB current limits to prevent brownouts. If your I2C sensors are browning out or resetting, check your power supply negotiation first.

Project Build: I2C Environmental Data Logger

To put these connections to the test, we will wire a BME280 environmental sensor via I2C and write a Python script to log temperature, humidity, and pressure to a local CSV file. This build targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Bookworm 64-bit.

Parts List

  • Board: Raspberry Pi 5 (8GB RAM)
  • Power: Official Raspberry Pi 27W USB-C PD Power Supply
  • Sensor: Adafruit BME280 I2C Breakout (Product ID: 2652)
  • Wiring: 20cm Silicone Female-to-Female Jumper Wires (26 AWG)
  • OS: Raspberry Pi OS Bookworm 64-bit (Desktop or Lite)

Pin Mapping Table

BME280 Breakout Pin Pi 5 Physical Pin Wire Color (Standard)
VIN / VCC Pin 1 (3.3V Power) Red
GND Pin 6 (Ground) Black
SDA Pin 3 (GPIO 2) Blue
SCL Pin 5 (GPIO 3) Yellow

Wiring Steps

  1. De-energize: Unplug the Pi 5 USB-C power cable before touching the GPIO header.
  2. Connect Power: Plug the red wire from the BME280 VIN pin to Physical Pin 1 (3.3V) on the Pi 5. Never connect VIN to 5V (Pin 2) on this specific Adafruit breakout if you are using the I2C interface, as the onboard 3.3V regulator can overheat.
  3. Connect Ground: Plug the black wire from BME280 GND to Physical Pin 6.
  4. Connect Data: Connect SDA (Blue) to Pin 3, and SCL (Yellow) to Pin 5.
  5. Verify: Boot the Pi, open a terminal, and run sudo i2cdetect -y 1. You should see 77 (or 76 depending on the breakout batch) in the grid.

Python Data Logging Script

Before running the code, install the required Adafruit Blinka library and the BME280 driver via the terminal:

pip3 install adafruit-circuitpython-bme280

The following script reads the sensor every 5 seconds and appends the data to a CSV file. It includes robust error handling to catch I2C bus drops without crashing the loop.

import time
import csv
import board
import busio
import adafruit_bme280
import os
from datetime import datetime

# --- Pin Definitions (Pi 5 RP1 Southbridge Mapping) ---
# Physical Pin 3 (GPIO 2) -> I2C SDA
# Physical Pin 5 (GPIO 3) -> I2C SCL
I2C_SDA_PIN = board.SDA
I2C_SCL_PIN = board.SCL

# File path for logging
LOG_FILE = '/home/pi/environment_log.csv'

def setup_sensor():
    """Initialize I2C bus and BME280 sensor."""
    # Create I2C bus object with explicit pin definitions
    i2c = busio.I2C(I2C_SCL_PIN, I2C_SDA_PIN)
    
    # Default address is 0x77; use 0x76 if your breakout has the SDO pad bridged
    sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
    
    # Set oversampling for higher accuracy (takes slightly longer to read)
    sensor.oversampling_temperature = 2
    sensor.oversampling_pressure = 2
    sensor.oversampling_humidity = 2
    
    return sensor

def main():
    print('Initializing Raspberry Pi 5 I2C Logger...')
    sensor = setup_sensor()
    
    # Create CSV header if file doesn't exist
    if not os.path.exists(LOG_FILE):
        with open(LOG_FILE, 'w', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(['Timestamp', 'Temp_C', 'Humidity_%', 'Pressure_hPa'])
    
    print('Logging started. Press Ctrl+C to stop.')
    
    try:
        while True:
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            temp = sensor.temperature
            humidity = sensor.relative_humidity
            pressure = sensor.pressure
            
            # Append to CSV
            with open(LOG_FILE, 'a', newline='') as f:
                writer = csv.writer(f)
                writer.writerow([timestamp, f'{temp:.2f}', f'{humidity:.2f}', f'{pressure:.2f}'])
            
            print(f'[{timestamp}] T: {temp:.2f}C | H: {humidity:.2f}% | P: {pressure:.2f}hPa')
            time.sleep(5)
            
    except KeyboardInterrupt:
        print('\nLogging stopped by user.')
    except Exception as e:
        print(f'\nFatal error in main loop: {e}')

if __name__ == '__main__':
    main()

Debugging: Remote I/O Error and Connection Failures

When working with I2C on the Pi 5, the most common and frustrating failure is the bus dropping out or failing to initialize. If your script crashes or i2cdetect shows a blank grid, you will likely encounter this exact error string:

OSError: [Errno 121] Remote I/O error

This error means the Pi 5's RP1 chip sent a clock pulse and data bit on the I2C bus, but the sensor failed to pull the SDA line low to acknowledge (ACK) the transaction. Here are the first three things to check when this happens:

  1. Verify the Address with i2cdetect: Run sudo i2cdetect -y 1. If the grid is entirely blank (only dashes), your Pi isn't seeing the device at all. If you see UU, the kernel driver has already claimed the device, and user-space Python cannot access it.
  2. Measure VCC at the Breakout: Use a multimeter to measure voltage between the VIN and GND pins on the sensor breakout itself. It must read 3.3V. If it reads 0V, you have a broken jumper wire. If it reads 5V, you wired it to Pin 2 and may have already damaged the sensor's onboard regulator.
  3. Check for SDA/SCL Swap: I2C is not symmetric. If you accidentally swap SDA (Pin 3) and SCL (Pin 5), the clock line will sit idle, and the sensor will never wake up to acknowledge the address. Swap them and reboot.

Ranked Causes for Errno 121

If the three checks above pass and you still get the error intermittently during a long logging session, consult this ranked cause list:

Rank Cause Fix / Mitigation
1 Loose Dupont Connections Female-to-female jumper wires wear out. Replace with crimped JST-XH connectors or solder directly.
2 Missing Pull-up Resistors The Pi 5 has internal 1.8kΩ pull-ups, but long wires add capacitance. Add external 4.7kΩ pull-ups to 3.3V on both SDA and SCL.
3 I2C Bus Capacitance Overload If you have more than 3 devices on the same I2C bus, the capacitance exceeds 400pF. Use an I2C multiplexer (like the TCA9548A).
4 Power Supply Brownout The sensor resets during a Wi-Fi transmission spike. Ensure you are using the official 27W Pi 5 PSU, not a phone charger.

Extending and Simplifying the Build

Once you have the basic I2C logger running, you will inevitably want to scale the project. The Raspberry Pi 5 offers unique hardware features that change how you approach expansion compared to older models.

How to Simplify

If breadboard wiring and Errno 121 debugging are eating up your time, abandon raw jumper wires and use a HAT (Hardware Attached on Top). The Raspberry Pi Official HATs or the Pimoroni Enviro+ HAT plug directly into the 40-pin header. They include pre-routed I2C/SPI traces, onboard level shifters, and built-in pull-up resistors, eliminating 90% of physical connection failures. The trade-off is cost: a good environmental HAT costs around $45-$60, compared to $15 for raw breakouts and wire.

How to Extend

The biggest leap in Pi 5 connections is the new PCIe 2.0 FPC connector located near the USB ports. If you are building a remote weather station that logs high-frequency data, writing to a MicroSD card via SPI or USB will bottleneck your system and risk file corruption during power loss.

Instead, extend your build by adding a Pimoroni NVMe Base or the official Raspberry Pi M.2 HAT+. This connects an M.2 NVMe SSD directly to the PCIe lane, giving you boot speeds and write endurance that far outstrip USB thumb drives. To enable it, add dtparam=nvme and dtparam=pciex1_gen=3 to your /boot/firmware/config.txt file. This frees up your USB bus for external LTE modems and leaves the 40-pin header strictly dedicated to low-latency sensor polling.

For deeper technical specifications on the RP1 southbridge and GPIO limits, always refer to the Raspberry Pi Official Documentation and the Adafruit BME280 Guide for sensor-specific I2C timing requirements.