Raspberry Pi GPIO Without RPi.GPIO: lgpio, gpiozero, and pigpio Compared
If you copied a GPIO script from an old tutorial onto a Raspberry Pi 5 and watched it die with RuntimeError: Cannot determine SOC peripheral base address or simply hang with no error at all, you've run into the biggest breaking change in Raspberry Pi software history: RPi.GPIO, the library nearly every Python GPIO tutorial of the last decade was built on, does not work on the Pi 5 and is not being updated to. The Pi 5's new RP1 southbridge chip moved GPIO handling off the main SoC and onto a dedicated I/O controller, and RPi.GPIO's register-level approach has no way to reach it. Raspberry Pi OS Bookworm compounds this by blocking direct /dev/mem access for unprivileged users on every Pi model, not just the 5, which breaks a second wave of older libraries that depended on it. The good news is there are three well-maintained replacements, and which one you want depends entirely on what your project actually needs.
Why RPi.GPIO Had to Go
RPi.GPIO worked by mapping the SoC's GPIO peripheral registers directly into your process's memory and twiddling bits by hand. That was fast and simple on the BCM2835/2711 chips in the Pi 1 through 4, where the application processor owned the GPIO hardware directly. The Pi 5's BCM2712 delegates all GPIO, SPI, I2C, and PWM muxing to the RP1 chip, which sits across the PCIe bus rather than in the same address space, so there's no register to memory-map into your process anymore. RPi.GPIO's maintainer confirmed there will be no Pi 5 port; the library is effectively frozen for the Pi 4 and earlier. Trying to run it on a 5 either throws immediately or, on some kernel versions, silently does nothing, which is the more dangerous failure mode if you're driving a relay or motor.
The Three Real Replacements
Raspberry Pi's own recommended path is the gpiozero and lgpio combination, but pigpio still has a place for timing-critical work. Here's how they actually differ in practice, not just in marketing copy.
LibraryAbstraction LevelWorks on Pi 5Remote GPIOBest For gpiozeroHigh-level (LED, Button, Motor objects)Yes (via lgpio backend)Yes, over networkBeginners, readable project code, quick prototyping lgpioLow-level (pin numbers, line requests)Yes, nativeNo (local only)Direct pin control, replacing raw RPi.GPIO calls 1:1 pigpioLow-level, daemon-basedYes (as of recent releases)Yes, over networkPrecise PWM, waveforms, microsecond-level timinggpiozero: The Default Choice for New Projects
gpiozero is an abstraction layer, not a GPIO driver itself — it sits on top of a pin factory, and as of current releases that default factory is lgpio. You write LED(17).on() or Button(2).when_pressed = callback instead of manipulating pin numbers and edge flags directly, which makes gpiozero code dramatically more readable and far less likely to leave a GPIO line claimed after a crash. Install it with sudo apt install python3-gpiozero python3-rpi-lgpio on Bookworm, or pip install gpiozero rpi-lgpio in a virtual environment. If you're starting a brand-new project and don't have a specific reason to go lower-level, start here. Its device classes already cover LEDs, buttons, motors, servos, distance sensors, and more, and when they don't cover what you need you can drop to DigitalInputDevice/DigitalOutputDevice for direct pin access without leaving the library.
lgpio: The Direct Replacement When You Need Raw Pins
lgpio is the lower-level library gpiozero is built on, and it's the right choice when you're porting old RPi.GPIO code that used direct pin numbers and don't want to restructure it into gpiozero's object model, or when you're writing a library of your own that needs to own GPIO lines without an abstraction layer in between. The API maps reasonably closely onto RPi.GPIO's mental model — you open a GPIO chip handle, claim a line as input or output, and read or write it — but the calls are different enough that it's a rewrite, not a find-and-replace. lgpio uses the kernel's modern character-device GPIO interface (/dev/gpiochip*) rather than memory-mapped registers, which is exactly why it works through RP1 on the Pi 5 and will keep working on whatever Raspberry Pi does next, since it talks to the kernel's GPIO subsystem instead of specific chip internals.
pigpio: Still the Answer for Hard Real-Time Work
pigpio runs as a background daemon (pigpiod) that your Python script talks to over a socket, and that architecture is what lets it do things neither gpiozero nor lgpio handle well: generating precise hardware PWM on more pins than the SoC's dedicated PWM channels, building arbitrary waveforms for driving WS2812 LEDs or servos with microsecond-accurate pulse widths, and — critically — controlling GPIO on a different Raspberry Pi over the network, since your script just connects to a daemon's IP and port. The daemon architecture adds latency for simple on/off switching compared to lgpio's direct line requests, so don't reach for pigpio for a button and an LED. Reach for it when you're doing software PWM that actually needs to be clean, bit-banging a protocol pigpio has a waveform generator for, or controlling GPIO on a headless Pi from a different machine on your bench.
Porting an Old Script
If you've got working RPi.GPIO code you need alive on a Pi 5, the fastest path is often not a rewrite at all: the community rpi-lgpio package is a drop-in shim that implements RPi.GPIO's exact API (GPIO.setmode(), GPIO.setup(), GPIO.output()) on top of lgpio under the hood. Uninstall the real RPi.GPIO and install rpi-lgpio in its place and most simple scripts run unmodified. It won't help with code that pokes at RPi.GPIO's internals or assumes BCM283x register addresses directly, which is rare but does exist in older advanced examples, but for the overwhelming majority of beginner and intermediate projects it's the lowest-effort fix available.
Which One Should You Actually Use
For a new project with no legacy constraints: gpiozero. It's readable, well-documented, actively maintained by the Raspberry Pi Foundation, and its abstraction cost is negligible for anything that isn't timing-critical. For porting old RPi.GPIO code with minimal changes: try the rpi-lgpio shim first, and fall back to a proper lgpio rewrite if the shim's edge cases bite you. For anything involving precise PWM, custom waveforms, or controlling a Pi's GPIO from another machine over the network: pigpio, accepting its slightly higher latency for the capabilities nothing else on this list offers. All three coexist fine on the same system, so there's no need to commit to just one across your entire shop's fleet of Pis — pick per project based on what that project is actually doing with its pins.