ラズパイ５でGPIOをC言語で操作するLibrary - libgpiommem

# 📌 はじめに
Raspberry Pi 5 のGPIOを扱う場合、これまでのModel A/B/3 ...の手法は使えず、かと言って現時点（2025/03/10)まとまった正規ドキュメントがなく、手探り状態でした。
そこで手がかりのあった、**pinctrl command**をハックしてC言語からGPIOを操作し、ついでそれをもとにmmemを直接操作するライブラリ **libgpiommem.so**を制作し公開します。

# 📌 これまでの方法
これまで試行錯誤した **Raspberry Pi 5 の GPIO 操作手法と、その結果 (成功/失敗)** をまとめます。  

---

# **📌 Raspberry Pi 5 GPIO 操作の試行錯誤まとめ**
| 方法 | 目的 | 結果 | 理由・問題点 |
|------|------|------|--------------|
| **`sysfs GPIO` (`/sys/class/gpio/`)** | `echo 21 > /sys/class/gpio/export` で GPIO21 を制御 | ❌ **失敗** | **Raspberry Pi 5 では `sysfs` GPIO が無効化されている** (カーネル仕様変更) |
| **`libgpiod` (`gpioset`, `gpioinfo`)** | `libgpiod` を使用し、GPIO21 を ON/OFF | ❌ **失敗** | `gpiochip571` などが `gpioinfo` に表示されない (`/dev/gpiochipX` が見つからない) |
| **`pinctrl` を使った手動制御 (`pinctrl set 21 hi`)** | `pinctrl` コマンドで GPIO を制御 | ✅ **部分成功** | GPIO のモード (`op`) は変更できたが、**`mmap` との連携が不完全** |
| **`/dev/mem` + `mmap` (`devmem2` を使ったレジスタ操作)** | `devmem2` で GPIO のレジスタを直接変更 | ❌ **失敗** | Raspberry Pi 5 では **レジスタ構成が異なる (`RP1-PIO`)** ため、従来の `devmem2` では動作せず |
| **`/dev/gpiomemX` (`gpiomem0` ~ `gpiomem4`)** | `/dev/gpiomemX` を使って GPIO 制御 | ❌ **失敗** | Raspberry Pi 5 では `/dev/gpiomemX` が使えない (`/dev/gpiomem` 自体が非推奨) |
| **`gpiod_ctxless_set_value()` (`libgpiod` の C API)** | `gpiod_ctxless_set_value()` で GPIO を制御 | ❌ **失敗** | `gpiod_chip_open_by_name("gpiochip571")` がエラー (`No such file or directory`) |
| **`pigpio` (`pigpiod` デーモンを使った GPIO 操作)** | `pigpio` を使って GPIO 制御 | ❌ **失敗** | `pigpiod` が **Raspberry Pi 5 に対応していない** (`gpioHardwareRevision: unknown rev code`) |
| **`rpio` (`rpio` ライブラリを使った GPIO 制御)** | `rpio` で GPIO を制御 | ❌ **失敗** | `rpio` が Raspberry Pi 5 に未対応 (`E: パッケージ rpio が見つかりません`) |
| **`pinctrl` のソースコードをハック** | `pinctrl` のコードを修正し、GPIO の制御を可能に | ✅ **成功！** | **GPIO を `mmem` で直接操作する方法を確立！ (`libgpiommem`)** |
| **`mmem` を使った GPIO 高速制御 (`libgpiommem`)** | `mmem` で GPIO レジスタを直接操作 | ✅ **成功！** | **15MHz 以上の高速 GPIO 制御に成功** (`libgpiommem` としてライブラリ化) |
---
正に、死屍累々、試してはNG、試してはNGの繰り返して疲弊すました。
# 📌 pinctrl とは
raspberry pi utilsに含まれるプログラムです。
![pinctrl](https://github.com/raspberrypi/utils)
先に上げた、方法とは違いラズパイ５でも動作します。
ところが、あくまでLinux commandですから、Cからsystem()で呼び出しても、速度は出ません。でも pinctrl内部では、mmem()を使ってkernel memryを直に操作しているので、その操作をそっくり真似てGPIOを制御できるはずです。
# 📌 手順
https://github.com/raspberrypi/utils
上記からソース一式をいただき展開します。
使用するのは utils/pinctrl だけです。
```:bash
cd utils
cp -r pinctrl libgpiommem
cd libgpiommem
```
# 📌 Makefile を用意する
utilはCmakeで管理されていますが、単一のlibraryなのでmakeで独立に管理します。
```:Makefile
CC = gcc
CFLAGS = -O2 -Wall -fPIC
LDFLAGS = -shared
PREFIX = /usr/local
LIB_NAME = libgpiommem
SRC_FILES = util.c gpiolib.c gpiochip_bcm2835.c gpiochip_bcm2712.c gpiochip_rp1.c
OBJ_FILES = $(SRC_FILES:.c=.o)
HEADER_FILES = gpiolib.h

all: libgpiommem.so

libgpiommem.so: $(OBJ_FILES)
        $(CC) $(LDFLAGS) -o $@ $(OBJ_FILES)

%.o: %.c
        $(CC) $(CFLAGS) -c $< -o $@

install:
        install -d $(PREFIX)/lib $(PREFIX)/include
        install -m 755 libgpiommem.so $(PREFIX)/lib/
        install -m 644 $(HEADER_FILES) $(PREFIX)/include/

clean:
        rm -f *.o *.so

```
# 📌 公開
本来は、ここで試験を行いますが、今回試験済みなので後回しにして公開します。
マシンの別の場所からも参照できるようになります。
```:bash
sudo make install
echo "/usr/local/lib" | sudo tee -a /etc/ld.so.conf.d/libgpiommem.conf
sudo ldconfig
```
# 📌 試験プログラムを書きます
任意の場所に作業用Dirを作って移動します。
```:bash
mkdir gpiommem
cd gpiommem
```
# 📌 はじめはLチカ
サンプルプログラムを書きます。
```:t_gpio_out.c
// MIT License.
// Copy right 2025 by Shigeru Makino.

#include <stdio.h>
#include <unistd.h>
#include <gpiolib.h>

#define LED_GPIO 21  // LED 用 GPIO

int main() {
    int ret;

    ret = gpiolib_init();
    if(ret < 0){
        printf("fail gpiolib_init = %d\n", ret);
        return -1;
    }

    ret = gpiolib_mmap();
    if(ret < 0){
        printf("fail gpiolib_mmap = %d\n", ret);
        return -1;
    }
    gpio_set_fsel(LED_GPIO, GPIO_FSEL_OUTPUT);

    printf("Blinking LED on GPIO %d...\n", LED_GPIO);

    while (1) {
        gpio_set_drive(LED_GPIO, 1);
        usleep(50000);
        gpio_set_drive(LED_GPIO, 0);
        usleep(50000);
    }

    return 0;
}
```
# 📌 make します
こちらにもMakefileをつくります。
```:Makefile
all: t_gpio_out

t_gpio_out: t_gpio_out.c
	gcc -o t_gpio_out t_gpio_out.c -lgpiommem
```
makeします。
```:bash
make
sudo ./t_gpio_out
```
# 📌 動作試験
GPIO21にLED+330ΩをつけLチカを確認してください。
usleep(50000)を調整すると周期が変わります。取ってしまったら、ocillo scopeでみて約20MHzでした。
![20MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245853/8c5b4d5c-f0e4-4ec4-80c5-ebea77a287d4.png)


# 📌 入出力試験
先立って、Libの開発とサンプルの開発を分けましょう。
```:bash
mkdir ../gpiommem_test
cp t_libgpiommen.c ../gpiommem_test
cd ../gpiommem_test
```
# 📌 入出力試験
出力を確かめたので、入力も試験します。
```:t_gpio_io.c
// MIT License.
// Copy right 2025 by Shigeru Makino.

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "gpiolib.h"

int main()
{
    int input = 0;

    unsigned int gpio20 = 20;
    unsigned int gpio21 = 21;
    int ret;

    ret = gpiolib_init();
    if(ret < 0){
        printf("fail gpiolib_init = %d\n", ret);
        return -1;
    }

    ret = gpiolib_mmap();
    if(ret < 0){
        printf("fail gpiolib_mmap = %d\n", ret);
        return -1;
    }

    gpio_set_fsel(gpio20, GPIO_FSEL_INPUT);
    gpio_set_pull(gpio20, PULL_UP);

    gpio_set_fsel(gpio21, GPIO_FSEL_OUTPUT);
    gpio_set_pull(gpio21, PULL_NONE);
    gpio_set_drive(gpio21, DRIVE_LOW);


    while(1){
        input = gpio_get_level(gpio20);
        if (input == 0) {
            gpio_set_drive(gpio21, DRIVE_HIGH);
        }
        else {
            gpio_set_drive(gpio21, DRIVE_LOW);
        }
    }

    return 0;
}
```
Makefilを改造します。
```:Makefile
all: t_gpio_out t_gpio_io

t_gpio_out: t_gpio_out.c
	gcc -o t_gpio_out t_gpio_out.c -lgpiommem
t_gpio_io: t_gpio_io.c
	gcc -o t_gpio_io t_gpio_io.c -lgpiommem

clean:
	rm t_gpio_out t_gpio_io

```
```:bash
make
sudo ./t_gpio_io
```
GPIO20とGPIO21をショートするとリングカウンターになり発振します。周波数は225kHzでした。
![225kHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245853/8b179399-6ab3-4b96-a1ff-b0a59f2e0ec9.png)

# 📌 まとめ
⚡ 他の従来の方法 (sysfs, libgpiod, /dev/gpiomem, pigpio, rpio) はすべて動作せず！
🔧 最終的に pinctrl をハックし、libgpiommap を構築することで問題を解決！