In Bookworm (and latest Bullseye) the kernel device driver for GPIO has been updated so GPIO settings are persistent and it is now possible to specify pulls.
This means libgpiod
behaves similarly to sysfs
(and most tools accessing registers).
There is kernel documentation and a python module gpiod
but it is difficult to follow (unless you are a kernel developer) and no User Documentation.
There are a few examples e.g. https://www.tomshardware.com/how-to/control-raspberry-pi-5-gpio-with-python-3
The current libgpiod is old (v1.6.3 in Bookworm and even older in Bullseye) the v2 code is significantly different but it unclear if or when this may be included in Raspberry Pi OS.
I decided to test the gpiod
module and write a wrapper which uses the same functions as my pi_gpio
library.
This obviously excludes I²C, SPI or Hardware PWM but I have code implementing these using kernel drivers which will be included in a final library as will Information functions.
This code has been updated and tested on a Pi5 and allows existing code to run with minimal change, but if writing new code use gpiod
or lgpio
(which includes additional functionality).
#! /usr/bin/env python3
"""
p_gpio is a Python module for the RaspberryPi
Using gpiod Python Interface
This is a wrapper which uses the same functions as pi_gpio library.
OVERVIEW
*GPIO*
setup_gpio - Set gpio as an input or an output
input_gpio - Returns the GPIO level__plibrary
output_gpio - Output to a GPIO channel
input_28 - Returns value of GPIO 0-27
output_28 - Sets value of GPIO 0-27
gpio_function - The current GPIO mode
get_pullupdn - The current GPIO pull/up down
"""
# 2024-04-04
import gpiod
VERSION='0.2'
chip = gpiod.find_line('ID_SDA').owner() # find chip with ID_SDA
gpiomap = {} # This is a dictionary to avoid repeated chip.get_line(gpio) calls
# GPIO
# NOTE all gpio use Broadcom BCM numbers
INPUT=0
OUTPUT=1
# LINE_REQ_DIR_AS_IS = 1
# LINE_REQ_DIR_IN = 2
# LINE_REQ_DIR_OUT = 3
# LINE_REQ_EV_BOTH_EDGES = 6
# LINE_REQ_EV_FALLING_EDGE = 4
# LINE_REQ_EV_RISING_EDGE = 5
# LINE_REQ_FLAG_ACTIVE_LOW = 4
# LINE_REQ_FLAG_BIAS_DISABLE = 8
# LINE_REQ_FLAG_BIAS_PULL_DOWN = 16
# LINE_REQ_FLAG_BIAS_PULL_UP = 32
# LINE_REQ_FLAG_OPEN_DRAIN = 1
# LINE_REQ_FLAG_OPEN_SOURCE = 2
# ACTIVE_HIGH = 1
# ACTIVE_LOW = 2
# BIAS_AS_IS = 1
# BIAS_DISABLE = 2
# BIAS_PULL_DOWN = 4
# BIAS_PULL_UP = 3
# DIRECTION_INPUT = 1
# DIRECTION_OUTPUT = 2
def setup_gpio(gpio, direction, pud):
# def setup_gpio(gpio, direction, pud=0): # optional pud
"""
Set gpio as an input or an output
direction: 0=IN, 1=OUT
pud: 0=None 1=Up 2=Down
"""
line = chip.get_line(gpio)
gpiomap[gpio] = line
if direction:
line.request(consumer="p_gpio", type=gpiod.LINE_REQ_DIR_OUT)
else:
f = gpiod.LINE_REQ_FLAG_BIAS_DISABLE
if pud==1:
f = gpiod.LINE_REQ_FLAG_BIAS_PULL_UP
if pud==2:
f = gpiod.LINE_REQ_FLAG_BIAS_PULL_DOWN
line.request("p_gpio", gpiod.LINE_REQ_DIR_IN, f)
print('pud= ', pud, 'bias= ', line.bias())
def input_gpio(gpio):
"""
Input from a GPIO channel.
Returns HIGH=1=True or LOW=0=False
"""
return gpiomap[gpio].get_value()
def output_gpio(gpio, value):
"""
Output to a GPIO channel.
value - 0/1 or False/True or LOW/HIGH
"""
gpiomap[gpio].set_value(value)
def gpio_function(gpio):
"""
Returns the current GPIO direction
Only works if gpio in use
Returns 0,1 (IN, OUT)
"""
if gpio in gpiomap:
return gpiomap[gpio].direction() - 1
return 0
def get_pullupdn(gpio):
"""
Return the current GPIO pull
Only works if gpio in use
Returns
0:None/Unknown
1:Up
2:Down
"""
if gpio in gpiomap:
return gpiomap[gpio].bias()-2
return 0
# Dummy functions
def setup():
pass
def cleanup():
pass
#-------------
import time
def main():
global chip
SigOUT = 12
SigIN = 13
LOOPS = 20000
setup_gpio(SigOUT, 1, 0)
# setup_gpio(SigIN, 0, 0)
# setup_gpio(SigIN, 0, 1)
setup_gpio(SigIN, 0, 2)
t0 = time.time()
for i in range(LOOPS):
output_gpio(SigOUT, 1)
output_gpio(SigOUT, 0)
t1 = time.time()
print("gpiod Python\t{:>10.0f} toggles per second".format((1.0 * LOOPS) / (t1 - t0)))
output_gpio(SigOUT, 1)
print("{}".format(input_gpio(SigIN)))
print('SigIN function= ', gpio_function(SigIN))
print('SigOUT function= ', gpio_function(SigOUT))
print('pud= ', get_pullupdn(SigIN))
print('pud= ', get_pullupdn(4))
if __name__ == '__main__':
main()
gpiod
V2 can be installed with pip
in a Python venv
and is much improved (but NOT backward compatible with V1).