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
- X is a number between 0 and 4 (inclusive)
- Y is one of A, B, C or D
- Z is a number between 0 and 7 (inclusive)
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
- ics.com — GPIO Programming: Exploring the libgpiod Library (libgpiod ≤ 1.6.x)