CircuitPython Usage

Displayio is only available on express board due to the smaller memory size on non-express boards.

It's easy to use OLEDs with Python and the Adafruit CircuitPython DisplayIO SSD1305 module.  This module allows you to easily write Python code to control the display.

To demonstrate the usage, we'll initialize the library and use Python code to control the OLED from the board's Python REPL.

I2C Initialization

If your display is connected to the board using I2C you'll first need to initialize the I2C bus.  First import the necessary modules:

Download: file
import board

Now for run this command to create the I2C instance using the default SCL and SDA pins (which will be marked on the board's pins if using a Feather or similar Adafruit board):

Download: file
i2c = board.I2C()

After initializing the I2C interface for your firmware as described above, you can create an instance of the I2CDisplay bus:

Download: file
import displayio
import adafruit_displayio_ssd1305
display_bus = displayio.I2CDisplay(i2c, device_address=0x3c, reset=board.D9)

Finally, you can pass the display_bus in and create an instance of the SSD1305 I2C driver by running:

Download: file
display = adafruit_displayio_ssd1305.SSD1305(display_bus, width=128, height=64)

Now you should be seeing an image of the REPL. Note that the last two parameters to the SSD1305 class initializer are the width and height of the display in pixels.  Be sure to use the right values for the display you're using!

Changing the I2C address

If you connect Pin #4 of the OLED to +3V instead of Ground the I2C address will be different different (0x3d):

Download: file
display_bus = displayio.I2CDisplay(i2c, device_address=0x3d, reset=board.D9)
display = adafruit_displayio_ssd1305.SSD1305(display_bus, width=128, height=64)

At this point the I2C bus and display are initialized. Skip down to the example code section.

SPI Initialization

If your display is connected to the board using SPI you'll first need to initialize the SPI bus. 

If you're using a microcontroller board, run the following commands:

Download: file
import board
import displayio
import adafruit_displayio_ssd1305

displayio.release_displays()

spi = board.SPI()
tft_cs = board.D5
tft_dc = board.D6
tft_reset = board.D9

display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs,
                                 reset=tft_reset, baudrate=1000000)
display = adafruit_displayio_ssd1305.SSD1305(display_bus, width=128, height=64)

The parameters to the FourWire initializer are the pins connected to the display's DCCS, and reset. Because we are using keyword arguments, they can be in any position.  Again make sure to use the right pin names as you have wired up to your board!

Note that the last two parameters to the SSD1305 class initializer are the width and height of the display in pixels. Be sure to use the right values for the display you're using!

Example Code

"""
This test will initialize the display using displayio and draw a solid white
background, a smaller black rectangle, and some white text.
"""

import board
import displayio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1305

displayio.release_displays()

# Reset is usedfor both SPI and I2C
oled_reset = board.D9

# Use for SPI
spi = board.SPI()
oled_cs = board.D5
oled_dc = board.D6
display_bus = displayio.FourWire(
    spi, command=oled_dc, chip_select=oled_cs, baudrate=1000000, reset=oled_reset
)

# Use for I2C
# i2c = board.I2C()
# display_bus = displayio.I2CDisplay(i2c, device_address=0x3c, reset=oled_reset)

WIDTH = 128
HEIGHT = 64  # Change to 32 if needed
BORDER = 8
FONTSCALE = 1

display = adafruit_displayio_ssd1305.SSD1305(display_bus, width=WIDTH, height=HEIGHT)

# Make the display context
splash = displayio.Group(max_size=10)
display.show(splash)

color_bitmap = displayio.Bitmap(display.width, display.height, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF  # White

bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

# Draw a smaller inner rectangle
inner_bitmap = displayio.Bitmap(
    display.width - BORDER * 2, display.height - BORDER * 2, 1
)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000  # Black
inner_sprite = displayio.TileGrid(
    inner_bitmap, pixel_shader=inner_palette, x=BORDER, y=BORDER
)
splash.append(inner_sprite)

# Draw a label
text = "Hello World!"
text_area = label.Label(terminalio.FONT, text=text, color=0xFFFFFF)
text_width = text_area.bounding_box[2] * FONTSCALE
text_group = displayio.Group(
    max_size=10,
    scale=FONTSCALE,
    x=display.width // 2 - text_width // 2,
    y=display.height // 2,
)
text_group.append(text_area)  # Subgroup for text scaling
splash.append(text_group)

while True:
    pass

Let's take a look at the sections of code one by one. We start by importing the board so that we can initialize SPI, displayio,terminalio for the font, a label, and the adafruit_displayio_ssd1305 driver.

Download: file
import board
import displayio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1305

Next we release any previously used displays. This is important because if the microprocessor is reset, the display pins are not automatically released and this makes them available for use again.

Download: file
displayio.release_displays()

Here we set oled_reset to board.D9 which will be used with either SPI or I2C. If your board is wired differently, be sure to change it to match your wiring.

Download: file
oled_reset = board.D9

If you're using SPI, you would use this section of code. We set the SPI object to the board's SPI with the easy shortcut function board.SPI(). By using this function, it finds the SPI module and initializes using the default SPI parameters. We set the OLED's CS (Chip Select), and DC (Data/Command) pins. We also set the display bus to FourWire which makes use of the SPI bus. The SSD1305 needs to be slowed down to 1MHz, so we pass in the additional baudrate parameter. We also pass oled_reset as the reset pin. If this differs for you, you could change it here.

Download: file
spi = board.SPI()
oled_cs = board.D5
oled_dc = board.D6
display_bus = displayio.FourWire(spi, command=oled_dc, chip_select=oled_cs,
                                 baudrate=1000000, reset=oled_reset)

If you're using I2C, you would use this section of code. We set the I2C object to the board's I2C with the easy shortcut function board.I2C(). By using this function, it finds the SPI module and initializes using the default SPI parameters. We also set the display bus to I2CDisplay which makes use of the I2C bus.

Download: file
# Use for I2C
i2c = board.I2C()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3c, reset=oled_reset)

