GPIO Programming: Exploring the libgpiod Library
After a hiatus of more than a year, I wanted to pick up this blog series and cover another GPIO library available on Linux. Since the last time I wrote on this topic, this library has become more widely supported and available on platforms like the Raspberry Pi, so the time was right to explore it further.
I mentioned in an earlier blog post that the GPIO sysfs interface was being deprecated in favor of a new GPIO character device API. In this blog post we'll look at libgpiod, a C library and tools for interacting with the Linux GPIO hardware.
And yes, it is a library and not a daemon process, as I initially assumed based on the common Linux convention of having long-running background processes that handle requests for services named ending in "d", like systemd and cupsd. In this context gpiod stands for GPIO device.
Installation
If you are running a recent version of the Raspberry Pi OS (formerly known as Raspbian), you can install the gpiod command line tools and library by installng the package gpiod. The header files required for development are contained in the package libgpdiod-dev. Similar packages are available on Ubuntu and other Debian-derived Linux distributions.
Command Line Tools
Gpiod provides a set of command line tools that are very useful for interactively exploring GPIO functions, and can be used in shell scripts to avoid the need to write C or C++ code if you only need to perform basic GPIO functions. The following commands are provided:
gpiodetect - List all GPIO chips present on the system, their names, labels and number of GPIO lines.
gpioinfo - List all lines of specified GPIO chips, their names, consumers, direction, active state and additional flags.
gpioget - Read values of specified GPIO lines.
gpioset - Set values of specified GPIO lines, and potentially keep the lines exported and wait until timeout, user input or signal.
gpiofind - Find the GPIO chip name and line offset given the line name.
gpiomon - Wait for events on GPIO lines, specifying which events to watch, how many events to process before exiting or if the events should be reported to the console.
Here is some sample output taken from a Raspberry Pi system. The gpiodetect program will detect the GPIO chips that are present. The library uses the term "chip" to identify groups of GPIO hardware functions which may or may not correspond to hardware-level chips. In the case of the Raspberry Pi the GPIO hardware is all contained in the Broadcom SOM (system on a module).
% gpiodetect
gpiochip0 [pinctrl-bcm2835] (54 lines)
gpiochip1 [raspberrypi-exp-gpio] (8 lines)
The output of gpioinfo reports all of the available GPIO lines, by default for all chips:
% gpioinfo
gpiochip0 - 54 lines:
line 0: "ID_SDA" unused input active-high
line 1: "ID_SCL" unused input active-high
line 2: "SDA1" unused input active-high
line 3: "SCL1" unused input active-high
line 4: "GPIO_GCLK" unused input active-high
line 5: "GPIO5" unused input active-high
line 6: "GPIO6" unused input active-high
line 7: "SPI_CE1_N" unused input active-high
line 8: "SPI_CE0_N" unused input active-high
line 9: "SPI_MISO" unused input active-high
line 10: "SPI_MOSI" unused input active-high
line 11: "SPI_SCLK" unused input active-high
line 12: "GPIO12" unused input active-high
line 13: "GPIO13" unused input active-high
line 14: "TXD1" unused input active-high
line 15: "RXD1" unused input active-high
line 16: "GPIO16" unused input active-high
line 17: "GPIO17" unused input active-high
line 18: "GPIO18" unused input active-high
line 19: "GPIO19" unused input active-high
line 20: "GPIO20" unused input active-high
line 21: "GPIO21" unused input active-high
line 22: "GPIO22" unused input active-high
line 23: "GPIO23" unused input active-high
line 24: "GPIO24" unused input active-high
line 25: "GPIO25" unused input active-high
line 26: "GPIO26" unused input active-high
line 27: "GPIO27" unused input active-high
line 28: "RGMII_MDIO" unused input active-high
line 29: "RGMIO_MDC" unused input active-high
line 30: "CTS0" unused input active-high
line 31: "RTS0" unused input active-high
line 32: "TXD0" unused input active-high
line 33: "RXD0" unused input active-high
line 34: "SD1_CLK" unused input active-high
line 35: "SD1_CMD" unused input active-high
line 36: "SD1_DATA0" unused input active-high
line 37: "SD1_DATA1" unused input active-high
line 38: "SD1_DATA2" unused input active-high
line 39: "SD1_DATA3" unused input active-high
line 40: "PWM0_MISO" unused input active-high
line 41: "PWM1_MOSI" unused input active-high
line 42: "STATUS_LED_G_CLK" "led0" output active-high [used]
line 43: "SPIFLASH_CE_N" unused input active-high
line 44: "SDA0" unused input active-high
line 45: "SCL0" unused input active-high
line 46: "RGMII_RXCLK" unused input active-high
line 47: "RGMII_RXCTL" unused input active-high
line 48: "RGMII_RXD0" unused input active-high
line 49: "RGMII_RXD1" unused input active-high
line 50: "RGMII_RXD2" unused input active-high
line 51: "RGMII_RXD3" unused input active-high
line 52: "RGMII_TXCLK" unused input active-high
line 53: "RGMII_TXCTL" unused input active-high
gpiochip1 - 8 lines:
line 0: "BT_ON" unused output active-high
line 1: "WL_ON" unused output active-high
line 2: "PWR_LED_OFF" "led1" output active-low [used]
line 3: "GLOBAL_RESET" unused output active-high
line 4: "VDD_SD_IO_SEL" "vdd-sd-io" output active-high [used]
line 5: "CAM_GPIO" unused output active-high
line 6: "SD_PWR_ON" "sd_vcc_reg" output active-high [used]
line 7: "SD_OC_N" unused input active-high
As you can imagine, the gpioget and gpioset commands allow reading and writing GPIO input and output lines.
A simple example is the following which sets line 24 of the first chip to a high output level for one second and then releases it:
% gpioset --mode=time -s 1 0 24=1
The following will read input channel 6 of the first GPIO chip and output it:
% gpioget 0 6
1
As I mentioned, the command line tools are often adequate for simple low-speed applications, and can be put in shell scripts or called as external programs. For better control over GPIO functions or applications which require more critical timing, you can call the APIs in the libgpiod library from a high level language such as C or C++. Note that there are also Python and other language bindings available as well.
Libgpiod Library API
The C API allows calling the gpiod library from C or languages that support C APIs like C++. The API is well documented, and too extensive to fully cover here. The basic use cases usually follows these steps:
- Open the desired GPIO chip by calling one of the gpiod_chip_open functions such as gpiod_chip_open_by_name(). This returns a gpiod_chip struct which is used by subsequent API calls.
- Open the desired GPIO line(s) by calling gpiod_chip_get_line() or gpiod_chip_get_lines(), obtaining a gpiod_line struct.
- Request use of the line as an input or output by calling gpiod_line_request_input() or gpiod_line_request_output().
- Read the value of an input by calling gpiod_line_get_value() or set the level of an output by calling gpiod_line_set_value().
- When done, release the lines by calling gpiod_line_release() and chips by calling gpiod_chip_close().
Other APIs are provided for more advanced functions like setting pin modes for pullup or pulldown resistors or defining a callback function to be called when an event occurs, like the level of an input pin changing.
Example Program
Let's look at a simple example, which works on a Raspberry Pi using the GPIO board that was previously discussed in this blog series. It toggles the three LEDs in a binary pattern until the pushbutton connected to a GPIO input pin is pressed. The code here was slightly simplified for readability by removing error checking. You can download the full example and a suitable CMake project file from reference 2 listed at the end of the blog post. I encourage you to try it out on a Raspberry Pi.
#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
const char *chipname = "gpiochip0";
struct gpiod_chip *chip;
struct gpiod_line *lineRed; // Red LED
struct gpiod_line *lineGreen; // Green LED
struct gpiod_line *lineYellow; // Yellow LED
struct gpiod_line *lineButton; // Pushbutton
int i, val;
// Open GPIO chip
chip = gpiod_chip_open_by_name(chipname);
// Open GPIO lines
lineRed = gpiod_chip_get_line(chip, 24);
lineGreen = gpiod_chip_get_line(chip, 25);
lineYellow = gpiod_chip_get_line(chip, 5);
lineButton = gpiod_chip_get_line(chip, 6);
// Open LED lines for output
gpiod_line_request_output(lineRed, "example1", 0);
gpiod_line_request_output(lineGreen, "example1", 0);
gpiod_line_request_output(lineYellow, "example1", 0);
// Open switch line for input
gpiod_line_request_input(lineButton, "example1");
// Blink LEDs in a binary pattern
i = 0;
while (true) {
gpiod_line_set_value(lineRed, (i & 1) != 0);
gpiod_line_set_value(lineGreen, (i & 2) != 0);
gpiod_line_set_value(lineYellow, (i & 4) != 0);
// Read button status and exit if pressed
val = gpiod_line_get_value(lineButton);
if (val == 0) {
break;
}
usleep(100000);
i++;
}
// Release lines and chip
gpiod_line_release(lineRed);
gpiod_line_release(lineGreen);
gpiod_line_release(lineYellow);
gpiod_line_release(lineButton);
gpiod_chip_close(chip);
return 0;
}
Summary
If you need to perform GPIO programming on a Raspberry Pi or other Linux-based embedded platform, the recommended approach is to use gpiod, either from a high level language like C or C++ or by using the provided command line tools. Replacing the older and now deprecated sysfs-based interface, it is more flexible, efficient, and easier to use from a high-level language.
If you missed earlier installments in our GPIO series, start here.