Search by Tags

GPIO Char Device API - libgpiod

 
Applicable for

Article updated at 04 Nov 2019
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 the mainline after 2020. For pin access by the 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 SODIMM pin 5 from an Apalis iMX6:

Apalis iMX6 I/O Pins -> Function List

From the datasheet, we can see that X1 Pin (from Apalis SODIMM) 5 is the LSIO.GPIO0_IO12, from the ALT3 mux GPIO function. This means we need to access GPIO bank 0 line 12.

Note: This bank and line nomenclature may change slightly depending on the processor family

libgpiod

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

Additionally, libgpiod project contains a set of command-line tools that should allow easy conversion of user scripts to using the character device.

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: For build Docker images from ARM architecture base follow the article: Configure Build Environment for Torizon Containers

FROM arm64v8/debian:buster-slim

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

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 arm32v7/debian:buster-slim

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

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.

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

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

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[])
{
	int meeting_number;
	struct gpiod_chip *output_chip;
	struct gpiod_line *output_line;
	int line_value;
	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 -1;
	}
 
	/* 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, "libgpiodTest",
		GPIOD_LINE_ACTIVE_STATE_HIGH);
 
	while (1) {
		line_value ^= 1;
		gpiod_line_set_value(output_line, line_value);
		sleep(1);
		printf("Setting pin to %d\n", line_value);
	}
 
	return 0;
 
error:
	printf("Error setting gpiod\n");
	return -190;
}

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

Note: For build Docker images from ARM architecture base follow the article: Configure Build Environment for Torizon Containers

# ----------------------------------------------------------------- first stage
FROM torizonextras/arm64v8-binutils AS Build

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

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

# compile
RUN gcc -o test test.c -lgpiod

# ---------------------------------------------------------------- second stage
FROM arm64v8/debian:buster-slim AS Deploy

# for the deploy we only need the library
RUN apt-get -y update && apt-get install -y \
    libgpiod2 \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

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

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

# the cmd is the bank line arguments
CMD [ "0", "12" ]

Leave 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 yourDockerUserName/arm64v8-libgpiod

Upload the image generated in the command above to your Dockerhub:

$ docker login
$ docker push yourDockerUserName/arm64v8-libgpiod

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

# docker run --rm -it --device /dev/gpiochip0 yourDockerUserName/arm64v8-libgpiod
# ----------------------------------------------------------------- first stage
FROM torizonextras/arm32v7-binutils AS Build

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

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

# compile
RUN gcc -o test test.c -lgpiod

# ---------------------------------------------------------------- second stage
FROM arm32v7/debian:buster-slim AS Deploy

# for the deploy we only need the library
RUN apt-get -y update && apt-get install -y \
    libgpiod2 \
    && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

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

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

# the cmd is the bank line arguments
CMD [ "0", "12" ]

Leave 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 yourDockerUserName/arm32v7-libgpiod

Upload the image generated in the command above to your Dockerhub:

$ docker login
$ docker push yourDockerUserName/arm32v7-libgpiod

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

# docker run --rm -it --device /dev/gpiochip0 yourDockerUserName/arm32v7-libgpiod

Put an LED or check the signal level of GPIO bank 0 line 12 with a multimeter, it should be changing their state between one and one second.