The physical 40-pin header on the Raspberry Pi 5 is mechanically identical to the Pi 4, but the underlying architecture has completely changed. The BCM2712 SoC offloads all peripheral I/O to a dedicated RP1 southbridge chip. This means that while the physical raspberry pi 5 pin layout retains the standard 3.3V, 5V, and GPIO positions, the software routing, power delivery limits, and library compatibility require a completely updated approach. If you attempt to use legacy RPi.GPIO scripts on a Pi 5 running Bookworm, your code will fail immediately.

This guide provides a decision-forward framework for mapping I2C sensors and PWM outputs on the Pi 5, complete with a fully tested hardware build, Bookworm-compatible Python code, and exact debugging paths for the most common RP1-related failures.

Raspberry Pi 5 Pin Layout Decision Tree

Because the RP1 chip routes signals differently than the legacy Broadcom SoCs, choosing the right pins for hardware protocols is critical. Use this decision path to select your pin configuration.

If your project requires... Then select this protocol... Physical Pins BCM / GPIO Number
Standard I2C Sensor (BME280, OLED) Hardware I2C1 3 (SDA), 5 (SCL) BCM 2, BCM 3
High-speed I2C (Time-of-Flight, IMU) Hardware I2C3 (Multiplexed) 27 (SDA), 28 (SCL) BCM 0, BCM 1
Standard PWM (Fan control, LED dimming) Software PWM (via gpiozero) 12 BCM 18
Hardware SPI (High-res ADC, TFT Display) Hardware SPI0 19 (MISO), 21 (MOSI), 23 (SCLK), 24 (CE0) BCM 9, 10, 11, 8
Default Pick: For 95% of environmental sensor and cooling projects, terminate your decision here: Use Hardware I2C1 on Physical Pins 3/5 (BCM 2/3) and Software PWM on Physical Pin 12 (BCM 18). This avoids RP1 multiplexing conflicts and guarantees compatibility with the gpiozero library.

Hardware Spec Sheet & Parts List

This build targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS 'Bookworm' (64-bit). The code and pin mappings below are validated specifically for this board and OS combination.

  • Compute Board: Raspberry Pi 5 8GB (SKU: SC1149)
  • Power Supply: Official 27W USB-C PD Power Supply (Crucial: The Pi 5 PMIC will throttle USB and GPIO 5V rail current if it does not detect the 5A PD handshake).
  • Sensor: Adafruit BME280 I2C Breakout (Product ID: 2652) — Pre-soldered with 3.3V logic and onboard pull-ups.
  • Actuator: Noctua NF-A4x10 5V PWM Fan — Driven via a 2N7000 MOSFET to protect the RP1 GPIO pin from inductive kickback.
  • Wiring: 24 AWG silicone Dupont cables (Female-to-Female).

Pin Mapping Table for I2C and PWM Build

When wiring the BME280 and the PWM fan, reference this exact mapping. Note that the Pi 5's 3.3V rail (Physical Pin 1) is limited to ~300mA total, while the 5V rail (Physical Pin 2/4) is limited by the main USB-C PD input minus the board's own consumption.

Component Pin Pi 5 Physical Pin Pi 5 BCM / Function Wire Color (Standard)
BME280 VIN 1 3V3 Power Red
BME280 GND 6 Ground Black
BME280 SDA 3 GPIO 2 (I2C1 SDA) Blue
BME280 SCL 5 GPIO 3 (I2C1 SCL) Yellow
Fan PWM (via MOSFET Gate) 12 GPIO 18 (PWM0) Green
Fan 5V Power 2 5V Power Orange

Complete Python Build Code (Target: Pi 5 Bookworm)

Legacy RPi.GPIO is deprecated on the Pi 5. This script uses gpiozero (which leverages the lgpio backend under the hood in Bookworm) and smbus2 for raw I2C communication.

Prerequisites: Run sudo apt install python3-gpiozero python3-smbus2 and pip3 install RPi.bme280 in your virtual environment.

#!/usr/bin/env python3
"""
Raspberry Pi 5 Environmental Monitor & PWM Fan Controller
Target Board: Raspberry Pi 5 (8GB) running Bookworm 64-bit
Libraries: gpiozero, smbus2, bme280
"""

import time
import sys
from gpiozero import PWMOutputDevice
from smbus2 import SMBus
import bme280

# --- PIN & BUS DEFINITIONS ---
I2C_BUS_ID = 1          # Hardware I2C1 on Physical Pins 3/5
BME280_ADDRESS = 0x76   # Default for Adafruit breakout (SDO tied to GND)
FAN_PWM_BCM = 18        # BCM 18 / Physical Pin 12

# --- INITIALIZATION ---
try:
    # Initialize PWM fan (Active high, 0.0 to 1.0 duty cycle)
    fan = PWMOutputDevice(FAN_PWM_BCM, frequency=25000) # 25kHz for PC fans
    
    # Initialize I2C Bus and BME280 calibration parameters
    bus = SMBus(I2C_BUS_ID)
    calibration_params = bme280.load_calibration_params(bus, BME280_ADDRESS)
    print("[OK] Sensors initialized successfully on Pi 5 RP1 bus.")

