保守注(2026年9月1日):本ページは現在、現行のRaspberry Pi GPIOとCPython拡張のワークフローを先に示し、末尾に2019年本文の全文を日付付きアーカイブとして保存しています。旧文のwiringPi番号前提、
setup.pyを直接実行するコマンド、デバッグ記録は当時の環境を述べたもので、現行手順ではありません。
元のプロジェクトはPythonから3Dプリンターのモーターを制御し、動作不良を観測してGPIO呼び出しをCへ移しました。これは有用な性能調査ですが、「Pythonが遅い」だけでは診断になりません。GPIOバックエンド、Linuxスケジューリング、呼び出しごとのオーバーヘッド、電気ドライバー、モーションコントローラーのいずれもボトルネックになり得ます。本ガイドでは一度に全スタックを置き換えず、各層を分離します。
Table of Contents
1. 問題を解ける最小の現行レイヤーを選ぶ
新しいRaspberry Pi OSプロジェクトでは、次の順に選びます。
| 必要なこと | 現行の出発点 |
|---|---|
| ヘッダーピンの識別 | Raspberry Piのpinoutコマンドと公式ボード図 |
| ボタン、LED、ドライバー経由のリレー、通常のイベント | GPIO Zeroと既定のlgpio pin factory |
まだ再設計できない既存RPi.GPIO API | rpi-lgpio互換実装を評価し、実際のPiモデルで試験 |
| LinuxネイティブGPIOアクセス | インストール済みメジャーバージョンを確認してからlibgpiod |
| Pythonアプリ内のCPU負荷が高い処理 | バッチ計算だけを受け持つ、小さく計測済みのCPython拡張 |
| 決定的なモーターパルスまたはハードリアルタイム動作 | ユーザー空間のタイミングループではなく、専用モーターコントローラー、マイコン、適切なハードウェア周辺機能 |
Raspberry PiのGPIOベストプラクティス文書は、Linux GPIOキャラクターデバイス・スタックを将来向けの抽象層としています。依存関係があればGPIO Zeroは現在LGPIOFactoryを選び、その文書はlgpioがRaspberry Pi 5を含む全Piモデルをサポートすると説明しています。
現在活発に保守されているWiringPiリポジトリは、旧チュートリアルで一般的だった放棄済みリリースとは別物です。version 3の言語ラッパーもCライブラリとの同期が保証されません。新規Pythonプロジェクトで2019年の環境を再現するためだけに、wiringPi番号や未検証のPythonラッパーを導入しないでください。
2. コードを書く前に配線・番号・権限を確認する
配線を変える前にPiの電源を切ります。Raspberry Pi GPIOは3.3 Vロジックです。公式ハードウェア文書は、3.3 V部品への5 V入力を避け、LEDには電流制限抵抗を付け、モーターはGPIOへ直結せずHブリッジまたはモーター制御ボードを使うよう警告しています。
実際のヘッダーとキャラクターデバイスを調べます。
pinout
ls -l /dev/gpiochip*
id -nG
本ガイドはBroadcom GPIO番号を使います。LED(17)はGPIO17、つまり物理ヘッダーpin 11です。物理pin 17でもwiringPi pin 17でもありません。プロジェクトの配線図にはBCM番号と物理pinの両方を記録します。
GPIOアクセス権は通常gpioグループ所属で得ます。サービスアカウントに不足している場合は、管理者が最小限必要なグループ所属を追加し、新しいログインセッションを開始します。権限エラーをアプリ全体のroot実行や、全/dev/gpiochip*の全員書き込み許可で解消しないでください。
3. GPIO Zeroの基準動作を確立する
Raspberry Pi OSではディストリビューションのパッケージを優先します。システムパッケージを使える仮想環境ならハードウェア・バインディングを再利用できます。
sudo apt update
sudo apt install --yes python3-gpiozero python3-venv python3-dev build-essential
python3 -m venv .venv --system-site-packages
. .venv/bin/activate
python -c 'from importlib.metadata import version; print(version("gpiozero"))'
Device.pin_factoryは遅延生成されるため、最初のdevice作成後に調べます。GPIO Zeroのmock factoryを選び、ハードウェアを通電せずにアプリのロジックを先に試します。
from gpiozero import Device, LED
from gpiozero.pins.mock import MockFactory
Device.pin_factory = MockFactory()
with LED(17) as led:
led.on()
assert led.value == 1
led.off()
assert led.value == 0
この試験が通ってから、電流制限抵抗付きLEDをGPIO17とgroundへ接続し、ハードウェアの基準動作を実行します。
from gpiozero import Device, LED
from signal import pause
with LED(17) as led:
print(type(Device.pin_factory).__name__)
led.blink(on_time=0.5, off_time=0.5)
pause()
現行Piで失敗したら、ライブラリを変更する前に、モデル、Raspberry Pi OSリリース、カーネル、GPIO Zeroバージョン、選択されたpin factory、/dev/gpiochip*権限を記録します。
4. Cを追加する前にボトルネックを測る
sleep()を削除しても意図的な待ち時間が消えるだけで、遅延がPythonディスパッチ、GPIOバックエンド、Linuxスケジューリング、ハードウェアのどこから来るかは分かりません。次の3項目を別々に測ります。
- GPIO呼び出しをfakeに置き換えたアプリ計算時間
- 安全なLED試験回路だけを駆動したときのAPI呼び出し時間
- ロジックアナライザーまたはオシロスコープで測る物理エッジのタイミング
次の小さな診断はPythonからバックエンドまでの呼び出し時間を測ります。電気エッジの正確な発生時刻ではありません。
from statistics import median
from time import perf_counter_ns
from gpiozero import LED
durations = []
with LED(17) as led:
for _ in range(1000):
started = perf_counter_ns()
led.toggle()
durations.append(perf_counter_ns() - started)
print({
"minimum_ns": min(durations),
"median_ns": int(median(durations)),
"maximum_ns": max(durations),
})
代表的なシステム状態で繰り返し、1回の結果を信じないでください。中央値が良くても最大遅延はパルス生成に影響します。ループをCへ移せばPython呼び出しの負荷は減らせますが、汎用Linuxがハードリアルタイム制御器になるわけではありません。
5. 計測したバッチ処理だけにC拡張を追加する
安全なパターンは、Pythonがデバイス所有権と上位制御を維持し、自己完結したCPU負荷の高い処理を1回の拡張呼び出しでCへ渡すことです。次の例は取得済みバイトバッファーの状態遷移を数えます。GPIO lineを要求せず、モーターを切り替えず、タイミング保証もしません。
gpiofast.cを作成します。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *
count_edges(PyObject *self, PyObject *args)
{
Py_buffer samples;
const unsigned char *data;
Py_ssize_t edges = 0;
(void)self;
if (!PyArg_ParseTuple(args, "y*:count_edges", &samples)) {
return NULL;
}
data = (const unsigned char *)samples.buf;
for (Py_ssize_t index = 1; index < samples.len; index++) {
if ((data[index - 1] == 0) != (data[index] == 0)) {
edges++;
}
}
PyBuffer_Release(&samples);
return PyLong_FromSsize_t(edges);
}
static PyMethodDef gpiofast_methods[] = {
{"count_edges", count_edges, METH_VARARGS,
"Count low/high transitions in a bytes-like sample buffer."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef gpiofast_module = {
PyModuleDef_HEAD_INIT,
.m_name = "gpiofast",
.m_doc = "Small batch helpers for GPIO sample analysis.",
.m_size = -1,
.m_methods = gpiofast_methods,
};
PyMODINIT_FUNC
PyInit_gpiofast(void)
{
return PyModule_Create(&gpiofast_module);
}
pyproject.tomlを作成します。
[build-system]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
直接実行するコマンドではなく、ビルド設定としてsetup.pyを作成します。
from setuptools import Extension, setup
setup(
name="gpiofast",
version="0.1.0",
ext_modules=[Extension("gpiofast", ["gpiofast.c"])],
)
有効化済み仮想環境内で、pipのビルドインターフェースを通してビルド・インストールします。
python -m pip install .
python - <<'PY'
import gpiofast
samples = bytes([0, 0, 1, 1, 0, 1])
assert gpiofast.count_edges(samples) == 3
print("extension test passed")
PY
実際のバッチサイズで、明快なPython実装とベンチマーク比較します。アプリ全体で改善に意味がある場合だけ拡張を残してください。コンパイル済みモジュールごとにCPython ABI、コンパイラー、アーキテクチャ、パッケージングの責任が増えます。
6. 旧ビルドエラーを理解し、旧ビルドはコピーしない
2019年の記録にある症状は有用ですが、対処には現在の文脈が必要です。
| 症状 | 現在の解釈 |
|---|---|
Python.h: No such file or directory | ビルドに使うインタープリターと一致するヘッダーを入れ、python3-config --includesを確認する。システムPythonと別のカスタムインタープリターを混ぜない |
undefined symbol: digitalWrite | シンボルを提供するライブラリへ正しくリンクしていないか、非互換ライブラリをロードしている。現行パッケージメタデータを使い、最終リンクコマンドを確認する |
dynamic module does not define module export function | import名、拡張名、PyInit_<name>シンボルを完全に一致させる |
Bad call flagsまたは旧い呼び出し規約 | 2引数C関数はMETH_VARARGSへ、METH_VARARGS | METH_KEYWORDSは文書化された3引数シグネチャとパーサーへ一致させる |
sudoでしか動かない | インストール手順ではなく権限・所有権の欠陥として扱う |
sudo python setup.py installを使わず、一般的な修正としてlibrary_dirsへ空文字列を入れないでください。現行pipは隔離環境でビルドし、選択した仮想環境へインストールします。外部Cライブラリには、依然として明示的でバージョンが正しいヘッダーとリンカーメタデータが必要です。
7. ネイティブコードがGPIOを所有するならlibgpiodから始める
Raspberry Piのベストプラクティス文書は、現在の移植可能な作業にカーネルGPIOキャラクターデバイス・インターフェースを推奨しています。OSからツールと開発メタデータを入れ、APIバージョンを仮定せず調べます。
sudo apt install --yes gpiod libgpiod-dev pkg-config
gpiodetect
gpioinfo
pkg-config --modversion libgpiod
libgpiod version 1とversion 2ではコマンドとC APIが異なります。対象Piで表示されたメジャーバージョンの上流例に従い、プロジェクトのビルド・配備文書へその版を明記します。1つのメジャー版を暗黙に仮定する汎用C拡張例より、前述のGPIO Zero基準動作のほうが信頼できます。
WiringPi version 3は現在も上流で活発に保守され、監査済み既存Cコードベースでは適切な場合があります。ただし旧ディストリビューションパッケージが有効に戻るわけでも、言語ラッパーの同期が保証されるわけでもありません。維持するなら現行上流リリースを使い、BCM番号を明示的に選び、対象Piモデルで必要機能をすべて試験します。wPi番号とGPIO Zero番号を混ぜないでください。
8. 検証チェックリスト
アクチュエーターを接続する前に、以下を確認します。
- モデル固有の
pinout出力を配線記録と一緒に保存する MockFactoryで純粋なロジックテストを実行する- サービスユーザーとして選択pin factoryとgpiochip権限を確認する
- 電流制限抵抗付きLEDまたは計測器付き低エネルギー治具だけを最初に試す
- タイミングが正しさの一部なら物理タイミングを計測器で測る
- シャットダウンや例外でドライバーとアクチュエーターが安全状態になることを確認する
- Piモデル、OS、カーネル、Python、GPIO Zero、GPIOバックエンド、ネイティブライブラリの版を記録する
- 決定的なモーターパルス生成は適切なハードウェアへ移し、PythonやCのbusy loopへ依存しない
2019年手順から変わった点
| 2019年アーカイブの内容 | 2026年の保守方針 |
|---|---|
| モーター動作の遅さを直接Pythonへ帰属 | 計算、バインディング、スケジューラー、電気、コントローラーを分けて測定 |
| 電気的境界なしにモーター制御を説明 | Hブリッジまたはモーター制御器を必須とし、安全なLED治具から開始 |
gpio readallと異なるwiringPi番号 | 公式pinout、BCM識別子、記録済み物理pin対応を使用 |
| Cの既定経路にwiringPiを選択 | 新規はGPIO Zero/lgpioまたは版が一致するlibgpiodから開始。現行WiringPiは明示した既存コード選択として扱う |
python setup.py build/install | 仮想環境、pyproject.toml、setuptoolsバックエンド、python -m pip install .を使用 |
| 拡張エラーを個別修正として列挙 | 各エラーをインタープリターヘッダー、リンカーメタデータ、モジュール名、呼び出し規約へ対応付け |
公式・上流の参考資料
- Raspberry Piハードウェア文書:GPIOヘッダー、電圧、モーター、権限
- Raspberry Piアプリケーションノート:GPIOの歴史と現行ベストプラクティス
- GPIO Zero文書:インストールと仮想環境
- GPIO Zero文書:pin factory、mock pin、lgpio
- Python文書:CまたはC++によるCPython拡張
- Python文書:CおよびC++拡張のビルド
- Python文書:仮想環境
- Setuptools文書:拡張モジュールのビルド
- libgpiod上流開発版文書
- WiringPiの保守中上流リポジトリとversion 3ラッパー警告
ABIまたはGPIOバックエンドを選ぶ前に、対象Piへ実際にインストールされたマニュアルとバージョンを確認してください。本ガイドは汎用Linuxにハードリアルタイム動作を約束しません。
2019年本文全文(原様保存・実行禁止)
I used gpiozero and RPi.GPIO to control my 3D printer. However, the speed is quite slow. After some nervewracking thinking, I found it the python that cause the slow motion of those motors. Even I delete the time.sleep() line, it still run slowly. This give me no choice that I have to rewrite my control module into c code. After the rewriting, the problem finally solved.
In the c code, I choose wiringPi library to control the GPIO.
The numbering system of the gpiozero and RPi.GPIO with wiringPi is quite different. You should use
gpio readall
to get the number map of your Pi.
Next you should wrap your c code in python.
Create a file test.c
and a file setup.py
Once the coding is finished, you should build and install you code. I suggest you install this in a virtual environment.
sudo apt install python-virtualenv
python setup.py build
python setup.py install
You might meet some problems in compiling as well as importing.
PyArg_ParseTuple()
Compiling
python.h: No such file or directory
Reinstall python3-dev solved this.
Importing
As we used library wiringPi , it should be specified in setup.py. Otherwise, we’ll get error
undefined symbol digitalWrite()
Extension(...,
library_dirs=[''],
libraries=['wiringPi'])
‘ImportError: dynamic module does not define init function’
I named the init function in a wrong name. PyInit_keywdarg should has the same suffix as defined in your keywdargmodule.
static struct PyModuleDef keywdargmodule = {
PyModuleDef_HEAD_INIT,
"keywdarg",
NULL,
-1,
keywdarg_methods
};
PyMODINIT_FUNC
PyInit_keywdarg(void)
{
return PyModule_Create(&keywdargmodule);
}
SystemError: Bad call flags in PyCFunction_Call. METH_OLDARGS is no longer supported! In the myMethods array, the field number of parameters should be defined as
METH_VARARGS | METH_KEYWORDS
static PyMethodDef keywdarg_methods[] = {
/* The cast of the function is necessary since PyCFunction values
*/
{"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
"Print a lovely skit to standard output."},
{NULL, NULL, 0, NULL} / sentinel /
};
- only take two PyObject* parameters, and keywdarg_parrot() takes
- three.
More detail in
[https://docs.python.org/3/extending/extending.html#the-module-s-method-table-and-initialization-function](https://docs.python.org/3/extending/extending.html#the-module-s-method-table-and-initialization-function)
