The primary Raspberry Pi connector for hardware integration is the 40-pin GPIO header. It provides 3.3V logic pins, 5V power rails, and dedicated hardware buses for I2C, SPI, and UART communication. If you are connecting standard environmental sensors or displays, I2C on pins 3 (SDA) and 5 (SCL) is the default and most reliable choice. For high-throughput data like TFT displays or ADCs, you will step up to the SPI0 connector cluster. Understanding the exact voltage limits and bus assignments of these connectors is the difference between a clean boot and a fried logic gate.

The 40-Pin GPIO Header: Pin Mapping and Power Limits

While the physical 40-pin layout has remained mechanically identical since the Pi 1 Model B+, the electrical characteristics—especially on the Raspberry Pi 5—have evolved. The Pi 5 requires a 27W USB-C PD power supply to unlock the full current capacity of the 5V pins. Below is the functional mapping for the pins you will actually use in embedded projects.

Pin #BCM GPIOFunctionNotes & Limits
1N/A3.3V PowerMax 50mA total draw across all 3.3V pins. Use for logic pull-ups only.
2, 4N/A5V PowerPi 4: ~1.2A max. Pi 5 (with 27W PSU): up to 2A max. Direct from USB-C.
3GPIO 2I2C1 SDAHardware I2C Data. Includes 1.8kΩ on-board pull-ups to 3.3V.
5GPIO 3I2C1 SCLHardware I2C Clock. Includes 1.8kΩ on-board pull-ups to 3.3V.
19, 21, 23GPIO 10, 9, 11SPI0 MOSI, MISO, SCLKHardware SPI0. Use for high-speed displays and ADCs.
24, 26GPIO 8, 7SPI0 CE0, CE1Chip Enable (Chip Select) for SPI devices.
8, 10GPIO 14, 15UART0 TX, RX3.3V logic. Pi 5 Note: Default console UART moved; these map to UART1/PL011.
27, 28GPIO 0, 1I2C0 SDA, SCLReserved for HAT EEPROM ID. Do not use for general sensors.
Bench Warning: Never feed 5V logic into the Pi's GPIO pins. The Raspberry Pi SoC operates strictly at 3.3V. Connecting a 5V Arduino sensor directly to Pin 3 (SDA) without a logic level shifter will backfeed the 3.3V rail and can permanently damage the PMIC or SoC.

Decision Tree: Which Raspberry Pi Connector Protocol to Use?

Choosing the right connector protocol prevents bottlenecking your CPU with bit-banged software protocols. Use this decision path to select your bus and a concrete reference part.

If your project needs...Then use this connector/busConcrete Part Pick
Low speed data (<400kHz), multiple sensors, minimal wiringI2C1 (Pins 3, 5)Bosch BME280 (Temp/Humidity/Pressure)
High speed data (>1MHz), single device, pixel dataSPI0 (Pins 19, 21, 23, 24)ILI9341 2.4" TFT LCD Display
Raw analog voltage reading (0-3.3V or 0-5V)I2C1 via external ADCTexas Instruments ADS1115 16-bit ADC
GPS NMEA sentences or serial console debuggingUART (Pins 8, 10)u-blox NEO-6M GPS Module

Default Recommendation: For 90% of maker environmental and telemetry builds, terminate your decision at I2C1. It requires only two wires, supports up to 127 addresses, and the Pi's hardware I2C controller handles the clock stretching natively.

Hardware Build: Interfacing a BME280 via I2C Connectors

Difficulty: Beginner | Time: 15 Minutes | Target Board: Raspberry Pi 5 (8GB) running Raspberry Pi OS (Bookworm)

We will wire a Bosch BME280 environmental sensor to the Pi's I2C1 connector. This build assumes you are using a modern breakout board with integrated pull-up resistors.

Parts List

  • Board: Raspberry Pi 5 (8GB variant) with 27W USB-C PD Power Supply
  • Sensor: Adafruit BME280 Breakout (STEMMA QT / Qwiic variant, Product ID: 2652)
  • Wiring: 4x Female-to-Female jumper wires (or STEMMA QT to Pi GPIO cable)
  • Software: Python 3.11+, smbus2 library

Wiring Steps

  1. De-energize: Unplug the Raspberry Pi's USB-C power cable. Never wire GPIO connectors while the board is powered.
  2. Connect VCC: Connect the BME280 VIN pin to the Pi's Pin 1 (3.3V). Do not use 5V unless the breakout explicitly has an onboard 3.3V LDO regulator.
  3. Connect GND: Connect the BME280 GND pin to the Pi's Pin 6 (Ground).
  4. Connect SDA: Connect the BME280 SDA pin to the Pi's Pin 3 (GPIO 2 / SDA1).
  5. Connect SCL: Connect the BME280 SCL pin to the Pi's Pin 5 (GPIO 3 / SCL1).
  6. Verify: Tug gently on each Dupont connector to ensure the metal grabber has seated past the plastic shroud of the Pi's header pins.

Python Code: Reading I2C with Error Handling

Before running the code, enable the I2C interface via sudo raspi-config (Interface Options > I2C > Enable), reboot, and install the SMBus library: sudo apt install python3-smbus2.

This script targets the Raspberry Pi 5 (and is fully backward compatible with Pi 4 and Zero 2 W). It reads the BME280 chip ID register to verify the I2C connection before attempting to parse environmental data.

import smbus2
import time
import sys

