Getting your Raspberry Pi pin configuration right is the difference between a project that runs for years and one that bricks a $20 sensor on day one. The Raspberry Pi uses a 40-pin header that exposes power, ground, and GPIO channels, but the mapping between the physical pin numbers and the Broadcom (BCM) SoC channels trips up almost every builder at least once. Furthermore, the transition to the Raspberry Pi 5 introduced the RP1 southbridge chip, fundamentally changing how the OS handles GPIO access and rendering legacy libraries obsolete.
This guide targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (Bookworm, 64-bit). We will wire a BME280 environmental sensor via I2C and trigger a 5V relay module, providing a complete data-dense pinout table, production-ready Python code, and a deep-dive troubleshooting matrix for the most common hardware communication failures.
Raspberry Pi 5 Pin Mapping & BCM Numbering
When configuring your Raspberry Pi pins, you must choose between Physical (BOARD) numbering (1 through 40) and BCM numbering (the Broadcom SoC channel ID). Modern Python libraries like gpiozero default to BCM numbering. The table below maps the physical pins on the top-left and top-right of the header to their BCM equivalents for our specific build.
| Physical Pin | BCM GPIO | Function / Alt | Wiring Target | Hardware Notes & Limits |
|---|---|---|---|---|
| 1 | N/A | 3V3 Power | BME280 VIN | Max 50mA draw per pin. Fused. |
| 2 | N/A | 5V Power | Relay Module VCC | Pi 5 can supply up to 2A on the 5V GPIO rail. |
| 3 | 2 | SDA1 (I2C) | BME280 SDI | Requires pull-up resistors (BME280 has them onboard). |
| 5 | 3 | SCL1 (I2C) | BME280 SCK | Default I2C bus 1. Speed: 100kHz/400kHz. |
| 6 | N/A | Ground | Relay Module GND | Common ground required for logic reference. |
| 9 | N/A | Ground | BME280 GND | Use separate ground path to avoid I2C noise. |
| 12 | 18 | PCM_CLK / PWM0 | Relay Module IN | Hardware PWM capable. 3.3V logic output. |
| 14 | N/A | Ground | Spare / Shield | Always pair signal wires with an adjacent ground. |
import RPi.GPIO as GPIO, they will fail on Bookworm. Always use gpiozero or libgpiod for modern Pi 5 pin configuration.
Hardware Parts List & Wiring Steps
To build this environmental monitoring and control hub, you need exact components that respect the Pi's 3.3V logic levels while safely switching higher voltage loads.
- Microcontroller: Raspberry Pi 5 (8GB) - ~$80.00
- Sensor: Adafruit BME280 I2C Breakout (Product ID: 2652) - ~$19.95
- Actuator: 5V Relay Module (Songle SRD-05VDC-SL-C with optocoupler) - ~$6.00
- Wiring: 22 AWG stranded silicone wire, female-to-female Dupont jumpers for prototyping.
Numbered Wiring Procedure
- De-energize the Pi: Never hot-swap I2C sensors. Unplug the USB-C power supply before touching the GPIO header.
- Wire the BME280 (I2C): Connect Pi Pin 1 (3V3) to BME280 VIN. Connect Pi Pin 3 (SDA) to BME280 SDI. Connect Pi Pin 5 (SCL) to BME280 SCK. Connect Pi Pin 9 (GND) to BME280 GND.
- Wire the Relay Module (Control): Connect Pi Pin 12 (BCM 18) to the Relay Module "IN" pin. Connect Pi Pin 6 (GND) to the Relay Module "GND".
- Wire the Relay Module (Power): Connect Pi Pin 2 (5V) to the Relay Module "VCC".
Warning: Cheap relay modules often have a "JD-VCC" jumper. Leave this jumper IN place for this setup. If you remove it to isolate the coil power, you must provide a separate 5V power supply to the JD-VCC pins and tie the grounds together.
- Verify Connections: Use a multimeter in continuity mode to verify that your 3V3 and 5V lines are not shorted to ground before applying power.
Python Control Code with Error Handling
The following Python script reads temperature and humidity from the BME280 and triggers the relay if the temperature exceeds a threshold. It uses gpiozero for the relay (which natively supports the Pi 5's RP1 chip) and smbus2 for raw I2C communication.
Prerequisites: Run sudo apt install python3-gpiozero python3-smbus2 i2c-tools and enable I2C via sudo raspi-config.
import time
import sys
from gpiozero import OutputDevice
from smbus2 import SMBus
import bme280
# --- PIN & CONFIGURATION DEFINITIONS ---
RELAY_PIN = 18 # BCM 18 (Physical Pin 12)
I2C_BUS = 1 # Default I2C bus on Pi 4/5
BME280_ADDRESS = 0x77 # Default Adafruit BME280 address (0x76 for some clones)
TEMP_THRESHOLD = 28.0 # Celsius threshold to trigger relay
# Initialize Hardware
relay = OutputDevice(RELAY_PIN, active_high=True, initial_value=False)
bus = SMBus(I2C_BUS)
# Load BME280 calibration parameters
try:
calibration_params = bme280.load_calibration_params(bus, BME280_ADDRESS)
print(f"[INFO] BME280 initialized at address 0x{BME280_ADDRESS:02X}")
except Exception as e:
print(f"[FATAL] Failed to initialize BME280: {e}")
sys.exit(1)
def monitor_environment():
try:
while True:
# Read sensor data
data = bme280.sample(bus, BME280_ADDRESS, calibration_params)
temp_c = data.temperature
humidity = data.humidity
print(f"Temp: {temp_c:.2f}°C | Humidity: {humidity:.1f}%", end="")
# Control Logic
if temp_c >= TEMP_THRESHOLD:
if not relay.is_active:
relay.on()
print(" | [ACTION] Relay ENGAGED (Cooling ON)")
else:
print(" | [STATUS] Relay already active")
else:
if relay.is_active:
relay.off()
print(" | [ACTION] Relay DISENGAGED (Cooling OFF)")
else:
print(" | [STATUS] Temp nominal")
time.sleep(5)
except KeyboardInterrupt:
print("\n[INFO] Interrupt received. Safely shutting down...")
except OSError as e:
print(f"\n[ERROR] I2C Communication Failure: {e}")
finally:
# Ensure relay is off and pins are cleaned up on exit
relay.off()
relay.close()
bus.close()
print("[INFO] Hardware state reset. Exiting.")
if __name__ == "__main__":
monitor_environment()
Debugging Pin & I2C Communication Failures
When your Raspberry Pi pin configuration is physically correct but the software throws an error, the issue usually lies in bus addressing, pull-up resistor physics, or OS-level permissions. If your script crashes, here are the first three things to check:
- Run an I2C Bus Scan: Execute
sudo i2cdetect -y 1in the terminal. You should see77(or76) in the grid. If the grid is entirely empty or full ofUU, your I2C wiring or pull-ups are failing. - Measure the I2C Voltage: Put your multimeter in DC Voltage mode. Probe the SDA and SCL lines while the Pi is idle. You should read ~3.3V. If you read 0V or a fluctuating low voltage, your sensor lacks pull-up resistors or the Pi's I2C interface is disabled in
raspi-config. - Check the Ground Reference: Ensure the ground wire from the Pi (Pin 6 or 9) is sharing the exact same ground plane as the sensor. A missing ground reference causes the I2C data line to float, resulting in random NACK errors.
Ranked Error Matrix & Fixes
| Exact Error String | Root Cause | Hardware / Software Fix |
|---|---|---|
OSError: [Errno 121] Remote I/O error |
I2C NACK. The Pi sent a request, but the sensor did not acknowledge. Usually caused by wrong address, missing pull-ups, or a loose SDA wire. | Verify address with i2cdetect. If using a clone BME280, change BME280_ADDRESS to 0x76. Check solder joints on the breakout header. |
RuntimeError: No access to /dev/mem. Try running as root! |
Legacy library conflict. You are trying to use RPi.GPIO on a Pi 5, or the user lacks i2c group permissions. |
Switch to gpiozero (as shown in our code). Add your user to the i2c group: sudo usermod -aG i2c $USER and reboot. |
gpiozero.exc.PinFactoryFallback: Falling back to... |
The preferred pin factory (lgpio/libgpiod) isn't installed, forcing gpiozero to use a slower or incompatible fallback. | Install the native Pi 5 backend: sudo apt install python3-lgpio. |
FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1' |
The I2C hardware interface is disabled at the OS level, or you are targeting the wrong bus number. | Run sudo raspi-config -> Interface Options -> I2C -> Enable. Reboot the Pi. |
Scaling the Build: Extensions & Simplifications
Once you have the baseline Raspberry Pi pin configuration working, you will inevitably want to modify the hardware footprint. Here is how to adapt the circuit based on your project constraints.
How to Extend the Build (Multiple Sensors)
The I2C bus allows up to 127 devices, but the BME280 only has two selectable addresses (0x76 and 0x77). To add a third or fourth environmental sensor, you cannot just wire them in parallel. The Fix: Insert a TCA9548A I2C Multiplexer (Adafruit Product ID: 2717, ~$7.50) between the Pi and the sensors. The multiplexer sits at address 0x70, and you use Python to toggle its internal switches, effectively creating 8 separate I2C buses. This prevents address collisions and isolates bus capacitance, which is critical if your sensor wires exceed 30cm in length.
How to Simplify the Build (Status LED)
If you don't need to switch a high-current load and just want a visual indicator, strip out the 5V relay module entirely. Replace it with a standard 5mm LED and a 330Ω current-limiting resistor. Wire the LED anode to the resistor, the resistor to BCM 18 (Pin 12), and the LED cathode to Pin 14 (GND). In the Python code, swap OutputDevice for LED(18) from the gpiozero library. This removes the 5V power draw and eliminates the risk of back-EMF voltage spikes from the relay coil.
For more details on I2C bus physics and pull-up resistor calculations, refer to the Adafruit BME280 Guide and the official Raspberry Pi Hardware Documentation. Always verify your specific GPIO library compatibility via the gpiozero documentation before deploying to a headless production environment.






