Pi IoT In Python Using Linux Drivers - GPIO Character Driver
Written by Mike James & Harry Fairhead   
Monday, 29 January 2024
Article Index
Pi IoT In Python Using Linux Drivers - GPIO Character Driver
Installing libgpiod and Python Binding
Working With More Than One Line

The libgpiod library is installed along with the tools discussed at the start of the chapter, but you don’t have to use it if you are happy with the ioctl system calls described in the next chapter.

The library splits into two parts, the context-less functions and the lower-level functions, which can be considered context-using. In nearly all cases you will want to use the lower-level functions as the context-less functions have some serious drawbacks. Let’s take a look at each in turn.

To use the library you will need to, if you haven’t already done so, install it:

sudo apt-get install gpiod libgpiod-dev libgpiod-doc

and you need the Python bindings to use the C library from Python:

sudo apt-get install python3-libgpiod

There are other ways to install the bindings and the library but at the time of writing using apt-get is the easiest. There are also other wrapper libraries that are designed to make using the gpio character device easier, but the basic bindings are simple enough.

The Python bindings only make a subset of the full libgpiod C library available for use, but they are the most important and useful functions.

Using libgpiod

The Python bindings make the basic functioning of the GPIO device context functions available for use. Behind the scenes the GPIO driver implements a set of files that represent the GPIO hardware. The Python wrapper uses these to access the hardware, but it mostly hides this from you by providing objects that do the same job.

The procedure to work with a GPIO line is always the same, you have to:

  1. Open the GPIO chip you want to use

  2. Get the GPIO line or lines you want to use

  3. Configure and use the lines

  4. Close the GPIO chip.

GPIOd Object

If you include:

import gpiod

in your project you have automatic access to a gpiod object.

First you have to open the GPIO chip of your choice and this is done using variations on the constructor:


where id identifies the GPIO chip and idType is one of:


For example:

chip = gpiod.Chip('/dev/gpiochip0', 
gpiod.Chip.OPEN_BY_PATH) chip = gpiod.Chip('gpiochip0', gpiod.Chip.OPEN_BY_NAME) chip = gpiod.Chip('gpio-mockup-A',
chip = gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER)

If you just don’t specify an option OPEN_LOOKUP is assumed and will open the chip based on the best guess what the first parameter is. For example, all of the previous examples can be written:

chip = gpiod.Chip('/dev/gpiochip0')
chip = gpiod.Chip('gpiochip0')
chip = gpiod.Chip('gpio-mockup-A')
chip = gpiod.Chip('0')

If the chip cannot be opened the constructor returns:

FileNotFoundError: [Errno 2] No such file or directory

which you can catch using:

except OSError as ex:

If it works then a Chip object is returned. You have to close the Chip object when you are finished with it.

Notice that the Pi 5 has more gpio chips than other models and the one that is connected to the external GPIO pins is gpiochip4. This means that when using the programs in this book with a Pi 5 you need to replace 0 by 4 in references such as chip = gpiod.Chip("0").

The Chip object has a small number of functions that can be used to find out about it:

import gpiod
print(chip.num_lines()) chip.close()

If you run this on a Pi other than a Pi 5 what you will see is:


On a Pi 5 you need to use:


and this produces:


You can also use the ChipIter iterator to list each of the GPIO chips in a system. For example:

import gpiod
for chip in gpiod.ChipIter():



Getting Lines

The main purpose of getting a Chip object is to get a Line object. The simplest way of doing this is to use the get_line method. You specify which line you want by its offset – a count of lines starting from zero. The GPIO line offset nearly always corresponds to the GPIO line number used internally by the processor. So, for example:


returns a line object corresponding to GPIO0.

You can also find lines by name:

line = chip.find_line(”name”)

You can also close the line:


The Line object has all of the methods and properties you need to work with the GPIO line. You can find out the current configuration using:

line=chip.get_line(1) offset = line.offset()
name = line.name()
consumer = line.consumer()
direction = line.direction()
active_state = line.active_state()
used = line.is_used() requested = line.is_requested()
open_drain = line.is_open_drain()
open_source = line.is_open_source()

There is also the small matter of whether a line is already in use. The old sysfs GPIO driver used export and unexport to reserve and release lines. The new driver simply reserves the line when you open it as a file or, using libgpiod, when you get the line from the GPIO chip, and unreserves it when you release it. Behind the scenes the file is opened when you create a Line object and closed when you release it.

Notice that many other Linux drivers use the GPIO driver to access lines and hence using a device via a driver might well reserve the GPIO lines it uses.

You can only use a line when it isn’t already in use and you can test to see if a line is free using:


which returns true if the line is in use. Notice that you cannot find out why a line is in use – it might be the operating system or another process. It could even be that the current process already has the line in use. You can check for this condition using:

requested = line.is_requested()

If this returns true the current process has ownership of the line and can use it without getting it.

Using Lines

Once you have a Line object you can use it to configure the line. There is a simple configuration method:

line.request(consumer, type, flags, default_val)

where consumer is the name of the consumer and usually the name of the program, The type of the request can be:







The EV event types are discussed in Chapter 6.

There are three configuration flags:



The default value of the line, default_val, is specified as a List for historical reasons and is now deprecated.

For example, to set a line to output, with consumer set to myprog.py and a default value of 0 you would use:


Once you have a line set to output you can set it using:


Once you have a line set to input you can read it using:

value = line.get_value() 

For example, a simple program to toggle GPIO4 as fast as possible is:

import gpiod
type=gpiod.LINE_REQ_DIR_OUT,default_vals=[0]) while(True): line.set_value(1) line.set_value(0)

The first part of the program opens the GPIO chip, remember to change this to "4" for the Pi 5, and retrieves GPIO4 which is set to output. Finally the infinite loop simply toggles the line. Notice that you cannot close the GPIO chip and continue to use the line.

In this case we didn’t close the lines or the chip for simplicity, but in principle you always should. Only when the line is closed does its status change from used to unused and another process can make use of it. In nearly all cases, however, the line is closed when your program ends.

If you try the program, you will discover that the pulses produced are around 2.4µs on a Pi 5, 4.2µs on Pi 4, 10µs on a Pi Zero 2W and 44µs on a Pi Zero. This should be compared to 0.8µs on Pi 4 and 2.0µs on a Pi Zero achievable using the same driver and C.

Last Updated ( Wednesday, 31 January 2024 )