Skip to content

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

#define GPIO_MAX_LINES      64
#define GPIO_DEBOUNCE_MS    50   /* Default software debounce threshold */

Chip Lifecycle

gpio_open

int gpio_open(gpio_chip_t *chip, const char *device);

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

void gpio_close(gpio_chip_t *chip);

Close the chip and release all requested lines.

Output Control

gpio_output_setup

int gpio_output_setup(gpio_chip_t *chip, uint32_t offset, int value, const char *label);

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

int gpio_output_set(gpio_chip_t *chip, uint32_t offset, int value);

Set the output value of a previously configured line.

Input / Edge Detection

gpio_input_edge_setup

int gpio_input_edge_setup(gpio_chip_t *chip, uint32_t offset,
                          gpio_edge_t edge, const char *label);

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

int gpio_wait_event(int event_fd, gpio_event_t *event, int timeout_ms);

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

int gpio_input_read(gpio_chip_t *chip, uint32_t offset, int *value);

Read the current level of an input line (snapshot, no event waiting).

Usage Examples

#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:

sudo usermod -aG gpio $USER
# Or add a udev rule:
echo 'SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="gpio", MODE="0660"' | \
    sudo tee /etc/udev/rules.d/99-gpio.rules

  • 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