In order to make it easy to change display sizes, we'll define a few variables in one spot here. We have WIDTH, which is the display width, HEIGHT, which is the display height and BORDER, which we will explain a little further below. FONTSCALE will be the multiplier for the font size. If your display is something different than these numbers, change them to the correct setting. For instance, you may want try changing the border size to 5 if you have a 128x32 display.

Download: file
WIDTH = 128
HEIGHT = 64     # Change to 32 if needed
BORDER = 8
FONTSCALE = 1

Finally, we initialize the driver with a width of the WIDTH variable and a height of the HEIGHT variable. If we stopped at this point and ran the code, we would have a terminal that we could type at and have the screen update.

Download: file
display = adafruit_displayio_ssd1305.SSD1305(display_bus, width=WIDTH, height=HEIGHT)

Next we create a background splash image. We do this by creating a group that we can add elements to and adding that group to the display. In this example, we are limiting the maximum number of elements to 10, but this can be increased if you would like. The display will automatically handle updating the group.

Download: file
splash = displayio.Group(max_size=10)
display.show(splash)

Next we create a Bitmap that is the full width and height of the display. The Bitmap is like a canvas that we can draw on. In this case we are creating the Bitmap to be the same size as the screen, but only have one color. Although the Bitmaps can handle up to 256 different colors, the display is monochrome so we only need one. We create a Palette with one color and set that color to 0xFFFFFF which happens to be white. If were to place a different color here, displayio handles color conversion automatically, so it may end up black or white depending on the calculation.

Download: file
color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF # White

With all those pieces in place, we create a TileGrid by passing the bitmap and palette and draw it at (0, 0) which represents the display's upper left.

Download: file
bg_sprite = displayio.TileGrid(color_bitmap,
                               pixel_shader=color_palette,
                               x=0, y=0)
splash.append(bg_sprite)

Next we will create a smaller black rectangle. The easiest way to do this is to create a new bitmap that is a little smaller than the full screen with a single color of 0x000000, which is black, and place it in a specific location. In this case, we will create a bitmap that is 5 pixels smaller on each side. This is where the BORDER variable comes into use. It makes calculating the size of the second rectangle much easier. The screen we're using here is 128x64 and we have the BORDER set to 8 , so we'll want to subtract 16 from each of those numbers.

We'll also want to place it at the position (8, 8) so that it ends up centered.

Download: file
# Draw a smaller inner rectangle
inner_bitmap = displayio.Bitmap(display.width - BORDER * 2, display.height - BORDER * 2, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000 # Black
inner_sprite = displayio.TileGrid(inner_bitmap,
                                  pixel_shader=inner_palette,
                                  x=BORDER, y=BORDER)
splash.append(inner_sprite)

Since we are adding this after the first square, it's automatically drawn on top. Here's what it looks like now.

Next let's add a label that says "Hello World!" on top of that. We're going to use the built-in Terminal Font and scale it up by a factor of two, which is what we have FONTSCALE set to. To scale the label only, we will make use of a subgroup, which we will then add to the main group.

We create the label first so that we can get the width of the bounding box and multiply it by the FONTSCALE. This gives us the actual with of the text.

Labels are automatically centered vertically, so we'll place it at half the display height for the Y coordinate, and we calculate the X coordinate to horizontally center the label. We use the // operator to divide because we want a whole number returned and it's an easy way to round it. Let's go with some white text, so we'll pass it a value of 0xFFFFFF.

Download: file
# Draw a label
text = "Hello World!"
text_area = label.Label(terminalio.FONT, text=text, color=0xFFFFFF)
text_width = text_area.bounding_box[2] * FONTSCALE
text_group = displayio.Group(max_size=10, scale=FONTSCALE, x=display.width // 2 - text_width // 2,
                             y=display.height // 2)
text_group.append(text_area) # Subgroup for text scaling
splash.append(text_group)

Finally, we place an infinite loop at the end so that the graphics screen remains in place and isn't replaced by a terminal.

Download: file
while True:
    pass

If you have the 2.3" 128x32 OLED Display, here's what the final output looks like with the height set to 32 and the border size set to 5:

Where to go from here

Be sure to check out this excellent guide to CircuitPython Display Support Using displayio

This guide was first published on Aug 07, 2015. It was last updated on Aug 07, 2015.
This page (CircuitPython Usage) was last updated on Jul 12, 2020.