GfxLCD and ILI9325 C driver – part 1

We know that we can get a decent performance with C and ILI based display, so let’s make the next step and add ili9325 and the ili9486 driver written in C to our GfxLCD package.

There is no  repository for now – sorry 🙂

Prequel 🙂


We have an interface in Python that requires some functions. What we will do is to write a driver in C that combines chip and driver from Python module and we will use interface as a guide. After that, we will write a wrapper for Python.

What about naming? This driver will be called cili9325 🙂

Our test C code consists of pure functions but we need a class. This class would take pin list as arguments. Next, we will add rotation.

After that, we can write all functions for drawing and setters/getters for colour, font and so.

Drawing image and text is something hard and we will look into it as the last task.

Next, we have two ILI displays (9325 and 9486) and two touch panels. We will try and create files in such way that common functions will be in one file, each module has its own file. Playing with compiler and files we should manage somehow 😀

Basic setup

Sources for C files are under src/ili9325 directory. I copied files from the previous attempt and renamed to cili.
In we have the code that should compile extension and put it under gfxlcd/driver/ili9325:

ili9325_module = Extension(
    ["src/ili9325/_cili9325.c", "src/ili9325/cili9325.c"],

and in setup() put somewhere:


The last step is to create class in drivers/ili9325 with dummy functions.

Class in C

We can start. First, let’s create a class and handle arguments. What arguments? Pin number and screen size.
In C we have no classes but we can use typedef struct. With help of this, I gained some knowledge about Python object in C.

What we need is a structure that will describe Python’s class. We can put it in cili9325.h file so we can use it anywhere:

typedef struct {
    int width;
    int height;
    int CS;
    int RS;
    int W;
    int DB8;
    int DB9;
    int DB10;
    int DB11;
    int DB12;
    int DB13;
    int DB14;
    int DB15;
    int RST;
    int color;
    int background_color;
} ILIObject;

We define variables for pins, size and colours for front and back.

Now we have to set how our Python class initializes, how it is destroyed and what kind of methods it has. All in C.

To set our function as one belonging to class we need to change PyObject *self to ILIObject *self:

static PyObject *ili9325_init_display(ILIObject *self);
static PyObject *ili9325_fill_rect(ILIObject *self, PyObject *args);
static PyObject *ili9325_set_color(ILIObject *self, PyObject *args);
static PyObject *ili9325_set_backgroundcolor(ILIObject *self, PyObject *args);

When we create an instance of a class, Python calls new and __init__ functions. In new function, we allocate memory for our structure and we can set default values (that needs to be different from null):

static PyObject *cili9325_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    ILIObject *self;
    self = (ILIObject *)type->tp_alloc(type, 0);

    self->color = get_color(255, 255, 255);
    self->background_color = 0;

    return (PyObject *)self;

Now we need __init__ and destroy functions:

static int cili9325_init(ILIObject *self, PyObject *args) {
    if (!PyArg_ParseTuple(args, "IIIIIIIIIIIIII", &self->width, &self->height,
        &self->CS, &self->RS, &self->W, &self->RST,
        &self->DB8, &self->DB9, &self->DB10, &self->DB11,
        &self->DB12, &self->DB13, &self->DB14, &self->DB15)) {
        return -1;

    return 0;

static void cili9325_dealloc(ILIObject* self) {

Our __init__ takes arguments. Those are size and pins.

Ok. We have few bricks but they are not connected. To join them we need to declare a special object that tells Python and C what is what:

static PyTypeObject ILIType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "cili9325.cili9325",       /* tp_name */
    sizeof(ILIObject),          /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)cili9325_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    module_docstring,          /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    module_methods,            /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)cili9325_init,   /* tp_init */
    0,                         /* tp_alloc */
    cili9325_new,              /* tp_new */

As you can see we can create lots of functions for Python attributes 🙂 And this is good but for our sake, we need only a few.

Ok finally we can change PyInit_cili9325 function:

PyMODINIT_FUNC PyInit_cili9325(void) {
    PyObject *module;

    if (PyType_Ready(&ILIType) < 0)
        return NULL;

    static struct PyModuleDef moduledef = {
    module = PyModule_Create(&moduledef);
    if (!module) return NULL;

    PyModule_AddObject(module, "cili9325", (PyObject *)&ILIType);

    return module;

It has only one more operation than old one, we tell that ILIType must be added to our module.

We are almost done. All our proxy functions and main C functions require additional parameter at first position: ILIObject *self

static PyObject *ili9325_init_display(ILIObject *self) {
    return Py_None;

void setup_pins(ILIObject *self) {
    pinMode (self->RS, OUTPUT);
    pinMode (self->W, OUTPUT);
    pinMode (self->DB8, OUTPUT);

Maybe it is not very simple but after doing it once we can get the idea, how to create class C for Python 🙂

Python script that uses the new class:

import RPi.GPIO 
import cili9325
RPi.GPIO.setup(6, RPi.GPIO.OUT)
RPi.GPIO.output(6, 1)

lcd = cili9325.cili9325(320, 240, 18, 27, 17, 25, 22, 23, 24, 5, 12, 16, 20, 21)

It works!


Let’s take next step and separate logic, extract common function, create interfaces, create files for ili9325, il9486, common ili functions and I/O functions. And one init funtion with both GPIO and SPI interface.
To detect GPIO or SPI we will count parameters and set proper class attributes and it should be possible to have one init for both ILI displays. We will see.

In ili.h we will have an “interface” that is same as in Python. It is responsible for function’s definitions for all kind of drawing, chip initialization and colour conversions.

To define functions for I/O we have an interface.h and implementation in gpio.c and later in spi.c.

Finally, to merge all files into one package we need to add in

ext_modules=[Extension("cili9325", ["_cili9325.c", "ili.c", "cili9325.c", "gpio.c"],  extra_link_args=['-lwiringPi'])],

Ok. Now we can work on naming, all long ili9325_* renamed to ili_*. And now call from Python looks like this:

import cili9325
lcd = cili9325.ili(320, 240, 18, 27, 17, 25, 22, 23, 24, 5, 12, 16, 20, 21)

Why? Because I want all functions in ILI9486/9325 to have the same name. And use pseudo-interfaces as much as possible.

During initialization, we can detect SPI/GPIO interface by the number of parameters. If we have 14 we are using GPIO and 7 for SPI. Another number generates an error.

static int ili_init(ILIObject *self, PyObject *args) {
    Py_ssize_t TupleSize = PyTuple_Size(args);
    if (TupleSize == 14) {
        if (!PyArg_ParseTuple(args, "IIIIIIIIIIIIII", &self->width, &self->height,
            &self->CS, &self->RS, &self->W, &self->RST,
            &self->DB8, &self->DB9, &self->DB10, &self->DB11,
            &self->DB12, &self->DB13, &self->DB14, &self->DB15)) {
            return -1;
    } else if (TupleSize == 7) {
         if (!PyArg_ParseTuple(args, "IIIIIII", &self->width, &self->height,
            &self->SPI, &self->SPEED,
            &self->CS, &self->RST, &self->RS)) {
            return -1;
    } else {
                    "Wrong number of parameters: \n width, height, PIN_CS, PIN_RS,PIN_W,PIN_RST,PIN_DB8,PIN_DB9,PIN_DB10,PIN_DB11,PIN_DB12,PIN_DB13,PIN_DB14,PIN_DB15 \n width, height, SPI, SPEED, CS, RST, RS");
        return -1;

    return 0;

Our constructor can handle both SPI and GPIO but first, we will focus on GPIO. Later when ILI9486 is on the table we will do an SPI communication.

But this to work require one more change, we need to add variables to our structure:

int SPI;
int SPEED;

Quick summary what is where:
– ili.c – functions that are same for 9325/9486, mostly drawing
– ili.h – definction for class and drawing/communication functions
– gpio.h / gpio.c – functions for GPIO communication
– cili9325.c – function only for specific display type like initialization or a select area
– _cili9325.c – wrapper for python functions for ili9325


We are slowly moving functionality to C. Because we will support only ILI 9325 and 9486 we can focus only on them and not NJU and we can compress the code.

We have one constructor for GPIO and SPI, detection is based on a number of arguments. All common functions are in one place. Functions responsible for communication with hardware are separated into gpio.c and spi.c.
And we will have two separate ILI modules.

Our plan changed a little but we are still moving forward 🙂 I decided to keep code separated from GfxLCD at the moment because it is easier to focus on problems at hand.



Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s