The transition to the Raspberry Pi 5 fundamentally changed how we handle Raspberry Pi GPIO Python scripting. With the introduction of the RP1 southbridge chip, legacy libraries like RPi.GPIO no longer work natively without compatibility shims. If you are following a tutorial from 2022 or earlier on a modern Pi 5 running Bookworm, your code will likely throw fatal runtime errors.

This guide provides the exact hardware bill of materials, the updated BCM pin mapping, a library comparison for the RP1 architecture, and complete, error-handled Python code using the modern gpiozero and lgpio stack.

Project Spec Sheet & Hardware BOM

Target Board Variant: This build and code specifically target the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (Bookworm or newer, 64-bit). The RP1 chip architecture dictates the software stack used below.
Component Exact Variant / Spec Est. Cost (2026) Purpose
Microcontroller Raspberry Pi 5 (8GB) $80.00 Main compute & GPIO host
Output 5mm Red LED (20mA, 2.0Vf) $0.10 Visual state indicator
Current Limiter 330Ω 1/4W Carbon Film Resistor $0.05 Limits LED current to ~10mA
Input 6x6mm Tactile Pushbutton (4-pin) $0.20 User input with internal pull-up
Wiring Female-to-Female Jumper Wires (28 AWG) $3.00 Breadboard to Pi header connection

The Raspberry Pi GPIO Python Pin Mapping Table

The Pi 5 retains the standard 40-pin physical header layout, but the internal BCM (Broadcom) routing is now handled by the RP1 chip. When writing Raspberry Pi GPIO Python scripts, always use the BCM GPIO numbering rather than physical pin numbers to maintain compatibility across Pi 3, 4, and 5.

Physical Pin BCM GPIO Function in this Build Recommended Wire Color Electrical Notes
1 N/A (3V3) Button Pull-Up Voltage Orange Max draw 50mA total across all 3V3 pins
6 N/A (GND) Common Ground Black Connect to LED cathode and Button pin 1
11 GPIO 17 LED Control (Output) Green Source current through 330Ω resistor
13 GPIO 27 Button Read (Input) Blue Use internal pull-up; active LOW

The Pi 5 Paradigm Shift: RPi.GPIO vs gpiozero vs lgpio

Before writing code, you must choose the right library. The RP1 chip on the Pi 5 broke backward compatibility with the legacy RPi.GPIO C-extension. According to the official Raspberry Pi documentation, the modern stack relies on lgpio (by Joan2937) under the hood.

Library Pi 5 Native Support? Syntax Style Verdict for 2026
RPi.GPIO No (Requires rpi-lgpio shim) Low-level, manual setup/cleanup Deprecated. Avoid for new projects.
gpiozero Yes (via lgpio backend) Object-oriented, high-level Recommended. Best for 95% of builds.
lgpio (Python bindings) Yes (Native) C-style, bitwise operations Use only for high-speed bit-banging or custom I2C/SPI.
Installation Command for Pi 5: Do not use pip install RPi.GPIO. Instead, install the system-packaged gpiozero and its lgpio backend via apt:
sudo apt update && sudo apt install python3-gpiozero python3-lgpio

Complete Python Build: Debounced Button & LED Control

This script targets the Pi 5 using gpiozero. It implements hardware debouncing via the bounce_time parameter and uses event-driven callbacks (when_pressed) rather than a blocking while True loop, which frees up the CPU for other tasks. For deeper library mechanics, refer to the gpiozero official documentation.

#!/usr/bin/env python3
"""
Raspberry Pi 5 GPIO Control using gpiozero and lgpio backend.
Targets: BCM GPIO 17 (LED) and BCM GPIO 27 (Button).
"""

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

# Configure basic logging for debug output
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# --- PIN DEFINITIONS (BCM Numbering) ---
LED_PIN = 17
BUTTON_PIN = 27

def setup_hardware():
    """Initialize GPIO devices with safety parameters."""
    try:
        # Initialize LED (Active High)
        led = LED(LED_PIN)
        
        # Initialize Button with internal pull-up and 50ms hardware debounce
        # pull_up=True means the pin reads HIGH (1) when open, LOW (0) when pressed
        button = Button(BUTTON_PIN, pull_up=True, bounce_time=0.05)
        
        return led, button
    except Exception as e:
        logging.critical(f"Hardware initialization failed: {e}")
        sys.exit(1)

