Search by Tags

GPIO Char Device API - libgpiod

 

Article updated at 20 Jan 2020
Compare with Revision




Subscribe for this article updates

Introduction

The Kernel Linux GPIO user space SysFS is deprecated and has been discontinued. It can still be enabled by CONFIG_GPIO_SYSFS but its use is discouraged and will be removed from mainline Linux after 2020. For GPIO access from user space the new char device API must be used.

The new user space API use the /dev/gpiochip devices through ioctl calls to manage GPIOs:

# ls /dev/gpiochip*
/dev/gpiochip0  /dev/gpiochip2  /dev/gpiochip4  /dev/gpiochip6
/dev/gpiochip1  /dev/gpiochip3  /dev/gpiochip5  /dev/gpiochip7

Each entry on the /dev/gpiochip corresponds to a GPIO bank that the operating system has access to.

This article complies to the Typographic Conventions for Torizon Documentation

Prerequisites

Operating System:

  • Torizon

Supported modules:

  • Apalis iMX8QM
  • Colibri iMX8X
  • Colibri iMX6
  • Colibri iMX7

GPIO Bank/Lines

To verify the GPIO banks and lines check the datasheet of the module on the section I/O Pins -> Functions List. For example, let's assume we need to access MXM3 X1 pin 5 (Apalis GPIO3) from an Apalis iMX8:

Apalis iMX8 I/O Pins -> Function List

From the datasheet we can see that Apalis MXM3 X1 Pin 5 has the GPIO function on ALT3 accessible at LSIO.GPIO0_IO12. This means we need to access GPIO bank 0 line 12.

Note: This bank and line nomenclature differs slightly depending on the processor family. Apalis iMX6 e.g. starts enumerating GPIO banks with 1, while GPIO banks in Linux are always start at 0. That means that GPIO2_IO6 on Apalis iMX6 is GPIO bank 1 line 6 on Linux.

libgpiod

libgpiod library encapsulates the ioctl calls and data structures behind a straightforward API.

Additionally, libgpiod project contains a set of command-line tools allowing access from shell scripts. These command-line tools can also be used to replace scripts which used the deprecated sysfs API directly.

libgpiod - Command Line Tools

For getting started with the libgpiod command-line tools let is exemplify some commands. We will use Torizon for testing with the following Dockerfile image:

Note: To build Container images for ARM architectures make sure to prepare your build environment according to the following article: Configure Build Environment for Torizon Containers

FROM torizon/arm64v8-debian-base

RUN apt-get -y update && apt-get install -y \
    gpiod \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# Allow the user torizon use GPIOs
RUN usermod -a -G gpio torizon

CMD [ "bash" ]

The above Dockerfile is built into the image torizonextras/arm64v8-gpiod.

Run the following command on the board terminal to download the image and mount the container for testing:

# docker run --rm -it --device /dev/gpiochip0 torizonextras/arm64v8-gpiod
FROM torizon/arm32v7-debian-base

RUN apt-get -y update && apt-get install -y \
    gpiod \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# Allow the user torizon use GPIOs
RUN usermod -a -G gpio torizon

CMD [ "bash" ]

The above Dockerfile is built into the image torizonextras/arm32v7-gpiod.

Run the following command on the board terminal to download the image and mount the container for testing:

# docker run --rm -it --device /dev/gpiochip0 torizonextras/arm32v7-gpiod

Note that in the command above the argument --device pass the char device from the GPIO bank that will be shared and accessed inside the container. You may specify that argument multiple times if access to different GPIO banks is required. Normally root user is required to access /dev/gpiochip devices. TorizonCore comes with udev rules which allow access for users part of the gpio group. Adding the torizon user to the group allows to also access GPIOs as an user from within the container (use USER torizon in your Dockerfile if you prefer running the container as non-root user).

GPIODETECT

Search for the GPIO banks, /dev/gpiochiop0 ... /dev/gpiochipX, and how many GPIO lines they have:

## gpiodetect
gpiochip0 [5d080000.gpio] (32 lines)

GPIOINFO

Read and displays the information contained in the GPIO bank lines:

