Use GPIO Pins on a Raspberry Pi
Control Raspberry Pi GPIO pins using libgpiod — the modern replacement for deprecated sysfs. Covers CLI tools, Python, and C with real working examples.
Before you start
- ▸Raspberry Pi 3B+, 4, or 5 with a 64-bit OS installed and network/SSH access
- ▸Basic electronics: LED, resistors, jumper wires, breadboard
- ▸GCC toolchain installed (build-essential on Debian/Ubuntu) for the C example
- ▸User account with sudo privileges to install packages and modify group membership
GPIO (General Purpose Input/Output) pins turn a Raspberry Pi into a bridge between software and physical electronics. The old /sys/class/gpio sysfs interface is deprecated since Linux 4.8 and removed from many modern kernels. The current standard is libgpiod — a character device-based API with C bindings, Python bindings, and a suite of command-line tools. This guide covers installing libgpiod, using its CLI tools, and writing minimal Python and C programs that drive GPIO pins correctly on modern Raspberry Pi OS (Bookworm) and Ubuntu 24.04 for Pi.
Why sysfs GPIO Is Gone
The sysfs interface exported pins through /sys/class/gpio/export and friends. It had no access control, no concept of chip ownership, and was impossible to use safely from multiple processes. The kernel moved to a character device model (/dev/gpiochip0, /dev/gpiochip1, …) where each pin request is an explicit, owned file descriptor. Tools and libraries that still use sysfs will silently fail or produce warnings on kernels ≥ 6.x. Do not use RPi.GPIO or WiringPi for new projects; both depend on the deprecated interface.
Prerequisites and Hardware Notes
- Raspberry Pi 3B+, 4, or 5 running Raspberry Pi OS Bookworm (64-bit recommended) or Ubuntu 24.04 LTS for Pi.
- An LED with a 330 Ω resistor (for output example) or a push-button with a 10 kΩ pull-down resistor (for input example).
- SSH access or a local terminal. Running as a normal user who is a member of the
gpiogroup (see below). - Basic familiarity with a text editor and the C compiler toolchain for the C example.
Step 1: Install libgpiod and Tooling
Raspberry Pi OS Bookworm and Ubuntu 24.04 both carry libgpiod 2.x in their main repositories.
# Raspberry Pi OS / Debian / Ubuntu
sudo apt update
sudo apt install -y gpiod libgpiod-dev python3-gpiod
# Fedora / RHEL family (if running on a Pi with Fedora ARM)
sudo dnf install -y libgpiod libgpiod-devel python3-libgpiod
# Arch Linux ARM
sudo pacman -S libgpiod python-libgpiod
The package installs the gpiodetect, gpioinfo, gpioget, gpioset, and gpiomon CLI tools, plus headers and the Python module.
Step 2: Grant User Access to GPIO
The character devices are owned by the gpio group on Raspberry Pi OS. Add your user, then log out and back in (or use newgrp) for the change to take effect.
sudo usermod -aG gpio $USER
newgrp gpio
On Ubuntu for Pi the group may be dialout or require a udev rule. Check with ls -l /dev/gpiochip* and add your user to whichever group owns those devices.
Step 3: Discover Your GPIO Chips and Lines
gpiodetect
Output will look something like:
gpiochip0 [pinctrl-bcm2835] (54 lines)
gpiochip1 [raspberrypi-exp-gpio] (8 lines)
The main header pins live on gpiochip0. List all lines with their current direction and any consumer holding them:
gpioinfo gpiochip0
Pin numbering uses the BCM (Broadcom) GPIO number, not the physical header position. GPIO 17 is physical pin 11; GPIO 27 is physical pin 13. Keep a pinout reference like pinout.xyz open while working.
Step 4: Drive a Pin from the Command Line
Read an input pin
# Read GPIO 17 on gpiochip0 (returns 0 or 1)
gpioget --chip gpiochip0 17
Set an output pin
# Drive GPIO 17 high, hold it for 5 seconds, then release
gpioset --mode=time --sec=5 gpiochip0 17=1
Without --mode=time, gpioset in libgpiod 2.x defaults to holding the line until the process exits, which is the correct behavior — the line is released when the owning process dies. In libgpiod 1.x you may need --mode=signal instead; check gpioset --help for your installed version.
Monitor a pin for edge events
# Print a timestamp each time GPIO 17 changes (Ctrl-C to stop)
gpiomon --num-events=10 --rising-edge --falling-edge gpiochip0 17
Step 5: Python Example — Blink an LED
Wire an LED through a 330 Ω resistor from GPIO 17 (BCM) to ground. The gpiod Python module (part of python3-gpiod) uses a context manager that guarantees the line is released when the block exits.
cat > blink.py << 'EOF'
import gpiod
import time
CHIP = "/dev/gpiochip0"
LINE = 17
with gpiod.request_lines(
CHIP,
consumer="blink-example",
config={LINE: gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT)},
) as request:
for _ in range(10):
request.set_value(LINE, gpiod.line.Value.ACTIVE)
time.sleep(0.5)
request.set_value(LINE, gpiod.line.Value.INACTIVE)
time.sleep(0.5)
print("Done. Line released.")
EOF
python3 blink.py
Note: The gpiod.request_lines() API is the libgpiod 2.x Python interface. If your system has libgpiod 1.x (Ubuntu 22.04, Debian Bullseye), use the older gpiod.Chip / chip.get_line() API instead and consult the 1.x documentation.
Step 6: C Example — Read a Button
Wire a push-button between GPIO 27 and 3.3 V with a 10 kΩ pull-down resistor to ground. The C API is the most direct and lowest-overhead way to use libgpiod.
cat > button.c << 'EOF'
#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>
#define CHIP "/dev/gpiochip0"
#define LINE 27
int main(void) {
struct gpiod_chip *chip = gpiod_chip_open(CHIP);
if (!chip) { perror("gpiod_chip_open"); return 1; }
struct gpiod_line_request *req;
struct gpiod_request_config *rcfg = gpiod_request_config_new();
struct gpiod_line_config *lcfg = gpiod_line_config_new();
struct gpiod_line_settings *ls = gpiod_line_settings_new();
gpiod_line_settings_set_direction(ls, GPIOD_LINE_DIRECTION_INPUT);
gpiod_line_settings_set_bias(ls, GPIOD_LINE_BIAS_PULL_DOWN);
gpiod_line_config_add_line_settings(lcfg, (unsigned int[]){LINE}, 1, ls);
gpiod_request_config_set_consumer(rcfg, "button-read");
req = gpiod_chip_request_lines(chip, rcfg, lcfg);
if (!req) { perror("request_lines"); return 1; }
for (int i = 0; i < 20; i++) {
enum gpiod_line_value val = gpiod_line_request_get_value(req, LINE);
printf("GPIO %d = %s\n", LINE, val == GPIOD_LINE_VALUE_ACTIVE ? "HIGH" : "LOW");
sleep(1);
}
gpiod_line_request_release(req);
gpiod_line_settings_free(ls);
gpiod_line_config_free(lcfg);
gpiod_request_config_free(rcfg);
gpiod_chip_close(chip);
return 0;
}
EOF
gcc -o button button.c $(pkg-config --cflags --libs libgpiod)
./button
The kernel-side pull-down set via GPIOD_LINE_BIAS_PULL_DOWN eliminates the need for an external resistor on Pi 4 and Pi 5 (BCM2711/BCM2712 both support software bias). On Pi 3, software bias is not available; use the physical resistor.
Step 7: Verify Everything Is Working
# Confirm the line is claimed while your program runs in another terminal
gpioinfo gpiochip0 | grep -E "line 17|line 27"
A correctly claimed line shows the consumer name you set (e.g., blink-example) and its direction. If it shows unused, the line was never claimed or was already released.
Troubleshooting
- Permission denied on /dev/gpiochipN: Your user is not in the correct group. Re-run
groupsto confirm membership took effect. You may need to fully log out and log back in rather than just usingnewgrp. - "Device or resource busy": Another process owns the line. Use
gpioinfoto find the consumer name, then identify and stop that process. - gpioset releases the pin immediately: You are using libgpiod 2.x which changed the default
--mode. Pass--mode=time --sec=Nor--mode=signalexplicitly. - Python ImportError for gpiod: The package name is
python3-gpiod(system) or install viapip install gpiodinside a virtual environment. The PyPI package tracks the libgpiod 2.x API. - C compile error — gpiod.h not found: Install
libgpiod-dev(Debian/Ubuntu) orlibgpiod-devel(Fedora/RHEL). Confirm withpkg-config --modversion libgpiod.
Frequently asked questions
- Can I still use RPi.GPIO or WiringPi?
- Avoid them for new projects. Both rely on the deprecated sysfs interface or direct memory mapping that conflicts with the character device model. RPi.GPIO is unmaintained; WiringPi's author discontinued it. Use libgpiod instead.
- What is the difference between gpiochip0 and gpiochip1 on the Pi?
- On Pi 3 and 4, gpiochip0 is the main BCM283x/BCM2711 controller covering all 40-pin header GPIOs. gpiochip1 is a secondary expander (the RP1 chip on Pi 5 restructures this significantly — gpiochip0 through gpiochip4 exist, with the 40-pin header on gpiochip4).
- How do I use PWM with libgpiod?
- libgpiod does not provide hardware PWM; use the kernel's PWM subsystem via /sys/class/pwm for hardware PWM channels, or a userspace PWM library. GPIO 18 (BCM) is the main hardware PWM pin on the Pi.
- Is it safe to set both input and output on the same pin from two processes?
- No. The character device model enforces exclusive ownership — the second request will fail with EBUSY. Design your application so one process owns each line, or use inter-process communication to share control.
- Does this work on Raspberry Pi 5?
- Yes, but the Pi 5 uses an RP1 south-bridge chip that exposes GPIO through gpiochip4, not gpiochip0. Run gpiodetect to find the right chip number, then target it in your code and CLI commands.
Related guides
AI and Artificial-Life Tools on Linux
Set up open-source AI/ML and artificial-life toolkits on Linux: PyTorch, JAX, DEAP, Avida, NetLogo, and RL environments with GPU driver guidance.
Assembly Language on Linux: A Starter Guide
Write x86-64 assembly on Linux from scratch: install NASM and GAS, learn syscalls, assemble and link a working program, then inspect and debug it.
How to Benchmark Disk Performance with fio
Learn to benchmark Linux disk performance with fio: writing job files, testing latency and throughput, and interpreting IOPS and percentile output correctly.
The Linux Boot Process Explained
Trace the full Linux boot sequence from UEFI firmware through GRUB2, the kernel, initramfs, and systemd to your login prompt — with diagnostics at each stage.