The 2026 Standard: Setting Up Raspberry Pi 5 for Hardware
If you are searching for how to setup the raspberry pi for physical computing, the legacy tutorials you find from 2022 will likely break your workflow. The Raspberry Pi 5 uses the new RP1 southbridge chip, meaning the classic RPi.GPIO Python library is officially deprecated and will throw architecture errors on Raspberry Pi OS Bookworm.
To properly setup the Raspberry Pi 5 for embedded hardware today, you need the 64-bit Bookworm OS, the gpiozero library (which uses the rpi-lgpio backend under the hood), and smbus2 for I2C communication. This guide walks through setting up a Pi 5 to read a BME280 environmental sensor over I2C and trigger a 5V relay via GPIO, complete with the exact pin mappings, compilable code, and the specific error strings you will encounter on the bench.
Time to Complete: 45 minutes
Target Board: Raspberry Pi 5 (8GB variant, BCM2712 SoC with RP1 southbridge)
Hardware Spec Sheet & Pin Mapping
Before flashing the SD card, verify your components. The Pi 5 requires a USB-C Power Delivery (PD) supply capable of 5V/5A (27W) to prevent brownouts when switching inductive loads like relays. Below is the exact bill of materials and the physical-to-BCM pin mapping required for this build.
| Component | Exact Variant / Model | Specs & Notes | Approx. Cost |
|---|---|---|---|
| Microcontroller | Raspberry Pi 5 (8GB) | BCM2712, RP1 southbridge, requires active cooling | $80.00 |
| Power Supply | Official Raspberry Pi 27W USB-C PD | 5V/5A. Prevents PCIe/GPIO current limiting | $12.00 |
| Sensor | Adafruit BME280 (Product 2652) | 3.3V logic, I2C/SPI, includes onboard pull-ups | $19.95 |
| Actuator | Songle 5V Relay Module (1-Channel) | Optocoupler isolated, active LOW trigger | $6.50 |
Wire the components according to this mapping. The Raspberry Pi uses BCM (Broadcom) numbering in software, but the physical pins on the header are numbered 1 through 40. Always double-check against the physical board layout.
| Physical Pin | BCM GPIO | Function | Wire Color (Standard) | Destination |
|---|---|---|---|---|
| 1 | N/A (3V3) | 3.3V Power | Red | BME280 VIN |
| 3 | GPIO 2 | I2C SDA | Blue | BME280 SDI |
| 5 | GPIO 3 | I2C SCL | Yellow | BME280 SCK |
| 6 | N/A (GND) | Ground | Black | BME280 GND |
| 11 | GPIO 17 | Digital Output | Green | Relay IN (Signal) |
| 2 | N/A (5V) | 5V Power | Red (Stripe) | Relay VCC |
| 9 | N/A (GND) | Ground | Black (Stripe) | Relay GND |
OS Configuration and Library Installation
With the hardware wired, you need to configure the OS to expose the I2C bus and install the correct Python backends. Do not use pip install RPi.GPIO; it will fail on the Pi 5.
- Flash the OS: Use the official Raspberry Pi Imager to flash Raspberry Pi OS (64-bit, Bookworm) to a high-endurance microSD card or an NVMe drive via the Pi 5 PCIe HAT.
- Enable I2C: Boot the Pi, open a terminal, and run
sudo raspi-config. Navigate to Interface Options > I2C and select Yes to enable the ARM I2C interface. - Verify I2C Hardware: Reboot, then run
sudo i2cdetect -y 1. You should see76or77in the grid, confirming the BME280 is on the bus. - Install Python Libraries: Install the system-managed packages to avoid PEP 668 externally-managed-environment errors. Run:
sudo apt update sudo apt install python3-gpiozero python3-smbus2 python3-rpi-lgpio
gpiochip4 in the Linux kernel. The python3-rpi-lgpio package automatically patches gpiozero to target chip 4, saving you from writing low-level device tree overrides.
Complete Python Control Code (BME280 + Relay)
The following script reads temperature and humidity from the BME280 and toggles the relay if the temperature exceeds a threshold. It targets the Raspberry Pi 5 using gpiozero for the GPIO pin and smbus2 for raw I2C register reads.
#!/usr/bin/env python3
"""
Raspberry Pi 5 BME280 I2C Reader & Relay Controller
Target: Raspberry Pi 5 (Bookworm OS)
Dependencies: gpiozero, smbus2, rpi-lgpio (backend)
"""
import time
import smbus2
from gpiozero import OutputDevice
from signal import pause
# --- PIN & BUS DEFINITIONS ---
RELAY_BCM_PIN = 17 # Physical Pin 11
I2C_BUS_ID = 1 # /dev/i2c-1
BME280_ADDR = 0x76 # Default Adafruit BME280 address (0x77 if SDO tied to VCC)
TEMP_THRESHOLD = 24.5 # Celsius
# --- I2C SENSOR SETUP ---
# BME280 register addresses for compensation and data
REG_DIG_T1 = 0x88
REG_TEMP_MSB = 0xFA
REG_CTRL_HUM = 0xF2
REG_CTRL_MEAS = 0xF4
bus = smbus2.SMBus(I2C_BUS_ID)
def initialize_bme280():
"""Configure BME280 oversampling and mode."""
# Set humidity oversampling to x1
bus.write_byte_data(BME280_ADDR, REG_CTRL_HUM, 0x01)
# Set temp/pressure oversampling to x1, mode to forced
bus.write_byte_data(BME280_ADDR, REG_CTRL_MEAS, 0x25)
time.sleep(0.1)
def read_raw_temp():
"""Read raw 20-bit temperature value from BME280."""
# Trigger a forced reading
bus.write_byte_data(BME280_ADDR, REG_CTRL_MEAS, 0x25)
time.sleep(0.05)
msb = bus.read_byte_data(BME280_ADDR, REG_TEMP_MSB)
lsb = bus.read_byte_data(BME280_ADDR, REG_TEMP_MSB + 1)
xsb = bus.read_byte_data(BME280_ADDR, REG_TEMP_MSB + 2)
raw_temp = (msb << 12) | (lsb << 4) | (xsb >> 4)
return raw_temp
def compensate_temperature(raw_temp):
"""Apply Bosch factory calibration to raw temp (simplified)."""
dig_T1 = bus.read_word_data(BME280_ADDR, REG_DIG_T1)
dig_T2 = bus.read_word_data(BME280_ADDR, 0x8A)
if dig_T2 > 32767: dig_T2 -= 65536
dig_T3 = bus.read_word_data(BME280_ADDR, 0x8C)
if dig_T3 > 32767: dig_T3 -= 65536
var1 = ((((raw_temp >> 3) - (dig_T1 << 1))) * dig_T2) >> 11
var2 = (((((raw_temp >> 4) - dig_T1) * ((raw_temp >> 4) - dig_T1)) >> 12) * dig_T3) >> 14
t_fine = var1 + var2
return ((t_fine * 5 + 128) >> 8) / 100.0
# --- MAIN EXECUTION LOOP ---
if __name__ == "__main__":
# Initialize Relay (Active LOW relay modules require active_high=False)
relay = OutputDevice(RELAY_BCM_PIN, active_high=False, initial_value=False)
initialize_bme280()
print(f"Starting Pi 5 Environmental Monitor on GPIO {RELAY_BCM_PIN}...")
try:
while True:
raw = read_raw_temp()
temp_c = compensate_temperature(raw)
print(f"Temperature: {temp_c:.2f} °C")
if temp_c > TEMP_THRESHOLD:
if not relay.is_active:
print("[!] Threshold exceeded. Engaging relay.")
relay.on()
else:
if relay.is_active:
print("[OK] Temp nominal. Disengaging relay.")
relay.off()
time.sleep(2.0)
except KeyboardInterrupt:
print("\nMonitor stopped by user.")
except OSError as e:
print(f"\nI2C Bus Error: {e}. Check wiring and pull-ups.")
finally:
relay.off()
relay.close()
bus.close()
print("GPIO and I2C resources released safely.")
Debugging: Exact Error Strings and Ranked Causes
When working with the Pi 5 RP1 architecture and I2C sensors, you will hit specific faults. Here are the exact error strings the terminal will throw, ranked by probability, and how to fix them.
Error 1: lgpio.error: 'GPIO busy'
Even though we use gpiozero, the underlying lgpio C-library throws this when a pin is already claimed by the kernel or another process.
- Cause A (Most Likely): A previous instance of your Python script crashed without hitting the
finallyblock, leaving the pin locked in thelgpiodaemon. - Cause B: The pin is configured for a kernel overlay (like UART or PWM audio) in
/boot/firmware/config.txt. - Fix: Run
gpioinfo | grep 17to see what holds the pin. Kill zombie python processes withsudo killall python3, or reboot the Pi to clear the RP1 GPIO state machine.
Error 2: OSError: [Errno 121] Remote I/O error
This occurs on the bus.read_byte_data() line.
- Cause A: The BME280 I2C address is wrong. Adafruit boards default to
0x76, but generic Amazon/eBay breakout boards often default to0x77. - Cause B: SDA and SCL wires are swapped, or the 3.3V line is loose, causing the sensor to brownout mid-transaction.
- Fix: Run
i2cdetect -y 1. If the grid is empty, swap SDA/SCL. If you see77, changeBME280_ADDR = 0x77in the code.
- Power Supply Voltage: Run
dmesg | grep -i under. If you see Under-voltage detected, your USB-C cable or power brick is failing under the Pi 5's transient current spikes. Switch to the official 27W PD supply. - I2C Pull-up Resistors: The Pi 5 has weak internal pull-ups (approx 50kΩ). If your sensor board doesn't have onboard 4.7kΩ pull-ups (the Adafruit 2652 does, generic ones often don't), the I2C bus will fail at 400kHz. Add external pull-ups or drop the bus speed in
config.txtusingdtparam=i2c_baudrate=100000. - Physical vs BCM Mapping: Verify you are passing the BCM number (
17) toOutputDevice, not the physical header number (11).gpiozerostrictly expects BCM.
Scaling the Build: Extensions and Simplifications
Once the baseline setup is stable, you can adapt the hardware to fit your specific deployment environment.
How to Extend the Build
- Add Local Data Logging: The Pi 5 features a dedicated PCIe 2.0 x1 lane. Add an NVMe SSD via a Pi 5 PCIe HAT (like the Pimoroni NVMe Base) and install InfluxDB and Grafana. This allows you to log the BME280 data at 1-second intervals for years without degrading a microSD card.
- Network Integration: Import the
paho-mqttlibrary and publish the compensated temperature readings to a local Mosquitto broker, allowing Home Assistant to trigger automations based on your Pi's sensor data.
How to Simplify the Build
- Drop the Relay: If you only need a desktop weather station, remove the relay module entirely. Delete the
gpiozeroimports and theOutputDeviceinitialization. This eliminates the 5V power requirement, allowing you to run the Pi 5 off a standard 15W USB-C phone charger without triggering the OS current-limiting warnings. - Use a Pre-Packaged Library: If raw I2C register mapping is too verbose, install Adafruit's CircuitPython libraries via pip (
pip3 install adafruit-circuitpython-bme280 --break-system-packages). This abstracts the calibration math into a singlesensor.temperaturecall, though it adds memory overhead.
Setting up the Raspberry Pi 5 for embedded projects requires unlearning a few legacy Pi 4 habits, specifically regarding the GPIO library and power delivery. By sticking to gpiozero, verifying your I2C pull-ups, and using the correct BCM pin mappings, your hardware integrations will be stable and production-ready.






