Project

General

Profile

Using an I2C OLED display

Introduction

I2C OLED displays are a family of easily available modules generally offering a monochrome array of 128x32 or 128x64 pixels.
This family of displays is defined by the controller chip providing the digital interface: the SSD1306.
A library is provided as part of the Dooba SDK to use these devices: ssd1306.

This page will present some usage examples for such OLED displays.

Wiring

Most devices only require connecting the I2C bus.
A few devices might expose a RESET line.

Note: It is usually a good idea to add 10k pull-up resistors on the I2C bus (SCL & SDA lines).

Using the library

Before we can actually access the library from our code, we need to add it as a dependency.
Unless this is already the case, we're also going to be initializing the I2C bus in our application. This means we need to include the i2c library in our dependency list.
This is done in the project definition file - dfe.conf.

# Firmware Element Configuration
# ...
deps:
  - ssd1306
  - i2c

Initialization

Before we try to display anything, we need to initialize the OLED.
Also, if this is not already done, we need to initialize the I2C bus.

#include <i2c/i2c.h>
#include <ssd1306/ssd1306.h>

// Main Entry Point
void main()
{
    // Initialize I2C bus
    i2c_init();

    // Initialize OLED display
    ssd1306_init();

    // ...

    // Never return from main
    for(;;);
}

Drawing

Everything is drawn to a framebuffer, which can be written to the display at any time.
Like many graphics libraries, the main use case is usually to integrate the drawing / refreshing in a main loop.

The code example below assumes the eloop library has been included (to provide a main loop), and performs the following at each iteration:
  • Clears the framebuffer
  • Draws some text
  • Writes the framebuffer to the display
#include <i2c/i2c.h>
#include <ssd1306/ssd1306.h>
#include <ssd1306/fb.h>
#include <ssd1306/txt.h>

// Called once at initialization
void init()
{
    // Initialize I2C Bus and Display
    i2c_init();
    ssd1306_init();
}

// Called repeatedly until power-off
void loop()
{
    // Clear Framebuffer
    ssd1306_fb_clear();

    // Draw some text
    ssd1306_txt((uint8_t *)"", 0, 0);

    // Write Framebuffer to display
    ssd1306_draw();
}

The sections below will present the methods used in the example above.

Clearing the framebuffer

Clearing the framebuffer can be done with the ssd1306_fb_clear method (from ssd1306/fb.h):

void ssd1306_fb_clear()

// Clear the framebuffer
ssd1306_fb_clear();

Writing the framebuffer to the display

This is accomplished using the ssd1306_draw method (from ssd1306/ssd1306.h):

void ssd1306_draw()

// Draw the framebuffer to the display
ssd1306_draw();

Fast drawing

The library also provides a mechanism for committing the framebuffer to the display much faster.
This however comes at a cost. Due to the double-buffering used to speed up the drawing process, the framebuffer size is doubled.

To use it, include the ssd1306/fdraw.h.

First, the fdraw system needs to be initialized:

void ssd1306_fdraw_init()

// Initialize Fast-Drawing
ssd1306_fdraw_init();

Then, instead of calling ssd1306_draw() to commit the framebuffer to the display, call the ssd1306_fdraw() method:

void ssd1306_fdraw()

// Draw Fast
ssd1306_fdraw();

Drawing a single pixel

Drawing a single pixel can be done using the ssd1306_fb_plot method (from ssd1306/fb.h):

void ssd1306_fb_plot(uint8_t x, uint8_t y, uint8_t v)
Arguments:

  • x -> X Position in pixels (from the left)
  • y -> Y Position in pixels (from the top)
  • v -> Pixel value (0 = dark / 1 = light)

// Draw a square
ssd1306_fb_plot(32, 8, 1);
ssd1306_fb_plot(33, 8, 1);
ssd1306_fb_plot(34, 8, 1);
ssd1306_fb_plot(32, 10, 1);
ssd1306_fb_plot(33, 10, 1);
ssd1306_fb_plot(34, 10, 1);
ssd1306_fb_plot(32, 9, 1);
ssd1306_fb_plot(34, 9, 1);

