GPIO on a Raspberry Pi CM5

ElectronicsTechnical

libgpio explained: using GPIO on a Raspberry Pi CM5 ioboard in Python

Using the GPIO on a Raspberry Pi CM5 ioboard is not straight forward at the time of writing this article. The RPi.GPIO library does not work on a CM5 and libgpio is "the preferred way of using GPIO" apparently. The libgpio python bindings API sadly is complicated to use while the documentation is useless. The syntax is not described there, available arguments are not explained, and the example is convoluted and intransparent. Also it seems to me, the API was written in a way that is good to write, but is neither easy to use nor understand. On the plus side, the library exists.

The only way to figure out how to use this library is to read the doc strings and the source code. As you don't have the time for that, I'll break it down for you.

How to use gpiod

There are two things you need to accomplish:

  1. configure the pin
  2. access the pin

Let's look at an example for reading a pin state of how to do that. Prerequisites are installing gpiod and running "usermod -a -G gpio your_username" in the terminal.

import gpiod

GPIO_CHIP = "/dev/gpiochip4"    # The chip to use
BUTTONPIN = 24          # GPIO-number/line offset

with gpiod.Chip(path=GPIO_CHIP) as gpiochip:
    gpioconfig = {
        "direction": gpiod.line.Direction.INPUT,
        "bias": gpiod.line.Bias.PULL_UP,
        "edge_detection": gpiod.line.Edge.NONE
    } 
    gpiolinesetting = gpiod.LineSettings(**gpioconfig)

    linerequest_config = {
        "config": {BUTTONPIN: gpiolinesetting},
        "consumer": "some-name-you-can-make-up-freely"
    }

    with gpiochip.request_lines(**linerequest_config) as gpiolines:
        pinstate = gpiolines.get_value(BUTTONPIN).value

    print(pinstate)

Here is a step-by-step explanation:

GPIO_CHIP = "/dev/gpiochip4"
BUTTONPIN = 24

These are the chip and the GPIO number used in this example. The chip path can be found by surveying /dev and maybe trying out the available options. "pinctrl" is a useful program to try out the pins, which comes with libgpio.

The pin number can be found on the official(?) pinout website. The shell command "pinout" should also do that job, but at the time of writing it doesn't work on the CM5. gpiod requires GPIO numbers, not actual pin numbers as with the Arduino. They are called "line offset" in libgpio.

with gpiod.Chip(path=GPIO_CHIP) as gpiochip:

This line opens the gpio chip for interaction. As the chip is represented as a character device, it needs to be opened and closed like a normal file. The "with" statements makes that more secure.

The next part holds the convoluted configuration:

    gpioconfig = {
        "direction": gpiod.line.Direction.INPUT,
        "bias": gpiod.line.Bias.PULL_UP,
        "edge_detection": gpiod.line.Edge.NONE
    } 
    gpiolinesetting = gpiod.LineSettings(**gpioconfig)

    linerequest_config = {
        "config": {BUTTONPIN: gpiolinesetting},
        "consumer": "some-name-you-can-make-up-freely"
    }

The first part defines the configuration of the pin you want to use. Config parameters can be found in site-packages/gpio/line.py and site-packages/gpio/line_settings.py. To make it complicated, they have to be defined using gpiod.line objects instead of strings and the defined settings have to be wrapped in a class instance afterwards instead of using the dictionary directly. But using **kwargs makes this at least more readable.

The second part connects the configuration to the pin number (aka "line"). It is again a dictionary of parameters that will be expanded with **kwargs in the following line request. The first paramter, called "config", expects a dict with line numbers as keys and LineSettings objects as values. So the syntax is: {line number: LineSettingsObject}, where "line number" can also be a tuple of numbers to assigns the config to all the specified lines. There is another argument called "consumer", which needs to exist for some reason, but is meaningless in basic usage.

    with gpiochip.request_lines(**linerequest_config) as gpiolines:
        pinstate = gpiolines.get_value(BUTTONPIN).value

Finally the gpio pins can be used. As the "lines" also need to be "closed" (gpiod.Chip.release() ) after interaction, this is best done using another "with"-statement. Otherwise it will read "busy" on the next try.

Edge detection

Did not actually work at the time of writing.

gpiod and async

When using asyncio, the lines can be polled without blocking the script with sleep statements. For that, the async function polling the pin will need to contain a loop with an async.sleep() statement.

Apparently there is an async watch line value in the C-library (see here).