GPIO Edge Detection on Raspberry Pi: Choosing Between sysfs and libgpiod
A practical look at GPIO edge detection on Raspberry Pi—when to use sysfs, when to reach for libgpiod, and why debounce behavior changes between Pi 3 and Pi 4.
15 Sept 2026, 02:41 UTC

A tactile button wired to GPIO 17 either misses a quick tap or reports three presses for one physical push. The root cause is rarely the wire; it is how the kernel notices the voltage change and how much noise the hardware lets through. On Raspberry Pi, edge detection is the mechanism that turns a pin transition into a software event, and the implementation details change depending on which interface you use and which board you own.
The practical takeaway: the legacy sysfs interface is fine for slow, human-scale signals, but if you need low-latency response you should use libgpiod and the kernel GPIO interrupt path. You also need to know that only the Pi 4 and newer include programmable debounce in the SoC; older boards require external filtering.
What “edge detection” actually does
When a GPIO pin is configured as an input, the kernel can watch it for a rising edge (low-to-high), a falling edge (high-to-low), or both. Instead of your code repeatedly reading the pin in a loop, the kernel’s interrupt controller maps the pin to an interrupt request (IRQ) line and wakes your process only when the transition happens. That is the difference between polling and event-driven code.
Two interfaces, two trade-offs
Raspberry Pi OS exposes GPIO through two main paths.
The older sysfs interface lives under /sys/class/gpio. You export a pin, set its direction, choose an edge, and then read value. It is easy to script and requires no compiled code, but it is a legacy interface that newer kernels have deprecated. It is best for prototyping and slow sensors.
The modern path is libgpiod, which talks to character devices such as /dev/gpiochip0. The kernel still handles the underlying IRQ, but your userspace program receives structured line events with timestamps. This avoids the overhead of repeatedly opening and reading sysfs files and is the recommended approach when latency matters. Access to /dev/gpiochipX typically requires root privileges or membership in the gpio group.
Worked example: watching GPIO 17 with sysfs
The following commands configure GPIO 17 for both-edge detection. Run them in a terminal on the Pi itself; you need root or sudo.
echo 17 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio17/direction
echo both > /sys/class/gpio/gpio17/edge
To confirm the setting took effect, read the file back:
cat /sys/class/gpio/gpio17/edge
Expected result: both. If you see none, the write failed or the pin is already claimed by another driver.
You can then monitor the pin by reading value in a loop or by using a small program that blocks on the file descriptor. The export step changes kernel state, so when you are finished, release the pin:
echo 17 > /sys/class/gpio/unexport
If you need tighter timing, the equivalent libgpiod C program requests the line, asks for both-edge events, and calls gpiod_line_event_read() in a loop. The kernel delivers the IRQ to the character device, and your process wakes with an event structure that includes a monotonic timestamp. That structure is what makes latency measurement straightforward.
The debounce gap between Pi generations
Mechanical switches do not close cleanly; they bounce for a few milliseconds, producing a burst of edges. If your software counts every edge, one press becomes many.
Debounce behavior is version-sensitive. On the Raspberry Pi 4 (BCM2711), the kernel GPIO driver applies a default debounce timeout of roughly 25 ms. On earlier models such as the Pi 3 (BCM2837/BCM2836) and the original Pi (BCM2835), the hardware does not offer programmable debounce, so the kernel cannot filter bounce for you.
| Board generation | SoC | Programmable debounce | Practical mitigation |
|---|---|---|---|
| Pi 4 and newer | BCM2711 | Yes, default ~25 ms | Adjust via device tree if needed |
| Pi 3 and earlier | BCM2835/2836/2837 | No | External RC filter or software timer |
An RC low-pass filter on the pin is the most deterministic fix for older boards. In software, you can ignore edges that arrive within a few milliseconds of the previous one, but that adds latency and complexity.
How to verify the interrupt is firing
Two checks confirm that edge detection is working at the kernel level.
First, inspect the interrupt counters before and after toggling the pin:
cat /proc/interrupts | grep -i gpio
The corresponding GPIO IRQ line should increment on each transition. If the count stays flat, the edge is not being detected—check direction and edge again.
Second, measure latency. In a libgpiod program, record clock_gettime(CLOCK_MONOTONIC) when the event arrives and compare it to the timestamp in the event structure. On a Pi 4 under light load, sub-10 ms response is realistic, but heavy CPU contention or a slow SD card can stretch that. Treat any latency figure as a property of your specific image and workload, not a fixed constant.
Limitations to plan around
sysfsis deprecated. Code that relies on it may break on future kernel releases, so treat it as a transitional tool.- Non-root access to
/dev/gpiochipXrequires a udev rule or group membership; without it, your program will fail with a permission error. - Debounce defaults are not uniform. A design validated on a Pi 4 may produce multiple spurious events when moved to a Pi 3.
- Very fast signals can be swallowed by the 25 ms debounce window on Pi 4. If your sensor pulses faster than that, you need a different input strategy.
Bottom line
Start with sysfs to prove your wiring and logic. Move to libgpiod when you need timestamps and lower latency. Always check which SoC you are targeting: if it predates the Pi 4, add an external RC filter rather than trusting the kernel to debounce for you. A quick look at /proc/interrupts and a read-back of the edge file will tell you whether the kernel is actually listening.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.