Note: x and y arguments MUST be within the framebuffer dimensions, or unexpected things may happen.
This rule only applies to single-pixel plotting (using ssd1306_fb_plot), the other methods presented below can be given negative or off-screen coordinates as arguments.

Drawing a line

Drawing a line can be done using the ssd1306_gfx_line method (from ssd1306/gfx.h):

void ssd1306_gfx_line(int from_x, int from_y, int to_x, int to_y, uint8_t v)
Arguments:

  • from_x -> Start X Position in pixels (from the left)
  • from_y -> Start Y Position in pixels (from the top)
  • to_x -> End X Position in pixels (from the left)
  • to_y -> End Y Position in pixels (from the top)
  • v -> Pixel value (0 = dark / 1 = light)

Drawing a rectangle

Drawing a rectangle can be done using the ssd1306_gfx_rect method (from ssd1306/gfx.h):

void ssd1306_gfx_rect(int x, int y, int w, int h, uint8_t v)
Arguments:

  • x -> Start X Position in pixels (from the left)
  • y -> Start Y Position in pixels (from the top)
  • w -> Width in pixels (from the left)
  • h -> Height in pixels (from the top)
  • v -> Pixel value (0 = dark / 1 = light)

Drawing a polygon

Drawing a polygon can be done using the ssd1306_gfx_ngon method (from ssd1306/gfx.h):

void ssd1306_gfx_ngon(uint8_t v, uint8_t n, ...)
Arguments:

  • v -> Pixel value (0 = dark / 1 = light)
  • n -> Number of lines to draw (determines number of additional variable arguments).

Coordinates should be passed as x0, y0, x1, y1, x2, y2, ...
Lines will be drawn:
  • x0;y0 -> x1;y1
  • x1;y1 -> x2;y2

Drawing a circle

Drawing a circle can be done using the ssd1306_gfx_circle method (from ssd1306/gfx.h):

void ssd1306_gfx_circle(int x, int y, uint8_t r, uint8_t v)
Arguments:

  • x -> Center X Position in pixels (from the left)
  • y -> Center Y Position in pixels (from the top)
  • r -> Radius in pixels
  • v -> Pixel value (0 = dark / 1 = light)

Drawing text

ASCII Text can be drawn to the framebuffer using the ssd1306_txt methods (from ssd1306/txt.h).
The ssd1306_txt_n method is a safe alternative to ssd1306_txt, requiring an additional argument: the length of the string.
The ssd1306_txt_f method allows printing a format string similar to the classic "printf".

void ssd1306_txt(void *s, int x, int y)
Arguments:

  • s -> Pointer to NULL-terminated string
  • x -> X Position in pixels (from the left, can be negative)
  • y -> Y Position in pixels (from the top, can be negative)

void ssd1306_txt_n(void *s, uint8_t l, int x, int y)
Arguments:

  • s -> Pointer to string
  • l -> Length of string s
  • x -> X Position in pixels (from the left, can be negative)
  • y -> Y Position in pixels (from the top, can be negative)

void ssd1306_txt_f(int x, int y, void *fmt, ...)
Arguments:

  • x -> X Position in pixels (from the left, can be negative)
  • y -> Y Position in pixels (from the top, can be negative)
  • fmt -> Format String (refer to Format Strings for details)

// Draw some text
ssd1306_txt("Hello World!", 0, 0);
ssd1306_txt_f(0, 8, "Number: %i", 42);
ssd1306_txt_f(0, 16, "Hex: %h", 42);
ssd1306_txt_f(0, 24, "Binary: %b", 42);

Drawing progress bars

Simple progress bars can be drawn using the ssd1306_pbar method (from ssd1306/pbar.h):

