The Case for C in Raspberry Pi GPIO Control
While Python dominates the Raspberry Pi ecosystem for quick prototyping, writing C in Raspberry Pi environments is mandatory when you need sub-microsecond GPIO toggling, deterministic timing, or minimal memory overhead. Python's Global Interpreter Lock (GIL) and OS-level scheduling jitter can introduce millisecond-level delays—enough to ruin bit-banged protocols like WS2812B LED timing or high-speed rotary encoder counting.
This guide provides a complete, production-ready C implementation for hardware-debounced button inputs and LED control using the pigpio library. Target Board Variant: This code and memory-mapping architecture specifically targets the Raspberry Pi 4 Model B (4GB or 8GB). If you are using a Raspberry Pi 5, read the critical architecture warning below before proceeding.
/dev/mem). The pigpio library relies on direct memory mapping and will not work on the Pi 5. For Pi 5 C development, you must use the modern libgpiod character device API or the newer lg library by joan2937. This guide uses the Pi 4 to demonstrate the classic, ultra-low-latency pigpio C API.
Hardware: Parts List and Pin Mapping
Before writing code, we need a stable physical circuit. Mechanical switches exhibit contact bounce, which we will handle in software using a digital glitch filter, but proper pull-up resistors are still required for clean logic levels.
Bill of Materials
- Microcontroller: Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS (64-bit, Bookworm or Bullseye).
- Switch: 6x6mm Tactile Pushbutton (SPST, Normally Open).
- Indicator: 5mm Through-hole LED (Red, 20mA max forward current).
- Resistors: One 330Ω 1/4W (LED current limiting), One 10kΩ 1/4W (External pull-up for switch stability).
- Wiring: Half-size 400-point breadboard and 22 AWG solid core jumper wires.
Pin Mapping Table
| Component | BCM GPIO | Physical Pin | Wiring Destination |
|---|---|---|---|
| Tactile Button (Leg 1) | 17 | 11 | BCM 17 (Input) |
| Tactile Button (Leg 2) | N/A | N/A | GND (Physical 9) |
| 10kΩ Pull-up Resistor | N/A | N/A | Between BCM 17 and 3.3V (Physical 1) |
| LED Anode (+) | 27 | 13 | BCM 27 via 330Ω Resistor |
| LED Cathode (-) | N/A | N/A | GND (Physical 14) |
Toolchain Setup and Compilation
The pigpio library is the gold standard for C-based GPIO manipulation on the Pi 4, offering PWM, waveforms, and callback functions. Unlike Python, C requires explicit compilation and linking.
- Install the pigpio C library and headers:
sudo apt update sudo apt install pigpio libpigpio-dev - Enable the pigpio daemon (optional but recommended for remote access):
Note: The C code below uses the direct memory-mapped API (sudo systemctl enable pigpiod sudo systemctl start pigpiodgpioInitialise), which does not require the daemon to be running, but you cannot use both simultaneously on the same pins. - Write and Compile the Code:
Save the code block below as
gpio_control.c. Compile it usinggcc, ensuring you link the pigpio, pthread, and realtime libraries:gcc -o gpio_control gpio_control.c -lpigpio -lpthread -lrt - Execute with Root Privileges:
Memory mapping
/dev/memrequires root access.sudo ./gpio_control
Complete C Code for Hardware Debounced Button and LED
This code implements an interrupt-driven callback. Instead of polling the button in a while loop (which wastes CPU cycles), pigpio triggers a callback only when the pin state changes. We also implement a 5000-microsecond (5ms) glitch filter to debounce the mechanical switch contacts in hardware-level software logic.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pigpio.h>
#define LED_PIN 27
#define BTN_PIN 17
// Global flag for clean shutdown
static volatile int keep_running = 1;
void handle_sigint(int signum) {
keep_running = 0;
}
// Interrupt callback triggered by pigpio on pin state change
void button_callback(int gpio, int level, uint32_t tick) {
// level 0 = pressed (active low, pulled to GND)
if (level == 0) {
int current_state = gpioRead(LED_PIN);
gpioWrite(LED_PIN, !current_state);
printf("Button pressed at tick %u. LED toggled to %d\n", tick, !current_state);
}
}
int main() {
// Catch CTRL+C to allow clean memory un-mapping
signal(SIGINT, handle_sigint);
// Initialize pigpio direct memory access
if (gpioInitialise() < 0) {
fprintf(stderr, "Fatal: pigpio initialisation failed. Check permissions and board variant.\n");
return 1;
}
// Configure Pin Modes
gpioSetMode(LED_PIN, PI_OUTPUT);
gpioSetMode(BTN_PIN, PI_INPUT);
// Enable internal pull-up (redundant if using external 10k, but good practice)
gpioSetPullUpDown(BTN_PIN, PI_PUD_UP);
// Apply 5000 microsecond (5ms) glitch filter for mechanical debounce
gpioSetGlitchFilter(BTN_PIN, 5000);
// Attach the interrupt callback
gpioSetAlertFunc(BTN_PIN, button_callback);
printf("System ready. Press the button to toggle LED. Press CTRL+C to exit.\n");
// Main loop just keeps the program alive while callbacks run in the background
while (keep_running) {
gpioDelay(100000); // Sleep 100ms to yield CPU to OS
}
// Clean up state on exit
gpioWrite(LED_PIN, 0);
gpioTerminate();
printf("\nClean exit. GPIO memory unmapped.\n");
return 0;
}
Debugging: Fatal pigpio Errors and Fixes
When working with C in Raspberry Pi environments, segmentation faults and initialization errors are common if memory permissions or background daemons conflict. Here are the exact error strings and how to resolve them.
Error 1: mmap gpio failed (Permission denied)
Ranked Causes:
- Missing Root Privileges: You ran
./gpio_controlwithoutsudo. The/dev/memdevice requires root to map physical RAM addresses to user space. - Cgroup/Device Tree Restrictions: On newer Raspberry Pi OS (Bookworm),
/dev/memaccess is heavily restricted. You may need to adddtoverlay=vc4-kms-v3dor adjustconfig.txtto allow legacy mmap access, or add your user to thegpioandkmemgroups.
Fix: Run with sudo ./gpio_control. If building a systemd service, ensure the service file includes SupplementaryGroups=gpio kmem.
Error 2: initInitialise: bind to port 8888 failed (Address already in use)
Ranked Causes:
- Daemon Conflict: The
pigpiodbackground daemon is already running and has locked the GPIO memory and the local socket port 8888. - Zombie Process: A previous C program crashed without calling
gpioTerminate(), leaving the memory lock active.
Fix: Stop the daemon via sudo systemctl stop pigpiod, or kill orphaned processes with sudo killall pigpiod. Alternatively, rewrite your C code to use pigpio_start(NULL, NULL) to connect to the daemon via sockets instead of mapping memory directly.
- Board Variant: Are you accidentally running this on a Pi 5? The RP1 chip will cause an immediate
mmapfailure. Move to a Pi 4 or rewrite usinglibgpiod. - Execution Context: Did you use
sudo? Direct memory access is blocked for standard users. - Daemon Interference: Run
systemctl status pigpiod. If it's active, stop it before running direct-mapped C code.
Extending or Simplifying the Build
Once you have the baseline C environment working, you can scale the complexity up or down based on your project requirements.
How to Extend: Adding Hardware PWM
Software PWM in C is possible, but the Pi 4 has dedicated hardware PWM channels that are completely immune to OS scheduling jitter. To add hardware PWM to the LED pin for a breathing effect, replace the gpioWrite logic with:
// Hardware PWM on BCM 27 (Channel 1)
// gpioHardwarePWM(user_gpio, PWMfreq, PWMduty)
// Duty cycle is 0 to 1,000,000 (1M = 100%)
gpioHardwarePWM(LED_PIN, 1000, 500000); // 1kHz frequency, 50% duty cycle
Note: Hardware PWM is only available on specific pins (BCM 12, 13, 18, 19 on Pi 4). BCM 27 does not support hardware PWM natively; you would need to move the LED to BCM 18 to use gpioHardwarePWM.
How to Simplify: The sysfs Fallback
If you cannot install pigpio due to embedded constraints, you can simplify the build by writing directly to the Linux sysfs character interface (e.g., echoing values to /sys/class/gpio/gpio17/direction). However, be warned: the Linux kernel community is actively deprecating sysfs GPIO in favor of libgpiod. sysfs also introduces severe latency (milliseconds per toggle) compared to pigpio's nanosecond memory writes. Use sysfs only for slow, non-time-critical relays.
Frequently Asked Questions
Is C faster than Python for Raspberry Pi GPIO?
Yes, drastically. Python's RPi.GPIO or gpiozero libraries introduce overhead from the Python interpreter and OS context switching, resulting in toggle jitter ranging from 10µs to over 1ms depending on CPU load. C using pigpio writes directly to the ARM memory bus, achieving deterministic toggle times in the nanosecond range with virtually zero jitter. For audio DACs, high-speed ADCs, or WS2812B LEDs, C is strictly required.
How do I compile C code on Raspberry Pi?
Raspberry Pi OS ships with the gcc compiler pre-installed. You compile a C file using the terminal: gcc -o output_name source.c. When using external hardware libraries like pigpio, you must append the linker flags at the end of the command: -lpigpio -lpthread -lrt. The -lrt flag links the realtime library, which is required for pigpio's high-precision timing functions.
Why does my C GPIO code fail on Raspberry Pi 5?
The Raspberry Pi 5 architecture moved GPIO control from the main Broadcom SoC to a dedicated RP1 southbridge chip. Legacy C libraries like wiringPi and pigpio rely on hardcoded physical memory addresses for the Broadcom SoC's GPIO registers. Because those registers no longer exist at those addresses on the Pi 5, the memory mapping fails. To write C on a Pi 5, you must use the modern libgpiod library, which communicates with the kernel's character device driver rather than touching raw memory.






