Citizen Engineer

Hardware Hacking with Linux SBCs Just Got Easier

by ladyada@alum.mit.edu and fill@2600.com

Back in February 2018, Bartosz Golaszewski, speaking at Free and Open-Source Developers' European Meeting (FOSDEM) at the ULB Solbosch campus in Brussels, announced a new GPIO interface for Linux user space.

What's that?  Before we get started, let's discuss Single Board Computers (SBC) and all the Linux-y hardware that's out there now.

According to linuxgizmos.com, there are more than 100 Linux boards that are under $200 and run Linux or Android.  They have a comprehensive list at: linuxgizmos.com/january-2018-catalog-of-hacker-friendly-sbcs

and a spreadsheet comparison at: docs.google.com/spreadsheets/d/e/2PACX-1vT_J1QkNQIFfMdcLisDUs4j-imyqwwsUpyn7RTKNYyz857TdT5PMlyeKTZ_lxemRIls-td9lAmupzo7/pubhtml

You've probably heard of Raspberry Pi, which is by far the most popular single-board computer in the world according to the estimates we're able to gather.

There are well over 15 million units in the wild.  That's a lot of Linux and a lot of potential to do hardware hacking.  The challenge is... each board interacts with hardware differently, and that brings us to libgpiod.

libgpiod is intended to be a fast kernel-level-supported method for writing/reading/monitoring GPIO pins on various Linux boards, replacing the two main methods that are currently used: sysfs file pokin' and devmem twiddling.

With sysfs style control, you end up with files such as /sys/class/gpio/gpio16 (for pin #16) that you can write and read from to set direction and read values.  It works O.K., and is very easy to use with shell scripts, but is clunky from C or Python, and is slow and incomplete (for example, pull-up/pull-downs are not supported).

With devmem twiddling, you open up a file point directly to chip memory and start poking and prodding at the registers directly, somewhat similar to older computers' "peek" and "poke" commands, or the microcontroller style PORTB |= 0x01.

Benefits?  It is heckin' fast.  Downsides are that its a horrible idea to poke into memory, and you end up having to make register maps for each processor and revision because the registers move around.

You can see some example code for aDHT driver for Raspberry Pi here: github.com/adafruit/Adafruit_Python_DHT/blob/master/source/Raspberry_Pi/pi_mmio.c

and then this different code for a BeagleBone (the register map is different): github.com/adafruit/Adafruit_Python_DHT/blob/master/source/Beaglebone_Black/bbb_mmio.c

As you can tell, this quickly becomes hard to support and it's dangerous - you need to be running as root and it's incredibly easy to accidentally poke the wrong location.

With more Linux boards coming out with GPIO (there's probably a dozen more released since we wrote this), having a consistent, reliable, complete GPIO interface is pretty important to avoid icky unmaintainable code.  So we're pretty psyched about libgpiod.

From our experiments, it's much much faster than sysfs.  It is not as fast as direct memory twiddling, but that's not too surprising - there are still kernel messages and error checking done.

A Pi 3 got us 400 kHz pin output toggles in a loop in C, 100 kHz in Python examples, and that's pretty good for bit-banging.  (For SPI or I2C, use the hardware peripherals - they can go multi-MHz and can be shared between processes!)

We really like the Python 3 bindings for libgpiod.  They're very easy to use.  Here's some example code for blinking an LED.

This will work on any computer with GPIO pins and libgpiod.  The only thing you might have to change is the PIN definition to match the pin you have an LED connected on!

#!/usr/bin/python
import time
import gpiod

# The name of the GPIO peripheral, almost always gpiochip0
CHIP = "gpiochip0"

# This is the 'offset' for the chip, e.g. Raspi GPIO #18 is pin number 18
PIN = 18

# Open the chip
with gpiod.Chip(CHIP) as chip:

    # Get control of the GPIO
    line = chip.get_line(PIN)

    # Set the pin to be an output, the consumer string can be anything
    line.request(consumer="blinky.py", type=gpiod.LINE_REQ_DIR_OUT)

# Toggle!
while True:
    Line.set_value(1)  # LED ON
    Time.sleep(0.1)    # Wait 100 ms
    Line.set_value(0)  # LED OFF
    Time.sleep(0.1)    # Wait 100 ms

Likewise, here's a very simple example for reading a button press (digital input).

This example does a little more, like keeping track of wire state.  A pull-up resistor is required to maintain a non-floating state.  Then connect a normal button or switch between the pin and ground.

#!/usr/bin/python
import time
import gpiod

# The name of the GPIO peripheral, almost always gpiochip0
CHIP = "gpiochip0"

# This is the 'offset' for the chip, e.g. Raspi GPIO #18 is pin number 18
PIN = 18

# Connect a ~10K pullup resistor from this pin to 3.3V (or whatever your logic is)
# Open the chip
with gpiod.Chip(CHIP) as chip:

    # Get control of the GPIO
    line = chip.get_line(PIN)

    # Set the pin to be an input, the consumer string can be anything
    line.request(consumer="button.py", type=gpiod.LINE_REQ_DIR_IN)

    # We'll keep track of the last button state, so we print changes
    last_button_status = line.get_value()

    while True:
        # Read the line
        line_status = line.get_value()

    # Did the state change?
    if line_status != last_button_status:

        # Print what happened!
        if line_status:
            print 'button just released'
        else:
            print 'button just pressed'
        last_button_status = line_status

    time.sleep(0.01)  # De-bounce buttons by putting a light delay

To get you started with more advanced projects in C, we have a basic "collect GPIO pulses and store them in a circular buffer" program here: github.com/adafruit/libgpiod_pulsein

This is basically code that will replace some Python DHT drivers we released, and has the benefit of being forward compatible with any other Linux board that runs a 4.8+ kernel.

We'll slowly be replacing the code we previously released to use libgpiod, so that we can have broad support for Raspberry Pi, Onion or Banana Boards, BeagleBone or Onion.io.

There's not a lot of libgpiod code out there, and libgpiod doesn't come stock on Linux distros yet, which may be why it's taking a little while to catch on.

There are bindings for C and Python.  Here's a script that can help you get started by compiling it for you: github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/main/converted_shell_scripts/libgpiod.sh

If you have any controlling votes in a distribution, please have libgpiod available through your package manager!

The latest code is here, and as you can tell there's a lot of active development: git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git

Good night and good luck.

Return to $2600 Index