The Raspberry Pi’s 40-pin GPIO header is the bridge between your software and the physical world. But staring at a block of unlabeled metal pins is a rite of passage for every maker. The direct answer to navigating this header is understanding the dual-numbering system: Physical Pin numbers (1 through 40, starting top-left) and BCM GPIO numbers (the Broadcom chip’s internal logic mapping). For 95% of projects, you should write your code using BCM numbering while wiring your breadboard using physical pin locations.
Below is a table-forward guide to the pinout, a complete hardware PWM build, and a debugging matrix for the exact runtime errors that halt most GPIO projects.
The 40-Pin Header: Power Rails and BCM Mapping
Before wiring anything, you must understand the electrical realities of the header. The Raspberry Pi GPIO operates at 3.3V logic. Feeding 5V into a standard GPIO pin will fry the Broadcom chip. Furthermore, specific pins have hardcoded hardware features (like fixed pull-up resistors or default UART routing) that will cause silent failures if you ignore them.
| Physical Pin | BCM GPIO | Name / Function | Electrical Gotchas & Notes |
|---|---|---|---|
| 1 | N/A | 3V3 Power | Max draw ~50mA total across all 3.3V pins. Do not use for motors. |
| 2, 4 | N/A | 5V Power | Direct from USB-C input. Use for high-current sensors/relays, but mind the total board thermal limit. |
| 3 | 2 | SDA1 (I2C) | Has a fixed 1.8kΩ hardware pull-up to 3.3V. Do not use as a standard output. |
| 5 | 3 | SCL1 (I2C) | Has a fixed 1.8kΩ hardware pull-up to 3.3V. Do not use as a standard output. |
| 8, 10 | 14, 15 | TXD, RXD (UART) | Defaults to serial console on boot. Must disable serial console in raspi-config to use for hardware UART. |
| 12 | 18 | GPIO 18 (PWM0) | Hardware PWM capable. Ideal for audio output or precise motor/LED control. |
| 32 | 12 | GPIO 12 (PWM0) | Hardware PWM capable. Shares channel with GPIO 18. |
| 33 | 13 | GPIO 13 (PWM1) | Hardware PWM capable. Independent channel from GPIO 12/18. |
| 6, 9, 14, 20, 25, 30, 34, 39 | N/A | Ground (GND) | All GND pins are common. Use the one closest to your signal pin to minimize loop area and noise. |
For a complete interactive map of every alternate function (SPI, DPI, PCM), reference the community-maintained Pinout.xyz database, which remains the gold standard for visual header mapping.
Target Board Variant and Hardware Parts List
The code and wiring in this guide specifically target the Raspberry Pi 4 Model B (4GB RAM) running Raspberry Pi OS (Bullseye or Legacy Bookworm).
RPi.GPIO Python library is no longer supported on Pi 5; you must use gpiozero or the lgpio bindings. The debugging errors listed later in this article apply specifically to the Pi 4 / RPi.GPIO ecosystem.
Required Parts
- Microcontroller: Raspberry Pi 4 Model B (4GB or 8GB variant)
- Power Supply: Official 27W USB-C Power Supply (5.1V / 5A) - prevents brownout warnings under load
- Breakout Board: Adafruit Pi Cobbler+ (Assembled) or generic T-Cobbler to translate pins to a breadboard safely
- Component: Standard 5mm Red LED (Forward voltage ~2.0V)
- Current Limiting: 220Ω through-hole resistor (1/4W)
- Wiring: Male-to-female jumper wires (22 AWG silicone jacket preferred for flexibility)
Hands-On Build: Hardware PWM LED Fade
We will build a breathing LED circuit using Hardware PWM. Unlike software PWM (which stutters if the Pi’s CPU is busy with background tasks), Hardware PWM on GPIO 18 is driven by a dedicated timer, yielding a perfectly smooth fade.
Wiring Steps
- Disconnect power from the Raspberry Pi before wiring.
- Plug the Cobbler+ into the breadboard, straddling the center trench.
- Connect a male-to-female jumper from the Cobbler’s GPIO 18 pin to breadboard row 10.
- Insert the 220Ω resistor with one leg in row 10 and the other in row 15.
- Insert the LED’s anode (long leg) into row 15, and the cathode (short leg) into the breadboard’s negative (blue) ground rail.
- Connect a jumper from the Cobbler’s GND pin to the breadboard’s ground rail.
- Apply power and boot the Pi.
Python Control Code
This script uses the RPi.GPIO library. It includes strict error handling and a finally block to ensure the GPIO state is reset even if you force-quit the script.
import RPi.GPIO as GPIO
import time
import sys
# --- PIN DEFINITIONS ---
LED_PIN = 18 # BCM GPIO 18 (Physical Pin 12)
PWM_FREQ = 1000 # 1kHz frequency (avoids visible flicker)
FADE_STEP = 0.02 # Duty cycle increment
FADE_DELAY = 0.02 # Seconds between steps
def setup_gpio():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False) # Suppress 'channel in use' warnings on startup
GPIO.setup(LED_PIN, GPIO.OUT)
return GPIO.PWM(LED_PIN, PWM_FREQ)
def main():
pwm = setup_gpio()
pwm.start(0) # Start with 0% duty cycle (LED off)
print(f'Starting PWM fade on BCM GPIO {LED_PIN}. Press Ctrl+C to exit.')
try:
while True:
# Fade In
for dc in range(0, 101, 1):
pwm.ChangeDutyCycle(dc)
time.sleep(FADE_DELAY)
# Fade Out
for dc in range(100, -1, -1):
pwm.ChangeDutyCycle(dc)
time.sleep(FADE_DELAY)
except KeyboardInterrupt:
print('\nFade interrupted by user.')
except Exception as e:
print(f'\nUnexpected error: {e}', file=sys.stderr)
finally:
# CRITICAL: Always clean up to release the hardware lock
pwm.stop()
GPIO.cleanup()
print('GPIO cleanup complete. Pins released.')
if __name__ == '__main__':
main()
Debugging GPIO Failures: Exact Errors and Ranked Causes
When working with RPi.GPIO on the Pi 4, you will inevitably hit runtime errors that halt your script. Here is the exact diagnostic path for the two most common failures.
Error 1: 'RuntimeError: The GPIO channel is already in use'
Exact String: RuntimeError: The GPIO channel is already in use. Use GPIO.setwarnings(False) to disable warnings.
Ranked Causes:
- Orphaned Process: A previous run of your script crashed or was killed (via
kill -9) before reachingGPIO.cleanup(). The OS still thinks the pin is locked. - Missing Cleanup Block: Your code lacks a
try...finallyblock, so a standard exception bypassed the cleanup routine. - Concurrent Scripts: Another Python script (or a Node-RED flow) is actively polling or writing to BCM 18 in the background.
The Fix: First, run GPIO.setwarnings(False) at the top of your script to override the soft lock. Second, ensure your code uses the finally block shown above. If the pin is truly locked by a zombie process, reboot the Pi or find and kill the process using ps aux | grep python.
Error 2: 'RuntimeError: No access to /dev/mem'
Exact String: RuntimeError: No access to /dev/mem. Try running as root!
Ranked Causes:
- Missing Sudo: You executed the script as a standard user (e.g.,
python3 fade.py) on an older OS build where/dev/memrequires root privileges. - User Group Misconfiguration: Your user is not part of the
gpioordialoutgroups in the OS permission table.
The Fix: Run the script with elevated privileges: sudo python3 fade.py. For a permanent fix on modern Raspberry Pi OS, ensure your user is in the gpio group by running sudo usermod -aG gpio $USER and then logging out and back in.
The First Three Things to Check When GPIO Fails
If your code runs without throwing errors, but the hardware does nothing, run this mental checklist before rewriting your code:
- Physical vs. BCM Mismatch: Did you wire physical pin 18, but set
GPIO.setmode(GPIO.BCM)and call pin 18 in code? Physical pin 18 is BCM GPIO 24. Always double-check your mode declaration. - Ground Loop Integrity: Use a multimeter to verify continuity between the Pi’s GND pin and your breadboard’s ground rail. A floating ground will result in erratic sensor readings or dead LEDs.
- Peripheral Power Starvation: If you are driving a relay or motor directly from the 3.3V or 5V pins, the Pi’s brownout detector may be throttling the CPU. Measure the 5V rail with a multimeter; if it drops below 4.8V under load, you need an external power supply.
Extending and Simplifying the Build
Once you have the basic PWM fade working, you have two paths forward depending on your project goals.
How to Simplify: Switch to gpiozero
If you find RPi.GPIO’s manual cleanup and setup tedious, migrate to the gpiozero library. It is the officially recommended Python library for Raspberry Pi. It abstracts away the BCM/BOARD numbering confusion and handles cleanup automatically via Python’s garbage collector.
from gpiozero import PWMLED
from time import sleep
from signal import pause
# gpiozero uses BCM numbering by default
led = PWMLED(18)
try:
led.pulse() # Built-in hardware-accelerated breathing effect
pause()
except KeyboardInterrupt:
led.off()
How to Extend: Add I2C Telemetry
To make this a true embedded system, add an I2C OLED display to show the current PWM duty cycle. Wire an SSD1306 128x64 OLED to Physical Pins 1 (3.3V), 3 (SDA), 5 (SCL), and 6 (GND). Because GPIO 2 and 3 have hardware pull-ups, you do not need external resistors for the I2C bus. Use the Adafruit_SSD1306 Python library to render the duty cycle percentage in real-time as the LED fades.
Mastering the Raspberry Pi GPIO pin layout is less about memorizing 40 pins and more about understanding the electrical boundaries of the Broadcom chip. Respect the 3.3V logic limit, always implement a finally cleanup block, and verify your physical-to-BCM mapping with a multimeter before applying power.