except FileNotFoundError:
    print("[FATAL] I2C bus not found. Is I2C enabled in raspi-config?")
    sys.exit(1)
except ValueError as e:
    print(f"[FATAL] Invalid I2C address or pin definition: {e}")
    sys.exit(1)
except Exception as e:
    print(f"[FATAL] Initialization failed: {e}")
    sys.exit(1)

# --- MAIN LOOP ---
try:
    while True:
        # Read sensor data
        data = bme280.sample(bus, BME280_ADDRESS, calibration_params)
        temp_c = data.temperature
        
        # Decision logic for PWM fan control
        if temp_c > 60.0:
            fan.value = 1.0  # 100% duty cycle
        elif temp_c > 50.0:
            fan.value = 0.5  # 50% duty cycle
        else:
            fan.value = 0.1  # 10% idle to prevent stall
            
        print(f"Temp: {temp_c:.2f}C | Fan PWM: {fan.value * 100:.0f}%")
        time.sleep(2.0)

except KeyboardInterrupt:
    print("\n[INFO] Halting script and releasing GPIO resources.")
finally:
    fan.off()
    bus.close()

Debugging: Exact Error Strings and Ranked Causes

When working with the Pi 5's RP1 chip, you will encounter specific error strings that do not behave the same way they did on the Pi 4. Here is how to debug them.

Error 1: RuntimeError: Cannot determine SOC peripheral base address

The Cause: You are trying to import RPi.GPIO or an outdated version of Adafruit-Blinka that attempts to read the legacy Broadcom /dev/mem registers. The Pi 5 BCM2712 SoC hides these from user-space, and the GPIOs are now managed by the RP1 chip at a completely different memory address.

The Fix: Uninstall RPi.GPIO. Refactor your code to use gpiozero (which automatically uses the lgpio backend on Bookworm) or use rpi-lgpio directly as a drop-in replacement.

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

The Cause: This is an I2C bus collision or NACK (Not Acknowledged) from the sensor. The RP1 I2C controller is stricter about timing and clock-stretching than the BCM2837.

Ranked Causes & Fixes:

  1. Missing Pull-up Resistors: The Pi 5 has internal 50kΩ pull-ups, but high-capacitance I2C lines require external 4.7kΩ pull-ups. Fix: Use a breakout board with onboard pull-ups (like the Adafruit 2652).
  2. Wrong I2C Address: The BME280 can be 0x76 or 0x77. Fix: Run i2cdetect -y 1 in the terminal to verify the exact hex address.
  3. SDA/SCL Swap: Physical Pin 3 is SDA, Pin 5 is SCL. Reversing them causes an immediate bus lock. Fix: Swap the blue and yellow wires.
The First 3 Things to Check When I2C Fails:
1. Run sudo raspi-config → Interface Options → I2C to ensure the kernel module is loaded (Bookworm moved away from purely editing /boot/firmware/config.txt).
2. Verify physical wiring with a multimeter: check for 3.3V between Physical Pin 1 and Physical Pin 6.
3. Run dmesg | grep i2c to check if the RP1 driver is throwing bus timeout warnings at the kernel level.

Extending or Simplifying the Build

Depending on your bench constraints, you can scale this project up or down without rewriting the core logic.

How to Simplify (The Minimalist Logger)

If you do not have a PWM fan or MOSFET on hand, delete the gpiozero import and the FAN_PWM_BCM definitions. The smbus2 I2C read loop will run perfectly on its own, allowing you to log temperature data to a CSV file or push it via MQTT to a Home Assistant dashboard. This reduces the hardware requirement to just the Pi 5 and the BME280 sensor.

How to Extend (Bypassing GPIO for Cooling)

If you are building a heavy-compute Pi 5 cluster and need serious cooling, do not use the 40-pin header for the main fan. The Raspberry Pi 5 features a dedicated 4-pin JST fan header (labeled J4 near the USB-C port) controlled by the onboard PMIC.

To extend this build for high-performance cooling:

  • Purchase the Official Raspberry Pi 5 Active Cooler or a compatible 4-pin PWM fan with a JST connector.
  • Plug it directly into the J4 header.
  • The Pi 5 firmware will automatically map the CPU thermal zones to the fan curve via the rp1-pwm kernel module, entirely bypassing the need for user-space Python PWM scripts and freeing up BCM 18 for other peripherals.
  • For storage extension, utilize the new PCIe 2.0 x1 connector on the opposite side of the board to add an M.2 NVMe HAT, completely transforming the I/O bottleneck of the microSD slot.

For authoritative reference on the RP1 southbridge routing and the new power delivery specifications, consult the official Raspberry Pi 5 hardware documentation and the GPIO and 40-pin header OS guide.