ラズパイ５GPIOをPythonで動かす - libgpiommem Wrapper

# はじめに
ラズパイでGPIOを操作する方法はたくさんありますが、その大部分がラズパイ５では動かないという驚愕の現実がありCで操作する、[libgpiommem](https://qiita.com/maclineto/items/6d8cf04b4df1969dedc8)を制作し公開しました。
少なからず反響があり、「Pythonでもこれを使いたい」という声があったため、Wrapperを作りましたので、公開します。
# 簡単な使い方
- [libgpiommem.so](https://qiita.com/maclineto/items/6d8cf04b4df1969dedc8)を書いてある手順で展開しinstallします。
- ここに書いた手順でPyGpioMmenをDLします。
- サンプルプログラムを試験します。
# GitLabからのFile取得
簡単に使えるよう[GitLab](https://gitlab.com/fanfanlab/pygpiommem/-/tree/main/)にもファイルを置きました。ご利用ください。

# PyGpioMmem本体
```:python
# MIT License.
# Copy right 2025 by Shigeru Makino.

import ctypes

# ライブラリのロード
libgpiommem = ctypes.CDLL("libgpiommem.so")

# 戻り値の型を設定
libgpiommem.gpiolib_init.restype = ctypes.c_int
libgpiommem.gpiolib_mmap.restype = ctypes.c_int
libgpiommem.gpio_set_fsel.argtypes = [ctypes.c_uint, ctypes.c_int]  # `GPIO_FSEL_T` は `int`
libgpiommem.gpio_set_pull.argtypes = [ctypes.c_uint, ctypes.c_int]
libgpiommem.gpio_set_drive.argtypes = [ctypes.c_uint, ctypes.c_int]
libgpiommem.gpio_get_level.argtypes = [ctypes.c_uint]
libgpiommem.gpio_get_level.restype = ctypes.c_int

# GPIO 定数
PULL_NONE = 0
PULL_UP = 1
PULL_DOWN = 2
DRIVE_LOW = 0
DRIVE_HIGH = 1

# `GPIO_FSEL_INPUT` と `GPIO_FSEL_OUTPUT` をハードコード（C の定義に基づく）
GPIO_FSEL_INPUT = 16  # `0x10` の値
GPIO_FSEL_OUTPUT = 17  # `GPIO_FSEL_INPUT + 1`

class GPIO:
    def __init__(self):
        """ GPIOライブラリの初期化 """
        ret = libgpiommem.gpiolib_init()
        if ret < 0:
            raise RuntimeError("Failed to initialize gpiolib")

        ret = libgpiommem.gpiolib_mmap()
        if ret < 0:
            raise RuntimeError("Failed to mmap gpiolib")

    def set_mode(self, pin, mode):
        libgpiommem.gpio_set_fsel.argtypes = [ctypes.c_uint, ctypes.c_int]  # `GPIO_FSEL_T` は int
        libgpiommem.gpio_set_fsel.restype = ctypes.c_int  # 戻り値を整数に設定
        ret = libgpiommem.gpio_set_fsel(ctypes.c_uint(pin), ctypes.c_int(mode))
        return ret

    def set_pull(self, pin, pull):
        """ GPIO のプル設定 """
        libgpiommem.gpio_set_pull(pin, pull)

    def write(self, pin, value):
        """ GPIO の出力を設定 """
        libgpiommem.gpio_set_drive.argtypes = [ctypes.c_uint, ctypes.c_int]  # 型を明示
        libgpiommem.gpio_set_drive.restype = None  # C 側で `void` の場合
        libgpiommem.gpio_set_drive(ctypes.c_uint(pin), ctypes.c_int(value))

    def read(self, pin):
        """ GPIO の入力を取得 """
        return libgpiommem.gpio_get_level(pin)
```
# 原理と問題点
ctypes を実直に使っています。
C言語側がenumで引数を使っているので、Pythonと齟齬が生じ面倒なのでdefineしてます。大本のpinctrl command で変更があると、追従できません。
# Sampleと使い方
## blink.py
出力試験です。高速のLチカです。
```:python
import pygpiogpmem as gpmem

gpio = gpmem.GPIO()

gpio21 = 21

# GPIO の設定
gpio.set_mode(gpio20, gpmem.GPIO_FSEL_INPUT)
gpio.set_pull(gpio20, gpmem.PULL_UP)

gpio.set_mode(gpio21, gpmem.GPIO_FSEL_OUTPUT)

# 初期化チェック
print("GPIO initialized successfully!")

while True:
    gpio.write(gpio21, gpmem.DRIVE_HIGH)
    gpio.write(gpio21, gpmem.DRIVE_LOW)
```

### 実行
```:bash
sudo python3 blink.py
```
GPIO21に矩形波が出ます。
最大応答速度を計測するため、システムに負荷がかかります。本来はusleep(1)を挟んで、調整してください。
オシロで測って430kHzが出ました。![430kHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245853/a48b71ef-3025-4f93-b80b-390f03e7766a.png)


## ring.py
```:python
import pygpiommem as gpmem

gpio = gpmem.GPIO()

gpio20 = 20
gpio21 = 21

# GPIOの設定
gpio.set_mode(gpio20, gpmem.GPIO_FSEL_INPUT)
gpio.set_pull(gpio20, gpmem.PULL_UP)

gpio.set_mode(gpio21, gpmem.GPIO_FSEL_OUTPUT)
gpio.write(gpio21, gpmem.DRIVE_LOW)

while True:
    if gpio.read(gpio20) == 0:
        gpio.write(gpio21, gpmem.DRIVE_HIGH)
    else:
        gpio.write(gpio21, gpmem.DRIVE_LOW)
```
![2025-03-12-165646.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245853/e6e5197d-0558-416b-a59a-d0b1ae77c4e1.jpeg)


### 実行
```:bash
sudo python3 ring.py
```
GPIO20,GPIO21をショートするとリング発振器になります。
周波数は133kHzでした。
![133kHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245853/2344210a-c53a-45ea-bc42-331bcc7dbf2e.png)

# まとめ
- libgipimmem にラッパーをかけ python で使えるようにしました
- 簡単にPythonでGPIO入出力ができます