Fork me on GitHub

Display Library for OLED Chip SSD1306


We purchased a 128x32 pixel OLED screen model SSD1306 from DIYMall via Amazon to display live information on a Raspberry Pi, that we had been using for debugging a product. The requirement for a Raspberry Pi to have an HDMI screen to see live outputs on say a GPIO or SPI pin is too cumbersome, especially when you are not in your lab environment with a TV or HDMI compatible monitor lying around.

With this in mind we purchased the OLED screen that works using I2C pins on the Raspberry Pi. If your task is simple and one-off we recommend using CircuitPython or MicroPython with the Adafruit SSD1306 library. More help on this can be found at https://learn.adafruit.com/monochrome-oled-breakouts/python-usage-2.

However, we wanted to write our own library for writing to the SSD1306 OLED chip directly in C, as eventually this would be used in our internal product tools and help with longer battery life. We tested the Adafruit library and it used more CPU than expected.

INTRODUCTION

We are releasing this library on Github as open source with the MIT license. We want anyone who uses the SSD1306 on a Raspberry Pi, and who wants a library that uses low power to use this library. This is also a library for those developers who love coding in C, such as us.

The library is designed to be very easy to use. It separates out the graphics required to display information from the communication with the chip which is done using I2C. Eventually, we may add SPI to the communication options if needed.

The library allows the user to create one or more framebuffers and generate graphics for display on those framebuffers and then use the I2C functions to send the framebuffers to the screen for display.

Since we have a lot of Beaglebone Black boards lying around we will also be supporting those boards, however a blog post on that will be coming soon. We also plan to support multi-purpose debugging boards such as TUMPA Lite with this library, so that when you are reverse engineering a product using TUMPA Lite, or similar board, using a Linux machine you can display live data on that board using its I2C ports and the SSD1306 screen.

COMPILE

To compile the library we clone the repo from Github and run the following commands:

## install required pre-requisites
$ sudo apt-get -y install build-essential libfreetype6-dev  i2c-tools

## install optional libraries
$ sudo apt-get -y install libev-dev libuv1-dev

## run our pre-configure scripts for your system
$ ./autogen.sh

## run the configure script with default options
$ ./configure

## run the configure script with libev and/or libuv
$ ./configure --with-libev --with-libuv

## if you want to install somewhere else other than /usr/local
$ ./configure --prefix=/path/to/local/install --with-libev --with-libuv

## run make
$ make
$ make check

## install in /usr/local or your local path
$ make install

I2C TOOLS

In the above step we installed i2c-tools as a pre-requisite. That gives us i2cdetect which is a useful tool to determine the list of I2C devices attached to the system, such as a Raspberry Pi, and also determine their address.

Below is what our Raspberry Pi 3B+ showed with the OLED SSD1306 device connected via the I2C pins.

$ i2cdetect -l
i2c-1   i2c             bcm2835 I2C adapter                     I2C adapter

$ ls /dev/i2c-1
/dev/i2c-1

Above the I2C device is /dev/i2c-1, which is the first device in the list. This is the filename we will be using to access the device in our code example.

$ i2cdetect  -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

The above command output shows us the address that the I2C device, our OLED screen, uses for every I/O request. The 1 in the command is for the first device in the list that was generated in the previous i2cdetect command shown earlier.

HEADER FILES

As of this post, there are two header files ssd1306_i2c.h and ssd1306_graphics.h that are important for the developer. The developer can include ssd1306_i2c.h to include both the files.

The ssd1306_graphics.h file has the functions for the developer to draw to a framebuffer in memory. The ssd1306_i2c.h has functions required by the developer to write to the OLED screen via the I2C bus.

EXAMPLE

We have provided examples in the examples directory. Let’s look at the test_ssd1306_i2c.c.

NOTE: this code is current as of commit a7e882980245561ca58621f684101ecaa2b56690 and may be updated in the future.

Let’s look at the main() function below.

#include <ssd1306_i2c.h>