## gpioinfo
gpiochip0 - 32 lines:
    line   0:      unnamed       unused   input  active-high 
    line   1:      unnamed       unused   input  active-high 
    line   2:      unnamed       unused   input  active-high 
    line   3:      unnamed       unused   input  active-high 
    line   4:      unnamed       unused   input  active-high 
    line   5:      unnamed       unused   input  active-high 
    line   6:      unnamed       unused   input  active-high 
    line   7:      unnamed       unused   input  active-high 
    line   8:      unnamed "ov5640_mipi_reset" output active-high [used]
    line   9:      unnamed "ov5640_mipi_pwdn" output active-high [used]
    line  10:      unnamed       unused   input  active-high 
    line  11:      unnamed       unused   input  active-high 
    line  12:      unnamed       unused   input  active-high 
    line  13:      unnamed       unused   input  active-high 
    line  14:      unnamed       unused   input  active-high 
    line  15:      unnamed       unused   input  active-high 
    line  16:      unnamed       unused   input  active-high 
    line  17:      unnamed       unused   input  active-high 
    line  18:      unnamed       unused   input  active-high 
    line  19:      unnamed       unused   input  active-high 
    line  20:      unnamed       unused   input  active-high 
    line  21:      unnamed       unused   input  active-high 
    line  22:      unnamed       unused   input  active-high 
    line  23:      unnamed       unused   input  active-high 
    line  24:      unnamed       unused   input  active-high 
    line  25:      unnamed       unused   input  active-high 
    line  26:      unnamed       unused   input  active-high 
    line  27:      unnamed       unused   input  active-high 
    line  28:      unnamed       unused   input  active-high 
    line  29:      unnamed       unused   input  active-high 
    line  30:      unnamed       unused   input  active-high 
    line  31:      unnamed "usb3503 connect" output active-high [used]

This command is useful to check which rows are being used, with their respective use descriptions, for a GPIO bank.

GPIOSET

Writes the output value of a certain line in a gpiochip passed by argument. The example set the GPIO bank 0 line 12 to output low:

## gpioset /dev/gpiochip0 12=0

You can also use only the GPIO bank index as a parameter:

## gpioset 0 12=0

Now the example to set the GPIO bank 0 line 12 to output high:

## gpioset 0 12=1

GPIOGET

Reads the value of input from a certain line in a gpiochip passed by argument:

## gpioget 0 13
1

The return of this command can be 1 if the input is high and 0 if the input is low.

## gpioget 0 13
0

GPIOMON

Wait for events on GPIO rows passed by argument:

## gpiomon 0 13
event: FALLING EDGE offset: 13 timestamp: [1570281706.661390750]
event: FALLING EDGE offset: 13 timestamp: [1570281706.661435750]
event:  RISING EDGE offset: 13 timestamp: [1570281706.661604000]
event:  RISING EDGE offset: 13 timestamp: [1570281706.916220125]
event: FALLING EDGE offset: 13 timestamp: [1570281706.918247625]

This command is useful for polling the lines to expect incoming input events.

LIBGPIOD - C Language Example

The example below uses the libgpiod API to access a GPIO bank and line that are the argument to the program:

#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
 
int main(int argc, char *argv[])
{
	struct gpiod_chip *output_chip;
	struct gpiod_line *output_line;
	int line_value = 0;
	int bank, line;
 
	/* check the arguments */
	if (argc > 2) {
		/* get GPIO bank argument */
		bank = atoi(argv[1]);
		/* get line argument */
		line = atoi(argv[2]);
	} else {
		printf("Example of use: test 0 12\n");
		return EXIT_FAILURE;
	}
 
	/* use libgpiod API */
 
	/* open the GPIO bank */
	output_chip = gpiod_chip_open_by_number(bank);
	/* open the GPIO line */
	output_line = gpiod_chip_get_line(output_chip, line);
	if (output_chip == NULL || output_line == NULL)
		goto error;
 
	/* config as output and set a description */
	gpiod_line_request_output(output_line, "gpio-test",
		GPIOD_LINE_ACTIVE_STATE_HIGH);
 
	while (1) {
		line_value = !line_value;
		gpiod_line_set_value(output_line, line_value);
		sleep(1);
		printf("Setting pin to %d\n", line_value);
	}
 
	return EXIT_SUCCESS;
 
error:
	printf("Error setting gpiod\n");
	return EXIT_FAILURE;
}

The following Dockerfile will compile the above program and build a Container image to be easily deployed to the board.

Note: For the examples below we will use the multi-stage Docker build.

Note: To build Container images for ARM architectures make sure to prepare your build environment according to the following article: Configure Build Environment for Torizon Containers

# First stage, x86_64 build container
FROM torizon/debian-cross-toolchain-arm64 AS cross-container

# install the libpiod development dependencies
RUN apt-get -y update && apt-get install -y \
    libgpiod-dev:arm64 \
    libgpiod2:arm64 \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# copy project source
COPY gpio-test.c /project/gpio-test.c
WORKDIR /project

# compile
RUN aarch64-linux-gnu-gcc -o gpio-test gpio-test.c -lgpiod

# Second stage, arm64v8 container for target
FROM torizon/arm64v8-debian-base AS deploy-container

# To run gpio-test we only need the libgpiod2 library. The gpiod package can
# be helpful for debugging.
RUN apt-get -y update && apt-get install -y \
    libgpiod2 \
    gpiod \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# get the compiled program from the Build stage
COPY --from=cross-container /project/gpio-test /deploy/gpio-test

# put the test binary as the entry point
ENTRYPOINT [ "/deploy/gpio-test" ]

# Use CMD to pass the GPIO bank line arguments
# Apalis iMX8 MXM3 pin 5 (Apalis GPIO3) is LSIO.GPIO0.IO12
CMD [ "0", "12" ]

Store the Dockerfile and gpio-test.c files in the same directory, open this directory in the terminal of your development PC and run the command:

