The 40-Pin Raspberry Pi Pin Diagram: 2026 Header Breakdown

If you are wiring up a new project, the direct answer to navigating the Raspberry Pi pin diagram is this: the physical 40-pin header layout remains identical across the Raspberry Pi 4 Model B and the Raspberry Pi 5. You have 26 usable GPIO pins, four power pins (two 5V, two 3.3V), eight ground pins, and dedicated hardware buses for I2C, SPI, and UART.

However, the underlying silicon changed drastically with the Pi 5. The Pi 5 routes its GPIO through the custom RP1 southbridge chip rather than the Broadcom SoC. While the physical pinout diagram is the same, this architectural shift means legacy Python libraries like RPi.GPIO will fail on a Pi 5. For any new build in 2026, you must use gpiozero (which leverages the lgpio backend) to ensure your code runs on both Pi 4 and Pi 5 hardware.

Safety & Hardware Warning: The GPIO pins operate at 3.3V logic. Feeding a 5V signal into any GPIO pin (including I2C/SPI lines) will permanently destroy the SoC on a Pi 4, and can damage the RP1 chip on a Pi 5. Always use a logic level shifter if interfacing with 5V Arduino-style sensors.

When reading the diagram, you will encounter two numbering systems: Physical (Board) and BCM (Broadcom). Physical numbering simply counts 1 through 40 down the header. BCM numbering maps to the internal silicon registers. In Python, always configure your libraries to use BCM numbering to prevent mapping errors when moving code between different Pi revisions.

Decision Tree: Choosing the Right Pins for Your Build

Do not just grab random pins from the header. Use this decision path to select the correct interface for your sensors and actuators, terminating in the optimal pin choices for a standard environmental monitor.

Your Requirement Interface Choice Physical Pins BCM Pins
Need to read an analog sensor (e.g., soil moisture)? SPI (via MCP3008 ADC) 19, 21, 23, 24 10, 9, 11, 8
Need high-speed data (e.g., TFT display)? SPI0 19, 21, 23, 24, 26 10, 9, 11, 8, 7
Need simple on/off or hardware PWM (e.g., LED dimming)? Standard GPIO 12 18 (Hardware PWM0)
Need serial console or GPS module? UART 8, 10 14, 15
Need digital environmental/IMU sensors? I2C1 3, 5 2, 3
The Concrete Pick: For a standard telemetry or environmental build, terminate your decision here: Use I2C1 (BCM 2 and 3) for your digital sensors, and BCM 18 for hardware PWM output. This leaves the SPI and UART buses completely free for future expansion.

Project Build: I2C BME280 Environmental Monitor with PWM Status LED

This build targets the Raspberry Pi 4 Model B (4GB) and Raspberry Pi 5 (4GB) running Raspberry Pi OS (Bookworm or newer). We will wire a BME280 temperature/humidity sensor via I2C, a hardware-PWM LED for status indication, and a button to trigger a manual reading.

