If you are starting a hardware project in 2026, programming for Raspberry Pi 5 requires a fundamental shift in how you approach GPIO and I2C communication. The Raspberry Pi 5 replaced the legacy BCM2711 SoC with the BCM2712 and a dedicated RP1 southbridge chip. Because the RP1 handles all peripheral I/O via a PCIe link, legacy libraries like RPi.GPIO are completely broken. To interact with hardware today, you must use lgpio for digital pins and smbus2 for I2C buses under the Bookworm OS architecture.
This guide provides a complete, bench-tested workflow for reading a BME280 environmental sensor over I2C and switching a 3.3V logic relay based on temperature thresholds.
Time to Complete: 45 minutes
Target Board Variant: Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (Bookworm 64-bit, Lite or Desktop).
1. Project Spec Sheet & Parts List
When programming for Raspberry Pi 5, logic level mismatches are the most common cause of dead boards. The Pi 5 GPIO header is strictly 3.3V. Feeding 5V back into a GPIO pin through a standard optocoupler relay module will destroy the RP1 chip. The parts below are specifically selected for 3.3V native compatibility.
| Component | Exact Variant / Model | Approx. Cost | Notes |
|---|---|---|---|
| Microcontroller | Raspberry Pi 5 (8GB) | $80.00 | Requires active cooler; 27W PD power supply recommended. |
| Environmental Sensor | Adafruit BME280 I2C Breakout (Product 2652) | $19.95 | Includes onboard 3.3V regulator and level shifting. |
| Relay Module | HiLetgo 3.3V Opto-Isolated Relay Module | $8.50 | MUST be 3.3V logic trigger. Standard 5V modules will not trigger reliably or will backfeed. |
| Wiring | 28 AWG Silicone Dupont Wires (F-F) | $6.00 | Keep I2C runs under 12 inches to avoid capacitance issues. |
2. Wiring the BME280 and Relay Module
The Pi 5 exposes I2C1 on the primary header. The RP1 chip includes internal 1.8kΩ pull-up resistors on SDA and SCL, which is generally sufficient for short runs to a single BME280 module. If you extend the bus, you will need external 4.7kΩ pull-ups to 3.3V.
Pin Mapping Table
| Pi 5 Physical Pin | BCM / RP1 Label | Function | Connected To |
|---|---|---|---|
| 1 | 3V3 | Power | BME280 VIN & Relay VCC |
| 3 | GPIO 2 (SDA1) | I2C Data | BME280 SDI |
| 5 | GPIO 3 (SCL1) | I2C Clock | BME280 SCK |
| 6 | GND | Ground | BME280 GND & Relay GND |
| 11 | GPIO 17 | Digital Out | Relay IN (Trigger) |
3. Complete Python Code (lgpio + smbus2)
Before running this script, install the required dependencies via the terminal:
sudo apt update
sudo apt install python3-smbus2 i2c-tools
pip3 install lgpio bme280
The following script targets the Raspberry Pi 5. On the Pi 5, the RP1 southbridge exposes the primary GPIO header as gpiochip4. The code includes explicit pin definitions, I2C error handling, and safe GPIO cleanup.
#!/usr/bin/env python3
import time
import sys
import smbus2
import bme280
import lgpio
# --- PIN & BUS DEFINITIONS ---
I2C_BUS_ID = 1
BME280_ADDRESS = 0x76 # Use 0x77 if your breakout board has the alternate address
RELAY_GPIO_PIN = 17
GPIO_CHIP_ID = 4 # Pi 5 uses gpiochip4 for the RP1 header
TEMP_THRESHOLD_C = 28.0 # Trigger relay if temp exceeds 28C
# --- HARDWARE INITIALIZATION ---
def init_hardware():
# Initialize I2C Bus
try:
bus = smbus2.SMBus(I2C_BUS_ID)
calibration_params = bme280.load_calibration_params(bus, BME280_ADDRESS)
print('[OK] BME280 initialized on I2C bus 1.')
except FileNotFoundError:
sys.exit('Fatal: I2C bus not found. Did you enable I2C in raspi-config?')
except Exception as e:
sys.exit(f'Fatal: Could not connect to BME280 at 0x{BME280_ADDRESS:02x}. Error: {e}')
# Initialize GPIO via lgpio
try:
h = lgpio.gpiochip_open(GPIO_CHIP_ID)
lgpio.gpio_claim_output(h, RELAY_GPIO_PIN, 0) # Default to OFF (0)
print(f'[OK] Relay control initialized on GPIO {RELAY_GPIO_PIN} (chip{GPIO_CHIP_ID}).')
except lgpio.error as e:
sys.exit(f'Fatal: lgpio error. Is the GPIO chip number correct for your Pi? Error: {e}')
return bus, calibration_params, h
def main():
bus, cal_params, gpio_handle = init_hardware()
print('Starting environmental monitor. Press Ctrl+C to exit.')
try:
while True:
# Read Sensor
data = bme280.sample(bus, BME280_ADDRESS, cal_params)
temp_c = data.temperature
humidity = data.humidity
# Logic Control
if temp_c > TEMP_THRESHOLD_C:
lgpio.gpio_write(gpio_handle, RELAY_GPIO_PIN, 1) # Relay ON
state = 'ACTIVE'
else:
lgpio.gpio_write(gpio_handle, RELAY_GPIO_PIN, 0) # Relay OFF
state = 'IDLE'
print(f'Temp: {temp_c:.2f}C | Humidity: {humidity:.1f}% | Relay: {state}')
time.sleep(2.0)
except KeyboardInterrupt:
print('\n[!] Interrupt received. Cleaning up GPIO...')
except Exception as e:
print(f'\n[!] Runtime error during loop: {e}')
finally:
# Safe Cleanup
lgpio.gpio_write(gpio_handle, RELAY_GPIO_PIN, 0)
lgpio.gpiochip_close(gpio_handle)
bus.close()
print('Hardware released safely.')
if __name__ == '__main__':
main()
4. Debugging: First Three Checks & Exact Error Strings
When programming for Raspberry Pi hardware, the physical layer is usually where builds fail. If your script crashes on startup, perform these first three checks:
- Verify I2C Visibility: Run
i2cdetect -y 1in the terminal. You should see76or77in the grid. If the grid is empty, your wiring is wrong or I2C is disabled. - Check Logic Voltages: Use a multimeter to measure the voltage between the BME280 VIN and GND pins. It must read exactly 3.3V. If it reads 5V, you are connected to Pin 2 instead of Pin 1, and you may have already damaged the sensor.
- Confirm Interface Settings: Run
sudo raspi-config, navigate to Interface Options > I2C, and ensure it is enabled. Reboot after changing.
Exact Error String: OSError: [Errno 121] Remote I/O error
This is the most common I2C failure in Python. The kernel attempted to clock data out, but the sensor did not acknowledge (ACK) the transaction.
- Cause 1 (Most Likely): Incorrect I2C address. The Adafruit BME280 defaults to
0x77, while generic Amazon/eBay clones often use0x76. Check your specific board's schematic and update theBME280_ADDRESSvariable. - Cause 2: Missing pull-up resistors. If your I2C wires exceed 12 inches, the RP1's internal 1.8kΩ pull-ups are too weak to pull the line high fast enough. Solder 4.7kΩ resistors between SDA/SCL and 3.3V.
- Cause 3: I2C bus baudrate is too high. Add
dtparam=i2c_baudrate=10000to your/boot/firmware/config.txtto force standard mode.
Exact Error String: RuntimeError: Cannot determine SOC peripheral base address
You will see this if you attempt to use the legacy RPi.GPIO library on a Raspberry Pi 5.
- Cause 1:
RPi.GPIOrelies on direct memory mapping (/dev/mem) to the old BCM2711 peripheral addresses. The Pi 5's RP1 chip uses a completely different PCIe-mapped memory space. Fix: Uninstall RPi.GPIO and uselgpioas shown in the code above. - Cause 2: You are using an outdated Python virtual environment that cached an old version of
rpi-lgpio(the shim library). Delete the venv and recreate it with fresh packages.
5. Extending or Simplifying the Build
Once the baseline I2C read and GPIO trigger are stable, you can adapt the project to your specific needs.
How to Extend:
To integrate this into a smart home, add the paho-mqtt library. Inside the while loop, format the temp_c and humidity variables into a JSON payload and publish them to an MQTT broker (like Mosquitto or Home Assistant). This allows you to graph the environmental data over time and trigger complex automations beyond a simple local relay.
How to Simplify:
If you only need data logging and don't require physical control, strip out all lgpio imports and relay logic. Replace the relay state check with a simple CSV file append operation using Python's built-in csv module. This reduces the script's memory footprint and eliminates the risk of GPIO backfeed entirely.
6. Frequently Asked Questions
What is the best language for programming for Raspberry Pi GPIO?
Python remains the undisputed standard for rapid prototyping and general scripting on the Pi, thanks to the lgpio and smbus2 libraries. However, if you are building a high-frequency data acquisition system (e.g., sampling an ADC at 100kHz), C++ using the lgpio C API or Rust via the rppal crate will provide the deterministic timing that Python's garbage collector cannot guarantee.
How do I fix RPi.GPIO errors when programming for Raspberry Pi 5?
You cannot fix them; you must replace the library. The Raspberry Pi Foundation officially recommends migrating to GPIO Zero (which uses rpi-lgpio as a backend on Pi 5) or using lgpio directly for raw pin control. Any tutorial written before late 2023 that relies on import RPi.GPIO will fail on the Pi 5 architecture.
Can I use C++ instead of Python for programming for Raspberry Pi sensors?
Yes. You can use the lgpio C library for GPIO control and the standard Linux i2c-dev kernel interface for I2C communication. While it requires writing more boilerplate code for I2C register mapping, C++ eliminates the overhead of the Python interpreter, making it ideal for battery-powered Pi deployments where CPU cycles (and therefore power draw) must be minimized.
Do I need a logic level shifter when programming for Raspberry Pi I2C devices?
It depends on the sensor module. If you are using a bare BME280 chip or a 5V-only sensor (like the classic HC-SR04 ultrasonic sensor), you absolutely need a bidirectional logic level shifter (like the Adafruit 4-channel I2C-safe bi-directional logic level converter) to step the Pi's 3.3V up to 5V and, more importantly, step the 5V return signal down to 3.3V to protect the RP1 chip. If you are using a breakout board with an onboard voltage regulator and level shifting MOSFETs (like the Adafruit 2652 specified in this guide), you can wire it directly to the 3.3V pin.






