Raspberry Pi IO: The 3.3V Reality and the Pi 5 RP1 Shift

The direct answer for every Raspberry Pi IO project: all GPIO pins operate strictly at 3.3V logic. Feeding 5V into any general-purpose pin will permanently destroy the silicon. On the Raspberry Pi 4, this fries the BCM2711 SoC directly. On the Raspberry Pi 5, it destroys the RP1 southbridge chip that handles peripheral IO. If you need to interface with 5V sensors, relays, or actuators, you must use a bi-directional logic level shifter or an optocoupler.

This guide targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Bookworm. The transition to Bookworm and the Pi 5 hardware architecture fundamentally changed how IO is handled in software and hardware, deprecating older libraries and shifting memory mapping to the RP1 chip.

Pi 4 vs Pi 5 IO Architecture Comparison

SpecificationRaspberry Pi 4 (BCM2711)Raspberry Pi 5 (RP1 Southbridge)
Logic Level3.3V3.3V
Max Current per Pin16 mA12 mA (default), up to 16mA configurable
Total Bank Current Limit50 mA50 mA
Internal Pull-up/down50kΩ - 65kΩConfigurable via RP1, typically 50kΩ
Software Backend (Bookworm)lgpio / rpi-lgpiolgpio (Native RP1 driver)

Project Build: Safe 5V Relay and Button Interfacing

We will build a circuit that reads a 5V-tolerant mechanical pushbutton and triggers a 5V relay module to switch a high-power AC load (like a desk lamp). Because the relay module requires a 5V logic trigger and the button outputs 5V, we will use a BSS138 MOSFET-based logic level shifter to protect the Pi 5 IO pins.

Difficulty: Intermediate | Time: 45 Minutes | Cost: ~$12 (excluding Pi)

Parts List

  • Microcontroller: Raspberry Pi 5 (8GB)
  • Level Shifter: BSS138 Bi-directional Logic Level Shifter (4-channel or 8-channel breakout board)
  • Relay Module: Songle SRD-05VDC-SL-C (5V trigger, active LOW or HIGH selectable)
  • Switch: Standard SPST tactile pushbutton
  • Resistors: 10kΩ (for pull-down on button if shifter lacks internal pull-downs)
  • Wiring: 22 AWG solid core jumper wires

Pin Mapping Table

Pi 5 BCM PinPhysical PinLevel Shifter LV SideLevel Shifter HV SideComponent
3V3 (Power)1LV (Power)-Shifter Low Voltage Rail
5V (Power)2-HV (Power)Shifter High Voltage Rail
GND6GNDGNDCommon Ground
GPIO 1711LV1HV1Pushbutton Input (5V side)
GPIO 2713LV2HV2Relay Trigger Output (5V side)

Wiring Steps

  1. De-energize: Ensure the Raspberry Pi is completely powered off and unplugged from the USB-C supply.
  2. Wire Power Rails: Connect Pi physical pin 1 (3.3V) to the shifter's LV pin. Connect Pi physical pin 2 (5V) to the shifter's HV pin. Connect Pi physical pin 6 (GND) to both GND pins on the shifter.
  3. Wire the Button (HV Side): Connect one leg of the pushbutton to the shifter's HV1 pin. Connect the other leg to the 5V rail. Add a 10kΩ pull-down resistor from HV1 to GND to prevent floating inputs when the button is released.
  4. Wire the Relay (HV Side): Connect the relay module's IN (signal) pin to the shifter's HV2 pin. Connect the relay's VCC to 5V and GND to the common ground.
  5. Verify: Use a multimeter in continuity mode to verify no shorts exist between the 5V and 3.3V rails before applying power.

Complete Python Code (gpiozero + lgpio Backend)

Under Raspberry Pi OS Bookworm, the legacy RPi.GPIO library is deprecated. The official standard is gpiozero, which automatically uses the lgpio backend on the Pi 5. The code below includes explicit pin definitions and robust error handling.

#!/usr/bin/env python3
"""
Raspberry Pi 5 IO: Safe 5V Relay Control via Logic Level Shifter
Target: Raspberry Pi 5 (8GB) / Bookworm OS
Backend: gpiozero (lgpio)
"""

from gpiozero import Button, OutputDevice
from signal import pause
import sys
import logging

# --- PIN DEFINITIONS (BCM Numbering) ---
BUTTON_PIN = 17  # Physical Pin 11
RELAY_PIN = 27   # Physical Pin 13

# Configure logging for debugging IO states
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def setup_io():
    """Initialize GPIO pins with explicit pull-down and active state definitions."""
    try:
        # Button on LV1 (reads 3.3V when HV1 sees 5V)
        # pull_up=None because we are using an external hardware pull-down resistor
        button = Button(BUTTON_PIN, pull_up=False, bounce_time=0.05)
        
        # Relay on LV2 (outputs 3.3V to HV2, which switches to 5V)
        # active_high=True means pin goes HIGH (3.3V) to trigger the shifter
        relay = OutputDevice(RELAY_PIN, active_high=True, initial_value=False)
        
        return button, relay
    except Exception as e:
        logging.critical(f"Failed to initialize Raspberry Pi IO pins: {e}")
        sys.exit(1)