int main ()
{
    fprintf(stderr, "DEBUG: Using library version: %s\n", ssd1306_i2c_version());
    const char *filename = "/dev/i2c-1";
    ssd1306_i2c_t *oled = ssd1306_i2c_open(filename, 0x3c, 128, 32, NULL);
    if (!oled) {
        return -1;
    }
    ssd1306_i2c_display_initialize(oled);
    sleep(3);
    ssd1306_framebuffer_t *fbp = ssd1306_framebuffer_create(oled->width, oled->height, oled->err);

    ssd1306_framebuffer_draw_bricks(fbp);
    ssd1306_framebuffer_hexdump(fbp);
    ssd1306_framebuffer_bitdump(fbp, 0, 0, true);
    ssd1306_i2c_display_update(oled, fbp);
    sleep(3);;
    ssd1306_i2c_run_cmd(oled, SSD1306_I2C_CMD_DISP_INVERTED, 0, 0);
    sleep(3);
    ssd1306_i2c_display_clear(oled);
    ssd1306_i2c_run_cmd(oled, SSD1306_I2C_CMD_DISP_NORMAL, 0, 0);
    ssd1306_framebuffer_clear(fbp);
    ssd1306_framebuffer_draw_pixel(fbp, 0, 0, false);
    ssd1306_framebuffer_draw_pixel(fbp, fbp->width - 1, 0, false);
    ssd1306_framebuffer_draw_pixel(fbp, 0, fbp->height - 1, false);
    ssd1306_framebuffer_draw_pixel(fbp, fbp->width - 1, fbp->height - 1, false);
    ssd1306_framebuffer_draw_pixel(fbp, 9, 10, false);
    ssd1306_framebuffer_bitdump(fbp, 0, 0, true);
    ssd1306_i2c_display_update(oled, fbp);
    sleep(3);
    ssd1306_framebuffer_clear(fbp);
    ssd1306_framebuffer_draw_pixel(fbp, 0, 0, true);
    ssd1306_framebuffer_draw_pixel(fbp, fbp->width - 1, 0, true);
    ssd1306_framebuffer_draw_pixel(fbp, 0, fbp->height - 1, true);
    ssd1306_framebuffer_draw_pixel(fbp, fbp->width - 1, fbp->height - 1, true);
    ssd1306_framebuffer_draw_pixel(fbp, 9, 10, true);
    ssd1306_framebuffer_bitdump(fbp, 0, 0, true);
    ssd1306_i2c_display_update(oled, fbp);
    sleep(3);
    ssd1306_i2c_run_cmd(oled, SSD1306_I2C_CMD_POWER_OFF, 0, 0);
    ssd1306_framebuffer_destroy(fbp);
    fbp = NULL;
    ssd1306_i2c_close(oled);
    oled = NULL;
    return 0;
}

We first open the device using ssd1306_i2c_open(). The device filename and the address are the same as that detected by the i2cdetect tool as shown in the section above.

The device address can be 0x3c or 0x3d and depending on i2cdetect -y 1 you must select the correct value. Based on the datasheet of the SSD1306, it is highly likely to be 0x3c. The height and width of the OLED screen are provided to the function since it initializes a copy of the memory needed to store the graphical display RAM (GDRAM) of the chip, so we could update the GDRAM all at once.

The OLED screen comes in 3 different sizes: 128x64, 128x32 and 96x16. In our example, we are using one that has the 128x32 size. This denotes the number of pixels available to the user on the OLED screen.

Next we initialize the display using the ssd1306_i2c_display_initialize() function. This function internally executes commands that turn on the display and make it ready for use.

We now create a framebuffer object and using some framebuffer functions found in ssd1306_graphics.h draw bricks onto the framebuffer object. Then we use the ssd1306_i2c_display_update() function and pass the framebuffer object to the display and make the screen display bricks.

Some extra helper functions like ssd1306_i2c_display_clear() are provided to clear the screen. The user can also debug the framebuffer object by dumping it to screen using the ssd1306_i2c_framebuffer_bitdump() function.

DEMONSTRATION

Here is a short video of the demo at https://youtu.be/PSZ2eYGcL0I.