Raspberry Pi、GPIO、Python、C/C++ 拡張、そして wiringPi の連携

私は gpiozeroRPi.GPIO を使って 3D プリンターを制御していました。しかし、速度がかなり遅い状態でした。神経をすり減らすようなデバッグの末、モーターの動作が遅い原因は Python にあることがわかりました。time.sleep() の行を削除しても、まだ動きは遅いままでした。そこで仕方なく、制御モジュールを C で書き直しました。書き直した後、ようやく問題は解決しました。

C コードでは、GPIO ピンの制御に wiringPi ライブラリを選びました。

gpiozeroRPi.GPIOwiringPi が使う GPIO の番号体系はかなり異なります。自分の Raspberry Pi のピン番号対応表を確認するには、次のコマンドを使います。

gpio readall

次に、C コードを Python 拡張モジュールとしてラップします。

test.c という名前のファイルを作成し、そこで Python モジュールの関数を定義します。重要なのは、エクスポートされる初期化関数の名前が、setup.py で使うモジュール名と一致している必要があるという点です。

拡張をビルドするために、setup.py という名前のファイルを作成します。この拡張は wiringPi にリンクするため、拡張設定の中でそれを指定します。

from distutils.core import setup, Extension

module = Extension(
    'keywdarg',
    sources=['test.c'],
    library_dirs=[''],
    libraries=['wiringPi'],
)

setup(
    name='keywdarg',
    version='1.0',
    description='Python C extension using wiringPi',
    ext_modules=[module],
)

コードが完成したら、ビルドしてインストールします。仮想環境の中にインストールすることをおすすめします。

sudo apt install python-virtualenv
python setup.py build
python setup.py install

コンパイル時やモジュールのインポート時に、いくつか問題に遭遇するかもしれません。

Python.h: No such file or directory

これはコンパイルエラーです。私の場合は、python3-dev を再インストールすることで解決しました。

sudo apt install python3-dev

undefined symbol: digitalWrite

この拡張は wiringPi ライブラリを使うため、setup.py で指定しておく必要があります。指定しない場合、Python はコンパイル済みモジュールをインポートできても、digitalWrite() を解決しようとした時点で失敗する可能性があります。

Extension(
    ...,
    library_dirs=[''],
    libraries=['wiringPi'],
)

ImportError: dynamic module does not define init function

私は初期化関数の名前を間違えていました。Python 3 では、PyInit_keywdarg の接尾辞が、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!

PyMethodDef 配列では、メソッドのフラグを次のように定義する必要があります。

METH_VARARGS | METH_KEYWORDS

例は次のとおりです。

static PyMethodDef keywdarg_methods[] = {
    /* The cast of the function is necessary since PyCFunction values
     * only take two PyObject* parameters, and keywdarg_parrot() takes
     * three.
     */
    {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* sentinel */
};

詳しくは Python のドキュメントに記載されています。

https://docs.python.org/3/extending/extending.html#the-module-s-method-table-and-initialization-function

Leave a Reply