The 40-pin header on a Raspberry Pi is the bridge between your software and the physical world. However, the transition to the Raspberry Pi 5 and its RP1 southbridge chip fundamentally changed how the OS interacts with these pins. Legacy tutorials using the RPi.GPIO library will silently fail or throw cryptic errors on modern Bookworm OS installations. This guide provides a bench-tested reference for Raspberry Pi pin mapping, a complete hardware build with PWM control, and exact debugging steps for the most common GPIO permission and factory errors you will encounter in 2026.

The Pi 5 Shift: RP1 Southbridge and Pin Compatibility

On the Pi 4 and earlier, the Broadcom SoC handled GPIO directly. The Pi 5 routes all Raspberry Pi pin I/O through an external RP1 microcontroller. This architectural shift means the memory addresses for /dev/mem and /dev/gpiomem have changed. If you attempt to use the deprecated RPi.GPIO library on a Pi 5, your code will crash. The modern, officially supported stack relies on gpiozero paired with the rpi-lgpio backend. This combination abstracts the RP1 hardware registers while maintaining the familiar Broadcom (BCM) numbering scheme that the community relies on.

Project Build: Hardware Debounced Button and PWM LED

Difficulty Rating: 2/5 (Beginner/Intermediate)
Target Board: Raspberry Pi 5 (4GB or 8GB) running Raspberry Pi OS (Bookworm or later)
Time to Build: 20 minutes

Parts List

  • Board: Raspberry Pi 5 (8GB variant recommended for desktop use)
  • LED: 5mm Red Diffused LED (Forward voltage ~2.0V)
  • Resistor (LED): 330Ω 1/4W Carbon Film (Limits current to ~10mA)
  • Switch: 6x6mm Tactile Push Button (4-pin, SPST-NO)
  • Resistor (Pull-up): 10kΩ 1/4W (Optional if using internal pull-ups, but recommended for noise immunity)
  • Prototyping: Half-size 400-point breadboard, Male-to-Female and Male-to-Male jumper wires

Raspberry Pi Pin Mapping Table

We use the BCM (Broadcom) numbering scheme in software, but you will wire to the Physical header pins. BCM 18 is specifically chosen because it is one of the few pins capable of true hardware PWM on the Pi 5.

Function BCM Pin (Software) Physical Pin (Header) Wiring Destination
3.3V Power N/A 1 Breadboard + Rail
Ground N/A 6 Breadboard - Rail
LED PWM Control 18 12 330Ω Resistor → LED Anode
Button Input 22 15 Button Pin 1 (with 10kΩ to 3.3V)
⚠️ Callout Tip: Current Limits
Never draw more than 16mA from a single Raspberry Pi pin. More importantly, the total combined current draw from all 3.3V GPIO pins must not exceed 50mA. Always use a transistor or MOSFET for motors, relays, or high-power LED strips.

Wiring Steps

  1. De-energize: Disconnect the Pi 5 from the USB-C power supply before wiring.
  2. Power Rails: Connect Physical Pin 1 (3.3V) to the breadboard red rail, and Physical Pin 6 (GND) to the blue rail.
  3. LED Circuit: Insert the 330Ω resistor from Physical Pin 12 to an empty row. Connect the LED anode (long leg) to the resistor, and the cathode (short leg) to the GND rail.
  4. Button Circuit: Place the tactile switch across the breadboard center trench. Connect one side to Physical Pin 15. Connect the same side to the 3.3V rail via the 10kΩ pull-up resistor. Connect the opposite side of the switch to GND.
  5. Verify: Double-check that no 5V pins (Physical 2 or 4) are accidentally bridged to your GPIO inputs. Applying 5V to BCM 22 will destroy the RP1 I/O pad.

Compilable Python Code with Error Handling

This script uses gpiozero to handle software debouncing and hardware PWM. It includes explicit exception handling for the most common pin factory and permission errors.

import sys
import time
import signal
from gpiozero import PWMLED, Button
from gpiozero.exc import GPIOPinInUse, BadPinFactory, PinInvalidFunction

# Explicit BCM Raspberry Pi Pin Definitions
PIN_LED = 18  # Physical Pin 12 (Hardware PWM0)
PIN_BTN = 22  # Physical Pin 15

def main():
    try:
        # Initialize components with explicit pin definitions
        # bounce_time=0.05 provides 50ms hardware-level software debouncing
        led = PWMLED(PIN_LED, frequency=1000)
        button = Button(PIN_BTN, pull_up=True, bounce_time=0.05)

        print(f"Monitoring BCM Pin {PIN_BTN}. Press button to fade LED.")
        print("Press Ctrl+C to exit.")

        def handle_press():
            print("Button pressed! Fading LED...")
            led.pulse(1, 1)  # 1s fade in, 1s fade out

        button.when_pressed = handle_press

        # Keep the main thread alive to listen for callbacks
        signal.pause()

    except GPIOPinInUse as e:
        print(f"ERROR: {e}")
        print("Fix: Another process is holding the pin. Run 'sudo fuser -k /dev/gpiomem'")
        sys.exit(1)
        
    except BadPinFactory as e:
        print(f"ERROR: Pin factory failed to load. {e}")
        print("Fix: On Pi 5 Bookworm, install the lgpio backend: sudo apt install python3-rpi-lgpio")
        sys.exit(1)
        
    except PermissionError as e:
        print(f"ERROR: {e}")
        print("Fix: Add your user to the gpio group: sudo usermod -aG gpio $USER, then reboot.")
        sys.exit(1)
        
    except KeyboardInterrupt:
        print("\nExiting cleanly.")
        led.off()
        sys.exit(0)