void ssd1306_pbar(uint8_t pct, uint8_t w, int x, int y)
Arguments:

  • pct -> Progress percentage (0 - 100)
  • w -> Total width in pixels
  • x -> X Position in pixels (from the left, can be negative)
  • y -> Y Position in pixels (from the top, can be negative)

// Draw a half-full progress bar 32 pixels wide
ssd1306_pbar(50, 32, 10, 10);

Drawing images

Please refer to (the bottom of this page) for instructions on how to export images in the proper format.
Place your images along with your source code, and change their extensions to .WIDTH.HEIGHT.ssd1306_img (replacing WIDTH and HEIGHT with the dimensions of your image in pixels, as described in SSD1306 Images).

These images will then be packaged into your application's binary file and stored in the flash of your microcontroller. This means they'll be available as XXXXX_data (actual data) and XXXXX_DATA_SIZE (byte size) from the code, but you'll need to load the image data into RAM to use it.

Then, images can be drawn using the ssd1306_img method (from ssd1306/img.h):

void ssd1306_img(uint8_t *img, int x, int y)
Arguments:

  • img -> Image binary data
  • x -> X Position in pixels (from the left, can be negative)
  • y -> Y Position in pixels (from the top, can be negative)

The example below assumes a 32x24 image has been exported from gimp into the source directory as foobar.32.24.ssd1306_img:

// RAM buffer to hold image data
uint8_t foobar_img[FOOBAR_DATA_SIZE];

// Main
void main()
{
    // ...

    // Load image into RAM
    memcpy_P(foobar_img, &foobar_data, FOOBAR_DATA_SIZE);

    // Draw image
    ssd1306_img(foobar_img, 20, 4);

    // ...
    for(;;);
}

Drawing animations

Animations can be created just like images, multiplying the height of the final image by the number of frames, and stacking frames vertically.
The file name should have the same structure as for images, with WIDTH and HEIGHT being those of an individual frame (and not the final image containing all frames).

Animations don't need to be loaded before use, each frame will be loaded into a buffer (the size of the framebuffer) automatically when drawn with ssd1306_anim (from ssd1306/anim.h).

void ssd1306_anim(const uint8_t *anim, uint8_t frame, int x, int y)
Arguments:

  • anim -> Pointer to animation data in flash storage
  • frame -> Frame index
  • x -> X Position in pixels (from the left, can be negative)
  • y -> Y Position in pixels (from the top, can be negative)

The example below assumes a 16x16 animation with 10 frames has been exported from gimp into the source directory as foobar.16.16.ssd1306_img:

// Main
void main()
{
    // ...

    // Draw frame 0 of animation
    ssd1306_anim(foobar_data, 0, 20, 4);

    // ...
    for(;;);
}

Text terminal

The display can also be used as a very basic text terminal using the methods provided in ssd1306/term.h:

void ssd1306_term_init()

void ssd1306_term_printf(void *fmt, ...)
Arguments:

// Main
void main()
{
    // Initialize I2C Bus and OLED display
    i2c_init();
    ssd1306_init();

    // Initialize Text Terminal
    ssd1306_term_init();

    // Print a few things
    ssd1306_term_printf("Hello world!\n");
    ssd1306_term_printf("Hex: %h\n", 15);
    ssd1306_term_printf("Bin: %b\n", 15);
    ssd1306_term_printf("Dec: %i\n", 15);
}

Menus

A basic menu system is provided through the ssd1306/menu.h header.
To be used, a struct ssd1306_menu object and item buffer (struct ssd1306_menu_item []) are required.

Two different visual styles are available:
  • SSD1306_MENU_STYLE_LINE8 -> Condensed text-only menu
  • SSD1306_MENU_STYLE_LINE16 -> Less dense menu allowing use of icons

Building a menu is done by first initializing the struct ssd1306_menu object, then adding as many items as desired.
Along with the menu object, an item buffer needs to be provided.

