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 msLikewise, 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 delayTo 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.