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. ``` #! /usr/bin/env python3 """ p_gpio is a Python module for the RaspberryPi Using gpiod Python Interface 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-01-04 import gpiod VERSION='0.1' # chip = gpiod.Chip('gpiochip0') # most Pi have gpiochip0 # I believe Pi5 uses 'gpiochip4' # The following should work for all models chip = gpiod.find_line('ID_SDA').owner() # find chip with ID_SDA gpiomap = {} # 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 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() ```