def main():
    led, button = setup_hardware()
    
    # --- EVENT CALLBACKS ---
    def on_button_press():
        logging.info("Button PRESSED -> Toggling LED state.")
        led.toggle()
        
    def on_button_release():
        logging.info("Button RELEASED.")

    # Bind callbacks to hardware events
    button.when_pressed = on_button_press
    button.when_released = on_button_release
    
    logging.info(f"System Ready. Monitoring GPIO {BUTTON_PIN}. Press Ctrl+C to exit.")
    
    # --- MAIN LOOP ---
    try:
        # signal.pause() halts the main thread efficiently until a signal (like SIGINT) is received
        pause()
    except KeyboardInterrupt:
        logging.info("Interrupt received. Cleaning up GPIO and exiting.")
    finally:
        # gpiozero handles cleanup automatically on exit, but explicit close is good practice
        led.close()
        button.close()

if __name__ == '__main__':
    main()

Debugging: Fatal GPIO Errors and the 'First Three' Rule

When your Raspberry Pi GPIO Python script crashes, the traceback usually points to the underlying C library failing to map memory. Here are the exact error strings you will encounter on a Pi 5, ranked by probability.

1. "RuntimeError: Not running on a RPi!"

  • Cause A (Most Likely): You are running a 32-bit OS on a Pi 5, or the rpi-lgpio shim is missing while using legacy code.
  • Cause B: You are executing the script inside a Docker container without passing the --device /dev/gpiochip0 flag.
  • Fix: Ensure you are on 64-bit Bookworm and run sudo apt install python3-rpi-lgpio if you absolutely must use legacy RPi.GPIO syntax.

2. "ModuleNotFoundError: No module named 'RPi'"

  • Cause: You used pip install RPi.GPIO in a virtual environment that lacks the C-compiler headers (python3-dev) required to build the legacy wheel for the RP1 chip.
  • Fix: Abandon RPi.GPIO. Refactor your code to use gpiozero as shown in the script above.

3. "gpiozero.exc.GPIOPinMissing: No pins given"

  • Cause: You instantiated a device like led = LED() without passing the BCM integer.
  • Fix: Always explicitly define your pins: led = LED(17).
The First Three Things to Check When GPIO Fails:
  1. Verify the Kernel recognizes the RP1 chip: Run dmesg | grep rp1 in the terminal. If you see no output, your OS is outdated or corrupted. Flash the latest 64-bit Bookworm image.
  2. Confirm the backend is installed: Run python3 -c "import lgpio; print(lgpio.version())". If it throws an ImportError, run sudo apt install python3-lgpio.
  3. Check for physical shorts: Before applying power, use a multimeter in continuity mode to verify your 3.3V pin (Physical 1) is not shorted to GND (Physical 6) on the breadboard. A dead short here will trigger the Pi 5's hardware over-current protection and shut down the 3.3V rail.

Scaling the Build: Extensions and Simplifications

Once the base circuit is stable, you can adapt the hardware and software to fit your specific project constraints.

How to Simplify (The Minimalist Blink)

If you only need a heartbeat indicator and want to strip away the button and event callbacks, replace the entire main() function with a single blocking command. This reduces CPU overhead to near zero.

from gpiozero import LED
from signal import pause

led = LED(17)
led.blink(on_time=1, off_time=1, background=True)
pause()

How to Extend (Sensor Integration & MQTT)

To turn this into an IoT node, add a BME280 I2C environmental sensor to Physical Pins 3 (SDA) and 5 (SCL). You can extend the Python script to read the temperature every 60 seconds and publish the state to an MQTT broker using the paho-mqtt library. Because gpiozero uses non-blocking callbacks, you can safely run an I2C polling loop in a separate threading.Thread without missing button press events.

For advanced users pushing the RP1 chip to its limits, bypass gpiozero entirely and use the raw lgpio Python bindings to implement custom SPI bit-banging or high-frequency PWM generation that the abstraction layer masks.