Using GPIO

Using GPIO on the Quartz64 and SOQuartz computers works like on any other SBC using the modern mainline way: through libgpiod.

However, to reasonably translate pins from the datasheet or GPIO pinout diagrams to the chip and line numbers libgpiod requires, we need to go over some more information.

The pins in schematics are usually called something like GPIOX_YZ where

For example, a pin may be called GPIO2_B3 in the schematic. To now translate this into a gpio chip number and line number, we need a little table.

The chip number is easy enough. X, the number immediately following GPIO, directly maps to the gpio chip number we want.

For the line number, we can use the following table, shown here as a list of C defines:

#define RK_GPIO_A0          0
#define RK_GPIO_A1          1
#define RK_GPIO_A2          2
#define RK_GPIO_A3          3
#define RK_GPIO_A4          4
#define RK_GPIO_A5          5
#define RK_GPIO_A6          6
#define RK_GPIO_A7          7
#define RK_GPIO_B0          8
#define RK_GPIO_B1          9
#define RK_GPIO_B2          10
#define RK_GPIO_B3          11
#define RK_GPIO_B4          12
#define RK_GPIO_B5          13
#define RK_GPIO_B6          14
#define RK_GPIO_B7          15
#define RK_GPIO_C0          16
#define RK_GPIO_C1          17
#define RK_GPIO_C2          18
#define RK_GPIO_C3          19
#define RK_GPIO_C4          20
#define RK_GPIO_C5          21
#define RK_GPIO_C6          22
#define RK_GPIO_C7          23
#define RK_GPIO_D0          24
#define RK_GPIO_D1          25
#define RK_GPIO_D2          26
#define RK_GPIO_D3          27
#define RK_GPIO_D4          28
#define RK_GPIO_D5          29
#define RK_GPIO_D6          30
#define RK_GPIO_D7          31

Debian Bookworm (libgpiod ≤ 1.6.x)

From The Command Line

To use GPIO from the command line, we can use the libgpiod tools contained in the gpiod package.

We can list the available GPIO chips and lines with sudo gpioinfo. You should see at least 5 gpiochips (0 to 4) listed with 32 lines (0 to 31) each.

We can read the state of a GPIO line with sudo gpioget chipnum offset1 offset2...

For example, sudo gpioget 3 20 would read the pin numbered "7" on the Quartz64 Model B's GPIO header, as it is GPIO3_C4 which translates to chip 3, line 20. For more options to the gpioget command, please consult man gpioget.

Analogous to this, we can set a gpio line with sudo gpioset chipnum offset1=value1 offset2=value2...

For example, the command sudo gpioset 3 20=1 would set the aforementioned pin numbered 7 on the Quartz64 Model B's GPIO header to a "high" value.

Please consult man gpioset for more information about other uses, such as to set pin biases.

From C

TODO: Write this section. (Sorry!)

From Python

You'll need to install python3-libgpiod for this.

Blinking a LED

This example turns on GPIO3_B3 (pin header pin number 29 on the Quartz64 Model B) for 3 seconds, and then turns it off again. You can use this for example to turn on an LED connected to it (with a current limiting resistor.)

#!/usr/bin/env python3
import sys
from time import sleep
import gpiod


def main():
    # first, open the /dev/gpiochip3 character device
    # You can use "with" syntax for this instead if you'd rather not explicitly
    # close the device after you're done
    chip = gpiod.Chip('3', gpiod.Chip.OPEN_BY_NUMBER)
    led = chip.get_line(11)     # Get B3 of GPIO3
    # request the line with our script name as the consumer and direction output
    # NOTE: do *not* use type=Line.DIRECTION_OUTPUT here! These bindings suck!
    led.request(sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT)
    led.set_value(1)            # turn on the LED
    sleep(3)                    # wait for 3 seconds
    led.set_value(0)            # turn off the LED
    # close the chip character device again
    chip.close()
    # you may no longer use the chip or line associated with it after this point


if __name__ == '__main__':
    main()

Reading From GPIOs

In this example, we assume a button connected to the aforementioned GPIO3_B3 (header pin number 29 on the Quartz64 Model B) between the pin itself and logic level high (3.3V). Note that in this case, we use the with syntax to automatically close the GPIO chip once it goes out of scope.

#!/usr/bin/env python3
import sys
import gpiod


def main():
    # first, open the /dev/gpiochip3 character device
    with gpiod.Chip('3', gpiod.Chip.OPEN_BY_NUMBER) as chip:
        button = chip.get_line(11)     # Get B3 of GPIO3
        # request the line with our script name as the consumer, and
        # input direction.
        button.request(sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
        val = -1
        prev_val = -1
        try:
            # very power unfriendly busy loop here
            # an actual program waiting for buttons would use events instead
            while True:
                prev_val = val
                val = button.get_value()
                if val != prev_val:
                    print(val)
        except KeyboardInterrupt:
            return
    # you may no longer use the chip or line associated with it after this point
    # as it gets closed once we leave the "with" block


if __name__ == '__main__':
    main()

Waiting On GPIO Events

In this example, we assume a button connected to the aforementioned GPIO3_B3 (header pin number 29 on the Quartz64 Model B) between the pin itself and logic level high (3.3V). We print out whether a button was pressed or released whenever the state changes. Instead of busy looping, we're waiting on events.

#!/usr/bin/env python3
import sys
import gpiod


def main():
    # first, open the /dev/gpiochip3 character device
    with gpiod.Chip('3', gpiod.Chip.OPEN_BY_NUMBER) as chip:
        button = chip.get_line(11)     # Get B3 of GPIO3
        # Request a GPIO sensitive to both rising and falling edges,
        # with a pull-down enabled so the pin isn't floating.
        # Note: pull-ups on the RK3566 GPIOs don't seem to work
        button.request(sys.argv[0], type=gpiod.LINE_REQ_EV_BOTH_EDGES,
                       flags=gpiod.LINE_REQ_FLAG_BIAS_PULL_DOWN)
        try:
            while True:
                # Wait on events with a timeout of 1 hour. The python bindings
                # are extremely bad so we can't actually tell it to wait
                # indefinitely because ts never gets NULL'd
                if button.event_wait(3600):
                    # read all the available events for us
                    # if using a real world button, you may want to debounce
                    # this (i.e. ignore events that happened too quickly)
                    for ev in button.event_read_multiple():
                        if ev.type == gpiod.LineEvent.RISING_EDGE:
                            print("Button pressed! Timestamp sec={} nsec={}"
                                  .format(ev.sec, ev.nsec))
                        elif ev.type == gpiod.LineEvent.FALLING_EDGE:
                            print("Button released! Timestamp sec={} nsec={}"
                                  .format(ev.sec, ev.nsec))
        except KeyboardInterrupt:
            return
    # you may no longer use the chip or line associated with it after this point
    # as it gets closed once we leave the "with" block


if __name__ == '__main__':
    main()

More Information

Please consult the examples in /usr/share/doc/python3-libgpiod/examples for further information, or inspect the module's documentation from an interactive Python interpreter with help(gpiod).

Debian Trixie (libgpiod ≥ 2.0)

Not to be confused with the package name "libgpiod2" in Bookworm (sigh), libgpiod 2.x.x makes breaking API changes.

TODO: Wait for Trixie to release with libgpiod 2.x and write this section with all the fun changes that aren't documented anywhere.

Further Reading