$ docker build -f Dockerfile . -t yourdockerhubuser/arm64v8-libgpiod

Upload the image generated in the command above to your Docker Hub:

$ docker login
$ docker push yourdockerhubuser/arm64v8-libgpiod

After that access the board terminal, the following command will pull the image, from the Docker Hub to the board, and will execute it:

# docker run --rm -it --init --device /dev/gpiochip0 yourdockerhubuser/arm64v8-libgpiod
# First stage, x86_64 build container
FROM torizon/debian-cross-toolchain-armhf AS cross-container

# install the libpiod development dependencies
RUN apt-get -y update && apt-get install -y \
    libgpiod-dev:armhf \
    libgpiod2:armhf \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# copy project source
COPY gpio-test.c /project/gpio-test.c
WORKDIR /project

# compile
RUN arm-linux-gnueabihf-gcc -o gpio-test gpio-test.c -lgpiod

# Second stage, arm32v7 container for target
FROM torizon/arm32v7-debian-base AS deploy-container

# To run gpio-test we only need the libgpiod2 library. The gpiod package can
# be helpful for debugging.
RUN apt-get -y update && apt-get install -y \
    libgpiod2 \
    gpiod \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# get the compiled program from the Build stage
COPY --from=cross-container /project/gpio-test /deploy/gpio-test

# put the test binary as the entry point
ENTRYPOINT [ "/deploy/gpio-test" ]

# Use CMD to pass the GPIO bank line arguments
# Apalis iMX6 MXM3 pin 5 (Apalis GPIO3) is GPIO2_IO6
# (gpiochip0 -> i.MX 6 GPIO1, gpiochip1 -> i.MX 6 GPIO2)
CMD [ "1", "6" ]

Store the Dockerfile and test.c files in the same directory, open this directory in the terminal of your development PC and run the command:

$ docker build -f Dockerfile . -t yourdockerhubuser/arm32v7-libgpiod

Upload the image generated in the command above to your Docker Hub:

$ docker login
$ docker push yourdockerhubuser/arm32v7-libgpiod

After that access the board terminal, the following command will pull the image, from the Docker Hub to the board, and will execute it:

# docker run --rm -it --init --device /dev/gpiochip1 yourdockerhubuser/arm32v7-libgpiod

Put a LED or check the signal level of Apalis MXM3 pin 5 (Apalis GPIO3) with a multimeter, the GPIO should toggle its state every second.

LIBGPIOD - Example with events

This example uses the libgpiod API to register for an event (interrupt driven) on a rising edge. For this example make sure to add 4 arguments, the first two specifying the bank and line number of the input GPIO and the next two for the bank and line number of the output GPIO (e.g. CMD [ "1", "5", "1", "6" ] for Apalis GPIO2/3 on Apalis iMX6).

#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
 
struct gpiod_line *get_gpio_line(int bank, int gpio)
{
	struct gpiod_chip *chip;
	struct gpiod_line *line;
 
	/* open the GPIO bank */
	chip = gpiod_chip_open_by_number(bank);
	if (chip == NULL)
	       goto error;
 
	/* open the GPIO line */
	line = gpiod_chip_get_line(chip, gpio);
	if (line == NULL)
		goto error;
 
	return line;
 
error:
	perror("Error setting gpiod\n");
	return NULL;
}
 
int main(int argc, char *argv[])
{
	struct gpiod_line *output_line;
	struct gpiod_line *input_line;
	struct gpiod_line_event event;
	int line_value = 0;
	int ret;
 
	/* check the arguments */
	if (argc < 5) {
		printf("Usage: "
			"gpio-event-test <input-bank> <input-gpio> <output-bank> <output-gpio>\n");
		return EXIT_FAILURE;
	}
 
	input_line = get_gpio_line(atoi(argv[1]), atoi(argv[2]));
 
	output_line = get_gpio_line(atoi(argv[3]), atoi(argv[4]));
 
	ret = gpiod_line_request_rising_edge_events(input_line, "gpio-test");
	if (ret < 0) {
		perror("Request events failed\n");
		return EXIT_FAILURE;
	}
 
	ret = gpiod_line_request_output(output_line, "gpio-test",
		GPIOD_LINE_ACTIVE_STATE_HIGH);
	if (ret < 0) {
		perror("Request output failed\n");
		return EXIT_FAILURE;
	}
 
	while (1) {
		gpiod_line_event_wait(input_line, NULL);
 
		if (gpiod_line_event_read(input_line, &event) != 0)
			continue;
 
		/* this should always be a rising event */
		if (event.event_type != GPIOD_LINE_EVENT_RISING_EDGE)
			continue;
 
		/* toggle output */
		line_value = !line_value;
		printf("Setting pin to %d\n", line_value);
		gpiod_line_set_value(output_line, line_value);
	}
 
	return EXIT_SUCCESS;
 
error:
	printf("Error setting gpiod\n");
	return EXIT_FAILURE;
}