The Decision Path: Choosing a C GPIO Library for Pi 5
If you are writing C code for Raspberry Pi GPIO in 2026, the hardware landscape has fundamentally shifted. The Raspberry Pi 5 uses the RP1 southbridge chip for peripheral routing, meaning older libraries that relied on direct memory mapping to the Broadcom SoC will silently fail or segfault. You must choose a library that interfaces with the Linux gpiochip character device.
| Library | Pi 5 Compatibility | Architecture | Verdict |
|---|---|---|---|
| wiringPi | None (Abandoned) | Direct Memory / sysfs | Avoid. Deprecated since 2019. |
| bcm2835 | Fails (RP1 blocker) | Direct Memory Mapping | Avoid. Will crash on Pi 5. |
| pigpio | Partial (Daemon required) | Socket / Daemon | Use only if you need legacy PWM support and don't mind running pigpiod. |
| lgpio | Native (Full Support) | /dev/gpiochip ioctl |
Concrete Pick. The modern standard for Pi 4 and Pi 5. |
The Default Recommendation: Use lgpio. It bypasses the need for a background daemon, talks directly to the Linux kernel's GPIO character device, and natively supports the RP1 chip's gpiochip4 interface on the Pi 5.
Hardware Spec Sheet and Pin Mapping
This build targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (Bookworm or later). The Pi 5's 40-pin header is physically identical to previous generations, but internally, the BCM GPIO numbers map to the RP1 chip.
Parts List
- Board: Raspberry Pi 5 (8GB) with official 27W USB-C PD power supply.
- LED: 5mm Diffused Red LED (2.0V forward voltage, 20mA max).
- Resistor: 330Ω 1/4W Carbon Film (drops 3.3V to safe LED current: ~10mA).
- Switch: 6x6mm Momentary Tactile Switch (4-pin DIP).
- Wiring: 22 AWG solid core jumper wires.
Pin Mapping Table
| Component | Physical Pin | BCM GPIO | RP1 Function |
|---|---|---|---|
| LED Anode (via 330Ω) | 11 | 17 | Output |
| LED Cathode | 9 | GND | Ground |
| Button Pin 1 | 13 | 27 | Input (Internal Pull-Up) |
| Button Pin 2 | 14 | GND | Ground |
Complete Compilable C Code with Error Handling
Before compiling, install the lgpio development headers on your Pi:
sudo apt update
sudo apt install liblgpio-dev
Save the following code as gpio_monitor.c. This program claims GPIO 17 as an output and GPIO 27 as an input with an internal pull-up. It polls the button state and toggles the LED, including basic software debouncing.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <lgpio.h>
// Pin definitions mapped to BCM numbering
#define LED_PIN 17
#define BTN_PIN 27
// Pi 5 routes the 40-pin header through the RP1 chip, which registers as gpiochip4
// Pi 4 and older use gpiochip0
#define GPIO_CHIP 4
int main() {
// 1. Open the GPIO chip
int h = lgGpiochipOpen(GPIO_CHIP);
if (h < 0) {
fprintf(stderr, "Error: lgGpiochipOpen failed on chip %d. Error code: %d\n", GPIO_CHIP, h);
fprintf(stderr, "Are you on a Pi 5? If on Pi 4, change GPIO_CHIP to 0.\n");
return 1;
}
// 2. Claim LED pin as Output (initial state: 0 / LOW)
int err_led = lgGpioClaimOutput(h, 0, LED_PIN, 0);
if (err_led < 0) {
fprintf(stderr, "Failed to claim LED pin %d: %d\n", LED_PIN, err_led);
lgGpiochipClose(h);
return 1;
}
// 3. Claim Button pin as Input with Internal Pull-Up
int err_btn = lgGpioClaimInput(h, LG_GPIO_PULL_UP, BTN_PIN);
if (err_btn < 0) {
fprintf(stderr, "Failed to claim Button pin %d: %d\n", BTN_PIN, err_btn);
lgGpiochipClose(h);
return 1;
}
printf("Monitoring button on GPIO %d. Press Ctrl+C to exit.\n", BTN_PIN);
int last_state = 1; // Pull-up means default is HIGH (1)
// 4. Main polling loop
while (1) {
int btn_state = lgGpioRead(h, BTN_PIN);
// Detect falling edge (button pressed, pulled to GND)
if (btn_state == 0 && last_state == 1) {
printf("Button Pressed! Toggling LED.\n");
int current_led = lgGpioRead(h, LED_PIN);
lgGpioWrite(h, LED_PIN, !current_led);
usleep(200000); // 200ms software debounce delay
}
last_state = btn_state;
usleep(10000); // 10ms loop yield to prevent CPU thrashing
}
// 5. Cleanup (Note: Ctrl+C bypasses this, see signal handling for production)
lgGpiochipClose(h);
return 0;
}
Compile and Run:
gcc -o gpio_monitor gpio_monitor.c -llgpio
./gpio_monitor
Debugging: Fixing 'lgGpiochipOpen' and Permission Errors
When writing C code for Raspberry Pi GPIO, the most common point of failure is the chip initialization. If your code compiles but fails at runtime, you will likely see this exact error string:
Error: lgGpiochipOpen failed on chip 4. Error code: -1
or
lgGpiochipOpen: /dev/gpiochip4: No such file or directory
The First Three Things to Check
- Verify your board generation: Run
ls /dev/gpiochip*in the terminal. If you only seegpiochip0, you are on a Pi 4 or older. Change#define GPIO_CHIP 4to0in the C code and recompile. - Check user group permissions: The
/dev/gpiochip*devices are owned by thegpiogroup. If your user isn't in this group, the kernel blocks access. Fix it with:sudo usermod -aG gpio $USER, then log out and log back in. - Confirm the library is linked: If you get
undefined reference to lgGpiochipOpenduring compilation, you forgot the-llgpioflag at the end of yourgcccommand.
Ranked Causes for Runtime Failures
| Rank | Cause | Fix |
|---|---|---|
| 1 | Wrong GPIO Chip ID (Using 4 on a Pi 4) | Change GPIO_CHIP macro to 0. |
| 2 | Missing gpio group permissions |
Add user to group and reboot/re-login. |
| 3 | Pin already claimed by another process | Run sudo fuser -v /dev/gpiochip4 to find and kill the offending daemon (like a lingering Python script). |
| 4 | Outdated kernel missing RP1 overlays | Run sudo apt full-upgrade to pull the latest Pi 5 device tree overlays. |
Extending and Simplifying the Build
Once you have the baseline C code running, you will inevitably need to adapt it for production or more complex hardware.
How to Simplify: Drop the Polling Loop
Polling a GPIO pin in a while(1) loop wastes CPU cycles. The lgpio library supports asynchronous alerts. You can simplify the build by replacing the polling loop with a callback function using lgGpioSetAlertsFunc(). This hands the monitoring off to the kernel, dropping your C program's CPU usage to effectively zero while waiting for a button press.
How to Extend: Add Hardware PWM for Motor Control
If you decide to swap the LED for a servo motor or a DC motor driver (like the DRV8871), you need Pulse Width Modulation. Unlike pigpio which relies on software-timed PWM via a daemon, lgpio can claim the Pi 5's dedicated hardware PWM channels.
To extend the code for a servo on BCM GPIO 18 (Physical Pin 12):
// Claim hardware PWM (Chip 4, GPIO 18, 50Hz for servos)
lgPwm(h, 18, 50, 0.5, 0); // 50Hz, 50% duty cycle (approx 1.5ms pulse)
Always consult the official lgpio C API documentation for the exact parameter ordering, as PWM initialization requires specifying the frequency, duty cycle, and offset flags explicitly.
lgpio, targeting gpiochip4, and handling character device permissions correctly, you bypass the legacy traps that break older tutorials. Keep your pin definitions macro-based, always check the return integers of lgGpioClaim* functions, and your embedded C projects will run natively and reliably.
For deeper kernel-level insights into how the character device maps to the RP1 silicon, refer to the Linux Kernel GPIO Character Device documentation.