if __name__ == "__main__":
    main()

Debugging: Pin Errors and the "First Three Checks"

When your script fails to initialize a Raspberry Pi pin, it rarely fails silently. Here are the exact error strings you will see, ranked by frequency, and how to fix them.

Error 1: The Permission Block

Exact Error String: PermissionError: [Errno 13] Permission denied: '/dev/gpiomem'

Ranked Causes:

  1. User not in GPIO group: Modern Pi OS disables root-by-default for GPIO. Your current user lacks access to the memory device file.
  2. Running via cron/sudo mismatch: If running as a cron job, the environment variables or user context might be stripping group permissions.

Fix: Run sudo usermod -aG gpio $USER in the terminal, then completely reboot the Pi. Logging out and back in is not enough to refresh the device file permissions.

Error 2: The Pi 5 Library Trap

Exact Error String: RuntimeError: This module can only be run on a Raspberry Pi! (or NotImplementedError when importing RPi.GPIO)

Ranked Causes:

  1. Using RPi.GPIO on Pi 5: The legacy library cannot map the RP1 southbridge memory addresses.
  2. Missing lgpio backend: gpiozero is installed, but the underlying C-binding (rpi-lgpio) is missing.

Fix: Uninstall the legacy library (pip uninstall RPi.GPIO) and install the modern backend: sudo apt install python3-rpi-lgpio.

The First Three Things to Check When It Fails

If your code compiles but the physical pin does nothing, run this mental checklist before rewriting your code:

  1. Verify the Pin Numbering Scheme: Are you passing the Physical pin number (e.g., 12) to a library expecting the BCM number (18)? gpiozero defaults to BCM. If you must use physical numbering, you have to explicitly set pin_factory or use the gpiozero Pin abstraction.
  2. Check for Pin Contention: Run sudo fuser -v /dev/gpiomem. If another Python script, Node-RED, or Home Assistant instance is holding the file handle open, your script will fail to claim the pin.
  3. Measure with a Multimeter: Set your DMM to DC Voltage. Probe the physical pin against a known ground while the script attempts to drive it HIGH. If you read 0V, the issue is software/permissions. If you read 3.2V-3.3V but the LED is dark, your hardware wiring or LED polarity is reversed.

Extending and Simplifying the Build

How to Simplify: If PWM fading is causing jitter or you just need a simple status indicator, swap PWMLED for the standard LED class in gpiozero. This drops the hardware PWM requirement, allowing you to move the LED to any standard GPIO pin (like BCM 17 / Physical 11) and simplifies the code to basic led.on() and led.off() toggles.

How to Extend: Add an I2C OLED display to log button presses. Wire an SSD1306 128x64 OLED to Physical Pin 3 (SDA1) and Pin 5 (SCL1). You can use the luma.oled Python library to render the press count. Because I2C uses dedicated hardware blocks on the RP1 chip, it will not interfere with the GPIO polling or PWM timing of your button and LED.

Frequently Asked Questions

Which raspberry pi pin provides 3.3V versus 5V power?

Physical Pins 1 and 17 provide 3.3V (max 50mA total draw across both). Physical Pins 2 and 4 provide 5V (directly tied to the USB-C input rail, capable of supplying the board's total input current minus the Pi's own consumption). Never feed 5V back into a 3.3V GPIO input pin; the RP1 I/O pads are not 5V tolerant and will instantly short to ground, permanently destroying that specific pin.

Why is my raspberry pi pin not outputting a full 3.3V logic high?

If your multimeter reads 2.8V or lower on a pin driven HIGH, you are likely exceeding the 16mA current limit of the I/O pad, causing the internal resistance of the RP1 chip to drop the voltage (voltage sag). Alternatively, if you are measuring an I2C or SPI pin, the internal pull-up resistors (typically 50kΩ) are weak; you must add external 4.7kΩ pull-up resistors to the 3.3V rail to achieve a crisp logic HIGH.

Can I wire a relay directly to a raspberry pi pin?

No. A standard 5V relay coil draws between 70mA and 120mA, which will instantly fry the 16mA-rated Raspberry Pi pin and potentially damage the RP1 southbridge. You must use a logic-level N-channel MOSFET (like an IRLZ44N or 2N7000) or an optocoupler relay module. Drive the MOSFET gate from the Pi pin, and let the MOSFET switch the higher current from the 5V rail to the relay coil.

How do I find the BCM number for a physical raspberry pi pin?

Open the terminal on your Pi and type pinout (provided by the gpiozero package). This renders a color-coded ASCII diagram of your specific board's header, mapping every Physical pin to its BCM GPIO number, I2C/SPI bus, and UART assignments. Alternatively, reference the official Raspberry Pi GPIO documentation.