void ssd1306_menu_init(struct ssd1306_menu *menu, uint8_t style, struct ssd1306_menu_item *items)
Arguments:

  • menu -> Pointer to menu object
  • style -> Visual style (see above)
  • items -> Pointer to item buffer

void ssd1306_menu_add_item(struct ssd1306_menu *menu, char *name, uint8_t user, uint8_t *icon)
void ssd1306_menu_add_item_n(struct ssd1306_menu *menu, char *name, uint8_t name_len, uint8_t user, uint8_t *icon)
void ssd1306_menu_add_item_f(struct ssd1306_menu *menu, uint8_t user, uint8_t *icon, char *fmt, ...)
Arguments:

  • menu -> Pointer to menu object
  • name -> Item name
  • name_len -> Item name length
  • user -> User storage (can be used to store a key related to the item)
  • icon -> Icon for item (should be 16x16)
  • fmt -> Format String for item name (refer to Format Strings for details)

// Menu & Item Buffer (50 entries - about 1000 bytes)
#define foo_menu_bufsize        50
struct ssd1306_menu foo_menu;
struct ssd1306_menu_item foo_menu_items[foo_menu_bufsize];

// Main
void main()
{
    // ...

    // Initialize Menu - Condensed
    ssd1306_menu_init(&foo_menu, SSD1306_MENU_STYLE_LINE8, foo_menu_items);

    // Add some items (storing their index as user values)
    ssd1306_menu_add_item(&foo_menu, "First item", 0, 0);
    ssd1306_menu_add_item_n(&foo_menu, "item2", 5, 1, 0);
    ssd1306_menu_add_item_f(&foo_menu, 2, 0, "Item: %i", 3);

    // ...
    for(;;);
}

Once the menu is built, it can be drawn to the display:

void ssd1306_menu_draw(struct ssd1306_menu *menu)
Arguments:

  • menu -> Pointer to menu object

// Menu & Item Buffer (50 entries - about 1000 bytes)
#define foo_menu_bufsize        50
struct ssd1306_menu foo_menu;
struct ssd1306_menu_item foo_menu_items[foo_menu_bufsize];

// Main
void main()
{
    // ...

    // Draw previously built menu
    ssd1306_menu_draw(&foo_menu);

    // ...
    for(;;);
}

The application can then browse through the menu items (for example using buttons) using the following two methods:

void ssd1306_menu_up(struct ssd1306_menu *menu)
Arguments:

  • menu -> Pointer to menu object

void ssd1306_menu_down(struct ssd1306_menu *menu)
Arguments:

  • menu -> Pointer to menu object

// Menu & Item Buffer (50 entries - about 1000 bytes)
#define foo_menu_bufsize        50
struct ssd1306_menu foo_menu;
struct ssd1306_menu_item foo_menu_items[foo_menu_bufsize];

// Main
void main()
{
    // ...

    // Go up
    ssd1306_menu_up(&foo_menu);

    // ...

    // Go down
    ssd1306_menu_down(&foo_menu);

    // ...
    for(;;);
}

The selected item can be accessed any time:

struct ssd1306_menu_item *ssd1306_menu_selected(struct ssd1306_menu *menu)
Arguments:

  • menu -> Pointer to menu object

uint8_t ssd1306_menu_selected_user(struct ssd1306_menu *menu)
Arguments:

  • menu -> Pointer to menu object

// Menu & Item Buffer (50 entries - about 1000 bytes)
#define foo_menu_bufsize        50
struct ssd1306_menu foo_menu;
struct ssd1306_menu_item foo_menu_items[foo_menu_bufsize];

// Main
void main()
{
    struct ssd1306_menu_item *selected;
    uint8_t u;
    // ...

    // Get selected item
    selected = ssd1306_menu_selected(&foo_menu);

    // ...

    // This is equivalent to selected->user
    u = ssd1306_menu_selected_user(&foo_menu);

    // ...
    for(;;);
}

