Raspberry Pi, GPIO, Python, C/C++ Extension and wiringPi Connected

I used gpiozero and RPi.GPIO to control my 3D printer. However, the speed was quite slow. After some nerve-wracking debugging, I found that Python was causing the slow motion of the motors. Even after deleting the time.sleep() line, it still ran slowly. This left me with no choice but to rewrite my control module in C. After the rewrite, the problem was finally solved.

In the C code, I chose the wiringPi library to control the GPIO pins.

The GPIO numbering systems used by gpiozero, RPi.GPIO, and wiringPi are quite different. Use this command to get the pin-number map for your Raspberry Pi:

gpio readall

Then wrap the C code as a Python extension module.

Create a file named test.c and define the Python module functions there. The important part is that the exported initialization function must match the module name used in setup.py.

Create a file named setup.py to build the extension. Since the extension links against wiringPi, specify it in the extension configuration:

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],
)

Once the code is finished, build and install it. I suggest installing it inside a virtual environment.

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

You might meet some problems while compiling or importing the module.

Python.h: No such file or directory

This is a compiling error. Reinstalling python3-dev solved it for me.

sudo apt install python3-dev

undefined symbol: digitalWrite

Because the extension uses the wiringPi library, it must be specified in setup.py. Otherwise, Python may import the compiled module but fail when it tries to resolve digitalWrite().

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

ImportError: dynamic module does not define init function

I had named the initialization function incorrectly. For Python 3, PyInit_keywdarg must have the same suffix as the module name defined in 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 PyMethodDef array, the method flags should be defined as:

METH_VARARGS | METH_KEYWORDS

For example:

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 */
};

More detail is available in the Python documentation:

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

Leave a Reply