GPIO (General Purpose I/O)¶
Overview¶
GPIO provides direct digital I/O control using the Linux chardev interface (/dev/gpiochipN). Supports output drive, input reading, and interrupt-driven edge detection with nanosecond-precision timestamps all without the deprecated sysfs interface.
Status: Implemented
Full implementation with output control, edge detection via poll/epoll, software debounce, and chip introspection.
Architecture¶
┌───────────────────────────────────────────────────────────────────┐
│ Linux GPIO Chardev Interface (/dev/gpiochipN): │
│ │
│ ioctl(GPIO_GET_CHIPINFO_IOCTL) → Chip info (name, #lines) │
│ ioctl(GPIO_GET_LINEINFO_IOCTL) → Per-line info (name, dir) │
│ ioctl(GPIO_GET_LINEHANDLE_IOCTL) → Request line for output │
│ ioctl(GPIO_GET_LINEEVENT_IOCTL) → Request line for edge detect │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Output │ │ Input │ │ Event │ │
│ │ Handle │ │ Handle │ │ FD │ │
│ │ (set H/L) │ │ (read val) │ │ (poll/epoll)│ │
│ └────────────┘ └────────────┘ └────────────┘ │
└───────────────────────────────────────────────────────────────────┘
API Reference¶
Data Structures¶
typedef enum {
GPIO_DIR_INPUT = 0,
GPIO_DIR_OUTPUT = 1,
} gpio_direction_t;
typedef enum {
GPIO_EDGE_NONE = 0,
GPIO_EDGE_RISING = GPIOEVENT_REQUEST_RISING_EDGE,
GPIO_EDGE_FALLING = GPIOEVENT_REQUEST_FALLING_EDGE,
GPIO_EDGE_BOTH = GPIOEVENT_REQUEST_BOTH_EDGES,
} gpio_edge_t;
typedef struct {
int chip_fd; /* /dev/gpiochipN fd */
char chip_name[32]; /* Chip name/label */
uint32_t num_lines; /* Number of GPIO lines */
int line_fds[GPIO_MAX_LINES]; /* Per-line handle fds */
bool is_open;
} gpio_chip_t;
typedef struct {
uint64_t timestamp_ns; /* Kernel timestamp of edge */
uint32_t line_offset; /* Which line triggered */
uint32_t edge_type; /* RISING_EDGE or FALLING_EDGE */
} gpio_event_t;
Constants¶
Chip Lifecycle¶
gpio_open¶
Open a GPIO chip and query its info (name, number of lines).
| Parameter | Type | Description |
|---|---|---|
device | const char * | Chip device path (e.g., "/dev/gpiochip0") |
gpio_close¶
Close the chip and release all requested lines.
Output Control¶
gpio_output_setup¶
Request a GPIO line for output with an initial value.
| Parameter | Type | Description |
|---|---|---|
offset | uint32_t | Line offset (0-based within the chip) |
value | int | Initial value: 0 (low) or 1 (high) |
label | const char * | Consumer label for debugging |
gpio_output_set¶
Set the output value of a previously configured line.
Input / Edge Detection¶
gpio_input_edge_setup¶
Request a line for input with edge event detection. Returns an event file descriptor suitable for poll() or epoll.
Returns: Event FD on success (for use with poll/epoll), -errno on failure.
gpio_wait_event¶
Block until an edge event occurs on the line.
| Parameter | Type | Description |
|---|---|---|
event_fd | int | FD from gpio_input_edge_setup |
event | gpio_event_t * | Output event with timestamp and edge type |
timeout_ms | int | -1 = block, 0 = poll, >0 = milliseconds |
Returns: 0 on event, -ETIMEDOUT on timeout.
gpio_input_read¶
Read the current level of an input line (snapshot, no event waiting).
Usage Examples¶
LED Blink (Output)¶
#include "gpio.h"
#include <stdio.h>
#include <unistd.h>
#define LED_LINE 17 /* GPIO17 on Raspberry Pi */
int main(void)
{
gpio_chip_t chip;
int rc = gpio_open(&chip, "/dev/gpiochip0");
if (rc < 0) {
fprintf(stderr, "GPIO open failed: %s\n", strerror(-rc));
return 1;
}
printf("Chip: %s (%u lines)\n", chip.chip_name, chip.num_lines);
/* Setup LED pin as output, initially OFF */
gpio_output_setup(&chip, LED_LINE, 0, "my-led");
/* Blink 10 times */
for (int i = 0; i < 10; i++) {
gpio_output_set(&chip, LED_LINE, 1);
usleep(500000); /* 500ms on */
gpio_output_set(&chip, LED_LINE, 0);
usleep(500000); /* 500ms off */
}
gpio_close(&chip);
return 0;
}
Button Edge Detection (Input with Debounce)¶
#include "gpio.h"
#include <stdio.h>
#include <time.h>
#define BUTTON_LINE 27 /* GPIO27 */
#define DEBOUNCE_NS (50 * 1000000ULL) /* 50ms in nanoseconds */
int main(void)
{
gpio_chip_t chip;
gpio_open(&chip, "/dev/gpiochip0");
/* Request rising edge events (button press) */
int event_fd = gpio_input_edge_setup(&chip, BUTTON_LINE,
GPIO_EDGE_BOTH, "my-button");
if (event_fd < 0) {
fprintf(stderr, "Edge setup failed\n");
return 1;
}
printf("Waiting for button events on GPIO%d...\n", BUTTON_LINE);
uint64_t last_event_ns = 0;
for (int i = 0; i < 20; i++) {
gpio_event_t event;
int rc = gpio_wait_event(event_fd, &event, 10000);
if (rc == -ETIMEDOUT) {
printf(" (timeout)\n");
continue;
}
/* Software debounce */
if (event.timestamp_ns - last_event_ns < DEBOUNCE_NS) {
continue; /* Ignore bounced edge */
}
last_event_ns = event.timestamp_ns;
const char *edge_str = (event.edge_type == GPIOEVENT_EVENT_RISING_EDGE)
? "RISING" : "FALLING";
printf(" [%lu.%09lu] %s edge\n",
event.timestamp_ns / 1000000000ULL,
event.timestamp_ns % 1000000000ULL,
edge_str);
}
gpio_close(&chip);
return 0;
}
Integration with Epoll Reactor¶
#include "gpio.h"
#include "epoll_reactor.h"
static gpio_chip_t chip;
void on_gpio_event(int fd, uint32_t events, void *userdata)
{
(void)events;
uint32_t *count = (uint32_t *)userdata;
gpio_event_t event;
if (gpio_wait_event(fd, &event, 0) == 0) {
(*count)++;
printf("GPIO event #%u: edge=%s, ts=%lu ns\n", *count,
(event.edge_type == GPIOEVENT_EVENT_RISING_EDGE) ? "↑" : "↓",
event.timestamp_ns);
}
}
int main(void)
{
reactor_t reactor;
reactor_init(&reactor);
gpio_open(&chip, "/dev/gpiochip0");
int efd = gpio_input_edge_setup(&chip, 27, GPIO_EDGE_BOTH, "reactor-gpio");
uint32_t event_count = 0;
reactor_add(&reactor, efd, EPOLLIN, on_gpio_event, &event_count);
reactor_run(&reactor, -1);
gpio_close(&chip);
reactor_destroy(&reactor);
return 0;
}
Software Debounce¶
Mechanical switches produce contact bounce rapid on/off transitions over 1–10ms after a press/release. The chardev interface reports all edges, so debounce must be handled in userspace:
Physical button press:
┌──┐┌─┐┌──────────────────────────
│ ││ ││
────────────────┘ └┘ └┘
← bounce →← stable high →
(~5ms)
After 50ms debounce filter:
┌──────────────
│
────────────────────────────────────┘
↑ single clean edge reported
Compare timestamps between consecutive events and ignore those within the debounce window (default: 50ms).
Build & Run¶
# Compile
gcc -Wall -Wextra -Werror -O3 -std=c11 -o gpio_demo gpio.c
# Verify chip exists
ls /dev/gpiochip*
# Check chip info
cat /sys/class/gpio/gpiochip0/label
# Run (requires gpio group or root)
./gpio_demo
Prerequisites¶
Permissions
Access to /dev/gpiochip0 requires the gpio group or root:
- Linux kernel 4.8+ (chardev GPIO interface)
- Avoid the deprecated
/sys/class/gpio/sysfs interface
Test Output¶
$ ./build/gpio_demo
[gpio] Opened /dev/gpiochip0: "pinctrl-bcm2835" (54 lines)
[gpio] Output: GPIO17 = LOW (LED off)
[gpio] Output: GPIO17 = HIGH (LED on)
[gpio] Edge setup: GPIO27, BOTH edges
[gpio] Event: RISING edge at 1705234567.123456789
[gpio] Event: FALLING edge at 1705234567.623456789
[gpio] Debounce: filtered 3 bounced edges
[PASS] All gpio tests passed