Parts List

  • Board: Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (4GB)
  • Sensor: Adafruit BME280 I2C Breakout (Part #2652) or equivalent 3.3V I2C BME280
  • Indicator: Standard 5mm Red LED
  • Resistor: 330Ω (for LED current limiting)
  • Input: 6x6mm Tactile Pushbutton
  • Wiring: Female-to-Female Dupont jumper wires

Pin Mapping Table

Component Component Pin Pi Physical Pin Pi BCM Pin Function
BME280 VIN 1 N/A 3.3V Power
BME280 GND 6 N/A Ground
BME280 SCL 5 3 I2C Clock
BME280 SDA 3 2 I2C Data
LED Anode (via 330Ω) 12 18 Hardware PWM0
LED Cathode 14 N/A Ground
Button Leg 1 16 23 GPIO Input (Pull-up)
Button Leg 2 20 N/A Ground

Wiring Steps

  1. Enable I2C: Open terminal and run sudo raspi-config. Navigate to Interface Options > I2C > Enable. Reboot the Pi.
  2. Wire Power & Ground: Connect the BME280 VIN to Physical Pin 1 (3.3V) and GND to Physical Pin 6. Never wire 5V to the BME280 VIN unless your specific breakout has an onboard voltage regulator.
  3. Wire I2C Bus: Connect SCL to Pin 5 and SDA to Pin 3. The Pi has internal 1.8kΩ pull-up resistors on these lines, so standard breakouts work without external resistors.
  4. Wire Actuator & Input: Connect the LED anode to Pin 12 through the 330Ω resistor, and cathode to Pin 14. Wire the pushbutton between Pin 16 and Pin 20.
  5. Verify Hardware: Run i2cdetect -y 1 in the terminal. You should see 76 or 77 in the grid output, confirming the BME280 is physically seen by the I2C controller.

Complete Python Code with Error Handling

The following script uses gpiozero for the LED and button, and smbus2 for raw I2C communication. This avoids the C-extension compilation issues common with older sensor libraries on the Pi 5's ARM64 architecture. Install dependencies via terminal: sudo apt install python3-gpiozero python3-smbus2.

import sys
import time
from gpiozero import PWMLED, Button
from gpiozero.exc import PinFactoryFallback
from smbus2 import SMBus, i2c_msg

# --- PIN DEFINITIONS (BCM Numbering) ---
PWM_LED_PIN = 18      # Physical Pin 12
BUTTON_PIN = 23       # Physical Pin 16
I2C_BUS_ID = 1        # I2C1 bus
BME280_I2C_ADDR = 0x76 # Default for Adafruit; use 0x77 if i2cdetect shows 77

# --- HARDWARE INITIALIZATION ---
try:
    # PWMLED defaults to 100Hz, which is fine for visual indication
    status_led = PWMLED(PWM_LED_PIN)
    # pull_up=True uses the Pi's internal pull-up resistor; button connects to GND
    read_button = Button(BUTTON_PIN, pull_up=True, bounce_time=0.05)
    bus = SMBus(I2C_BUS_ID)
except Exception as e:
    print(f"[FATAL] Hardware initialization failed: {e}")
    sys.exit(1)

def verify_sensor():
    """Reads the BME280 Chip ID register (0xD0) to verify I2C connection."""
    try:
        # Write 1 byte to register 0xD0, then read 1 byte back
        msg = i2c_msg.write(BME280_I2C_ADDR, [0xD0])
        bus.i2c_rdwr(msg)
        msg = i2c_msg.read(BME280_I2C_ADDR, 1)
        bus.i2c_rdwr(msg)
        chip_id = list(msg)[0]
        
        if chip_id == 0x60:
            print(f"[INFO] BME280 detected successfully (Chip ID: 0x{chip_id:02X})")
            return True
        else:
            print(f"[WARN] Unexpected Chip ID: 0x{chip_id:02X}. Sensor may be incompatible.")
            return False
    except OSError as e:
        print(f"[ERROR] I2C Communication Failed: {e}")
        return False

def pulse_led_success():
    """Visual feedback for successful read."""
    status_led.pulse(fade_in_time=0.2, fade_out_time=0.2, n=1, background=False)

def main():
    print("Starting Environmental Monitor. Press Ctrl+C to exit.")
    if not verify_sensor():
        print("[HALT] Fix I2C wiring and restart.")
        sys.exit(1)

    # Turn LED on to 10% brightness to indicate standby
    status_led.value = 0.1 

    try:
        while True:
            if read_button.is_pressed:
                print("[ACTION] Button pressed. Polling sensor registers...")
                status_led.value = 1.0 # Full brightness during read
                
                # In a production build, you would read registers 0xF7-0xFE 
                # and apply the compensation algorithm from the Bosch datasheet here.
                # For this diagnostic script, we confirm the bus is stable.
                time.sleep(0.1) 
                pulse_led_success()
                print("[OK] Bus stable. Ready for full compensation logic.")
                status_led.value = 0.1 # Return to standby
                
            time.sleep(0.05) # Debounce loop delay
            
    except KeyboardInterrupt:
        print("\n[INFO] Shutting down gracefully.")
    finally:
        status_led.off()
        bus.close()

if __name__ == "__main__":
    main()

Debugging: "Remote I/O Error" and GPIO Failures

When working with the 40-pin header, physical layer failures are vastly more common than software bugs. If your script crashes, check these first three things:

  1. Run i2cdetect -y 1: If the grid is empty, your issue is physical wiring or disabled I2C in raspi-config. Do not debug Python until the OS sees the hardware.
  2. Check Dupont Wire Seating: Female-to-female jumper wires suffer from metal fatigue. The internal crimp often breaks while the plastic housing looks intact. Swap your wires with known-good ones.
  3. Verify 3.3V Rail Health: Use a multimeter to measure between Physical Pin 1 (3.3V) and Pin 6 (GND). If it reads below 3.2V, your Pi's power supply is browning out under load, causing the I2C controller to drop packets.

Common Error Strings and Ranked Causes

Error 1: OSError: [Errno 121] Remote I/O error

This is the universal I2C failure code in Python. The Pi's I2C controller sent a clock pulse but received no acknowledgment (ACK) from the sensor.

  • Cause 1 (Most Likely): I2C address mismatch. The BME280 defaults to 0x76 or 0x77 depending on the manufacturer. Check your i2cdetect output and update the BME280_I2C_ADDR variable in the code.
  • Cause 2: SDA and SCL wires are swapped. I2C is not auto-polarizing; cross them and the bus locks up.
  • Cause 3: Missing pull-up resistors on cheap clone sensors. While the Pi has internal pull-ups, wire capacitance over 30cm can corrupt the signal. Add external 4.7kΩ pull-ups to the 3.3V rail.

Error 2: RuntimeError: No access to /dev/mem. Try running as root!

  • Cause 1: You are using the deprecated RPi.GPIO library on Raspberry Pi OS Bookworm or on a Pi 5. The OS security model no longer allows user-space memory mapping for GPIO.
  • Fix: Rewrite your code using gpiozero as shown in the script above, which uses the lgpio backend and respects modern OS permissions without requiring sudo.

Extending and Simplifying the Build

Depending on your project timeline and budget, you can scale this hardware configuration up or down without rewriting your core logic.

How to Simplify (The Bench Test)

If you are just learning the pin diagram and want to verify your Python environment without buying sensors, strip the build down to the button and LED. Delete the smbus2 imports and the verify_sensor() function. Use the button press to toggle the LED state. This isolates your software logic from I2C hardware variables, proving your gpiozero installation is healthy.

How to Extend (Adding Telemetry)

To turn this into a standalone kiosk, add an SPI OLED display. Because we intentionally left the SPI0 bus (Physical Pins 19, 21, 23, 24) untouched in our decision tree, you can plug in an Adafruit 128x64 SPI OLED (Part #938) directly to the header. You will need to enable SPI via raspi-config and install the adafruit-circuitpython-ssd1306 library. The OLED will draw roughly 20mA, which is well within the safe limits of the Pi's 3.3V rail, allowing you to render real-time temperature graphs right on the breadboard.