Text Editor

A minimal text editor is also provided through ssd1306/edit.h.
Three modes are currently supported:

  • SSD1306_EDIT_MODE_TEXT -> Normal text edit
  • SSD1306_EDIT_MODE_PHONE -> Only accept numbers, * (star), # (pound) and + (plus)
  • SSD1306_EDIT_MODE_RDONLY -> Read-only mode - useful for simply displaying multi-line text

Similar to what was done for menus (presented above), a struct ssd1306_edit object needs to be provided.
The buffer to hold the text also needs to be provided upon initialization.

void ssd1306_edit_init(struct ssd1306_edit *edit, uint8_t mode, char *title, uint8_t *icon, char *value, uint16_t value_size, uint16_t value_pos, uint16_t value_len)
void ssd1306_edit_init_n(struct ssd1306_edit *edit, uint8_t mode, char *title, uint8_t title_len, uint8_t *icon, char *value, uint16_t value_size, uint16_t value_pos, uint16_t value_len)
void ssd1306_edit_init_f(struct ssd1306_edit *edit, uint8_t mode, uint8_t *icon, char *value, uint16_t value_size, uint16_t value_pos, uint16_t value_len, char *fmt, ...)
Arguments:

  • edit -> Pointer to edit object
  • mode -> Edit mode (see above)
  • title -> Edit title
  • title_len -> Edit title length
  • icon -> Icon for edit (should be 16x16)
  • value -> Text data buffer
  • value_size -> Size of text data buffer
  • value_pos -> Initial position of cursor within text data
  • value_len -> Initial length of text data (used to pre-fill the edit)
  • fmt -> Format String for title (refer to Format Strings for details)

// Edit & Text Buffer
#define foo_edit_text_size        200
struct ssd1306_edit foo_edit;
uint8_t foo_edit_text[foo_edit_text_size];

// Main
void main()
{
    // ...

    // Initialize Edit - Normal mode
    ssd1306_edit_init(&foo_edit, SSD1306_EDIT_MODE_TEXT, "Text entry:", 0, foo_edit_text, foo_edit_text_size, 0, 0);

    // ...
    for(;;);
}

The editor's cursor can be controlled with the following methods:

void ssd1306_edit_up(struct ssd1306_edit *edit)
void ssd1306_edit_down(struct ssd1306_edit *edit)
void ssd1306_edit_left(struct ssd1306_edit *edit)
void ssd1306_edit_right(struct ssd1306_edit *edit)
Arguments:

  • edit -> Pointer to edit object

Text can be written at the cursor position using the following method:

void ssd1306_edit_write(struct ssd1306_edit *edit, char c)
Arguments:

  • edit -> Pointer to edit object
  • c -> Character to write

Also, a numeric keypad entry method is provided:

void ssd1306_edit_hit_num(struct ssd1306_edit *edit, uint8_t num)
void ssd1306_edit_confirm_num_hit(struct ssd1306_edit *edit)
Arguments:

  • edit -> Pointer to edit object
  • num -> Key number (numbers = 0x00 - 0x09, * (star) = 0x0a, # (pound) = 0x0b)

Backspace / delete are also provided:

void ssd1306_edit_bksp(struct ssd1306_edit *edit)
void ssd1306_edit_del(struct ssd1306_edit *edit)
Arguments:

  • edit -> Pointer to edit object

Finally, the editor can be drawn using the following method:

void ssd1306_edit_draw(struct ssd1306_edit *edit)
Arguments:

  • edit -> Pointer to edit object

Exporting from GIMP

To export images in a format that can be used by dbuild, a simple option is to use GIMP, which is a free image manipulation tool.

Work in RGB image mode.
Draw the pixels you want active as black (#000000), leave the rest as white.
When ready to export, do "Color to Alpha" -> set white as alpha.
Convert to Grayscale image mode.
Export as 'Raw image data' (leave default settings).