# --- Pin & Bus Definitions ---
# I2C Bus 1 corresponds to physical pins 3 (SDA) and 5 (SCL)
I2C_BUS = 1
# Default BME280 I2C address (0x76 if SDO is tied to GND, 0x77 if tied to VCC)
BME280_ADDR = 0x76
BME280_CHIP_ID_REG = 0xD0
EXPECTED_CHIP_ID = 0x60

def initialize_sensor(bus, address):
    """Reads the Chip ID register to verify I2C communication."""
    try:
        chip_id = bus.read_byte_data(address, BME280_CHIP_ID_REG)
        if chip_id == EXPECTED_CHIP_ID:
            print(f'Success: BME280 found at 0x{address:02X} (Chip ID: 0x{chip_id:02X})')
            return True
        else:
            print(f'Error: Device found but wrong Chip ID: 0x{chip_id:02X}')
            return False
    except OSError as e:
        handle_i2c_error(e)
        return False

def handle_i2c_error(error):
    """Parses specific I2C bus errors for debugging."""
    if error.errno == 121:
        print('Fatal I2C Error: [Errno 121] Remote I/O error.')
        print('-> Cause: NACK received. The Pi sent a clock pulse, but the sensor did not respond.')
    elif error.errno == 122:
        print('Fatal I2C Error: [Errno 122] Host is down.')
        print('-> Cause: I2C bus is locked or hardware fault on SCL line.')
    elif error.errno == 16:
        print('Fatal I2C Error: [Errno 16] Device or resource busy.')
        print('-> Cause: Another process is holding the /dev/i2c-1 file descriptor.')
    else:
        print(f'Unexpected I2C OSError: {error}')

if __name__ == '__main__':
    try:
        # Initialize SMBus on Bus 1
        bus = smbus2.SMBus(I2C_BUS)
        print('Scanning I2C bus...')
        
        if initialize_sensor(bus, BME280_ADDR):
            # Placeholder for continuous read loop
            print('Sensor initialized. Ready for data polling.')
        else:
            print('Sensor initialization failed. Check wiring.')
            sys.exit(1)
            
    except FileNotFoundError:
        print('Error: /dev/i2c-1 not found. Is I2C enabled in raspi-config?')
        sys.exit(1)
    except Exception as e:
        print(f'Unhandled exception: {e}')
        sys.exit(1)
    finally:
        if 'bus' in locals():
            bus.close()

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

If your script crashes with OSError: [Errno 121] Remote I/O error, the Linux kernel is telling you that the I2C master (the Pi) sent an address byte, but the slave (your sensor) failed to pull the SDA line low on the 9th clock cycle (a NACK). This is a physical layer failure, not a Python syntax error.

The First Three Things to Check

  1. Run i2cdetect: Execute sudo i2cdetect -y 1 in the terminal. If the grid is entirely empty (only dashes), your wiring or power is dead. If you see UU, a kernel driver has already claimed the address. If you see 76 or 77, the hardware is fine and your Python address variable is wrong.
  2. Verify Logic Levels: Measure the voltage between the sensor's VCC and GND pins with a multimeter. It must read 3.3V. If it reads 0V, your jumper wire is broken or the Pi's 3.3V rail is starved.
  3. Check Pull-Up Resistors: The Pi's internal pull-ups on Pins 3 and 5 are ~1.8kΩ. If you are using long wires (>30cm) or multiple sensors, the bus capacitance rises, and the edges become too slow. You may need external 4.7kΩ pull-ups to 3.3V.

Ranked Causes for Errno 121

  • Cause 1 (60% of cases): Wrong I2C Address. The BME280 can be 0x76 or 0x77 depending on the breakout board manufacturer. Check the silkscreen on your PCB.
  • Cause 2 (20% of cases): SDA/SCL Swapped. You wired Pin 3 to SCL and Pin 5 to SDA. I2C is not bidirectional in pin assignment; swap them.
  • Cause 3 (15% of cases): Missing Ground. The Pi and the sensor do not share a common ground reference, causing the logic high/low thresholds to misalign.
  • Cause 4 (5% of cases): Dead Sensor. The sensor's internal voltage regulator or I2C state machine is fried, often from a previous 5V overvoltage event.

Extending and Simplifying Your Pi Connector Build

Once you have a stable baseline I2C connection, you will inevitably hit the limits of the 40-pin header's native routing. Here is how to scale your project without rewriting your stack.

How to Simplify: Adopt STEMMA QT / Qwiic

Stop using raw Dupont jumper wires for I2C. The Adafruit STEMMA QT and SparkFun Qwiic ecosystems use a standardized 4-pin JST-SH connector. By purchasing a "STEMMA QT to Raspberry Pi GPIO" cable, you eliminate reversed-pin wiring errors entirely. The physical keying prevents swapping SDA and SCL, and the connectors lock in place, surviving the vibration of robotics or 3D printer enclosures.

How to Extend: I2C Multiplexing

The I2C protocol limits you to one device per address. If you need to connect three identical BME280 sensors (all hardcoded to 0x76), you cannot just wire them in parallel.

The Fix: Insert a TCA9548A I2C Multiplexer between the Pi's Pins 3/5 and your sensors. The TCA9548A sits at address 0x70 and acts as an 8-channel hardware switch. You send an I2C command to the mux to enable Channel 1, read Sensor A, switch to Channel 2, and read Sensor B. This allows you to connect up to 64 identical sensors to a single pair of Raspberry Pi I2C connector pins.

For further hardware reference, consult the Raspberry Pi Official Documentation for Pi 5 specific PCIe and UART routing changes, and keep Pinout.xyz bookmarked on your bench monitor for instant BCM-to-physical pin translation.