def main():
    button, relay = setup_io()
    logging.info("Raspberry Pi IO initialized. Waiting for button press...")

    # Bind events
    button.when_pressed = lambda: (relay.on(), logging.info("Button PRESSED -> Relay ENGAGED"))
    button.when_released = lambda: (relay.off(), logging.info("Button RELEASED -> Relay DISENGAGED"))

    try:
        pause()  # Keep the script running efficiently
    except KeyboardInterrupt:
        logging.info("Interrupt received. Cleaning up Raspberry Pi IO states.")
    except Exception as e:
        logging.error(f"Unexpected runtime error: {e}")
    finally:
        # gpiozero handles cleanup on exit, but explicit close is safer for relays
        relay.close()
        button.close()
        logging.info("GPIO resources released safely.")

if __name__ == "__main__":
    main()

Debugging: "RuntimeError: This module can only be run on a Raspberry Pi!"

If you copy legacy code from older tutorials, you will likely hit a wall on the Pi 5. When attempting to import RPi.GPIO on a Pi 5 running Bookworm, the interpreter throws this exact error string:

RuntimeError: This module can only be run on a Raspberry Pi!
Or alternatively: RuntimeError: Cannot determine SOC peripheral base address

Ranked Causes

  1. Using deprecated RPi.GPIO on Pi 5: The RPi.GPIO library relies on direct memory mapping to the BCM2711 SoC. The Pi 5 uses the RP1 chip, which maps memory entirely differently. The legacy library literally cannot find the hardware addresses.
  2. Missing the rpi-lgpio compatibility shim: If you absolutely must run legacy code, Raspberry Pi provides a shim package called rpi-lgpio that intercepts RPi.GPIO calls and translates them to lgpio commands. If this is missing, the import fails.
  3. Incorrect User Permissions: In older OS versions, you needed sudo to access GPIO. In Bookworm, the gpio group handles this via udev rules. Running as a standard user without the group assignment can sometimes mask itself as a hardware detection failure depending on the library version.

The First Three Things to Check When It Fails

  1. Check your OS and Backend: Run cat /etc/os-release. If it says "Bookworm", stop using RPi.GPIO. Rewrite your script using gpiozero (as shown above) or native lgpio. Reference the official Bookworm release notes for the deprecation details.
  2. Verify Installed Packages: Run pip list | grep -i gpio. Ensure gpiozero and lgpio are installed. If you are forced to use legacy code, install the shim via sudo apt install python3-rpi-lgpio.
  3. Confirm Pin Numbering Mode: The Pi 5 RP1 chip handles physical vs. BCM pin mapping differently at the driver level. Always use BCM numbering (e.g., GPIO 17) in your code, as physical pin numbering (e.g., Pin 11) can cause offset errors in third-party libraries ported to the RP1.

Extending and Simplifying Your IO Build

To Simplify: If wiring a BSS138 shifter feels like too much overhead, purchase a pre-assembled Raspberry Pi Relay HAT (like those from Waveshare or Seeed Studio). These HATs include the necessary optocouplers and flyback diodes onboard, plugging directly into the 40-pin header and isolating the 5V relay coils from the Pi's 3.3V IO natively.

To Extend: The Pi 5 only has 27 usable GPIO pins. If your project requires reading dozens of 5V sensors (like a home security matrix), do not daisy-chain shifters. Instead, use an MCP23017 I2C IO Expander. This chip communicates over the Pi's 3.3V I2C bus (pins 3 and 5) and provides 16 additional 5V-tolerant IO pins. You can chain up to 8 MCP23017 chips on a single I2C bus, giving you 128 extra IO pins while only using two Pi GPIO pins.

Raspberry Pi IO FAQ

How many amps can a single Raspberry Pi IO pin source?

According to the official Raspberry Pi 5 datasheet, the absolute maximum current per GPIO pin is 16mA, but the recommended continuous current is 8mA. More importantly, the total current sourced or sunk across all GPIO pins combined must not exceed 50mA. If you need to drive an LED that requires 20mA, you must use a 2N2222 NPN transistor or a MOSFET driven by the GPIO pin, rather than powering the LED directly from the Pi.

Why does my Raspberry Pi 5 GPIO behave differently than my Pi 4?

The Pi 4 routes GPIO directly through the main BCM2711 SoC. The Pi 5 routes all peripheral IO through a separate southbridge chip called the RP1, connected via PCIe. This architectural shift means that internal pull-up and pull-down resistors are handled by the RP1 silicon, not the main CPU. Consequently, some legacy code that relied on specific hardware timing or direct memory register manipulation for pull-down states will fail or behave erratically on the Pi 5 without the updated lgpio drivers.

Can I use I2C and SPI on the same Raspberry Pi IO header simultaneously?

Yes. I2C (default pins 3 and 5) and SPI (default pins 19, 21, 23, 24, 26) use dedicated hardware controllers on the RP1 chip and do not conflict. However, be careful with pinmuxing. If you attempt to enable secondary SPI or I2C buses via the config.txt overlays, ensure you are not assigning them to pins already claimed by the UART console or PWM audio outputs.

What is the safest way to read a 12V sensor with Raspberry Pi IO?

Never use a simple resistor voltage divider for 12V-to-3.3V conversion in industrial or automotive environments; voltage spikes will bypass the divider and fry the Pi. The safest method is galvanic isolation using an optocoupler like the PC817. Wire the 12V sensor output through a 1kΩ current-limiting resistor to the optocoupler's internal LED. Connect the optocoupler's phototransistor side between the Pi's 3.3V GPIO pin (with internal pull-up enabled) and GND. When the 12V sensor triggers, the LED lights up, pulling the Pi's IO pin to GND safely with zero electrical connection between the 12V and 3.3V circuits.