PiicoDev OLED Module SSD1306 - Raspberry Pi Pico Guide

Updated 14 June 2022

Let's get started with the PiicoDev® OLED Module SSD1306. In this guide, we'll connect the PiicoDev OLED to our Raspberry Pi Pico and get it working with some example code to display text, shapes, and animations.

To follow along, it's best to have:

If you prefer not to use the Expansion Board for Raspberry Pi Pico, there are other connection options in our PiicoDev Connection Guide.

Contents

Connect the PiicoDev OLED Module to your Pico

First, make sure your OLED is set to the correct address. Referring to the image below: For modules with an address jumper (labelled "ADDR"), this jumper should be unconnected - as shown on the left. For modules with an address switch (labelled "ASW"), this switch should be in the OFF position - as shown on the right.

piicodev-oled-ssd1306-default-address

Plug your Pico into the Expansion Board, connect your OLED to the Expansion Board via the PiicoDev cable, and finally connect your Pico to your computer with a USB lead.

If you're unfamiliar with connecting PiicoDev modules, read the PiicoDev Connection Guide before proceeding.

connect-piicodev-oled-ssd1306-to-raspberry-pi-pico

Download MicroPython modules

We will need three files to easily drive the OLED module:

  • Download the PiicoDev Unified LibraryPiicoDev_Unified.py (right-click, "save link as").
  • Download the device module: PiicoDev_SSD1306.py (right-click, "save link as")
  • Download the example script: main.py (right-click, "save link as")

It will be best to keep these files wherever you like to keep your coding projects eg. Documents > PiicoDev

Feature Demo

We'll be working in Thonny - if you're unfamiliar working with Thonny see our guide for Thonny and Raspberry Pi Pico.

Open Thonny, connect to your Pico and navigate to where you stored the files from the last step. Right-click each file and select "Upload to /" to upload them to your Pico (Hint: View the files menu with View > Files)

Restart your Pico (Keyboard shortcut, Ctrl+D) and you should see your OLED Module run through a feature demonstration. The demos, in order, are:

  • Show alphanumeric text
  • Display values using bargraphs
  • Plot a function as it changes over time
  • Animation

Line

line-piicodev-oled-module-ssd1306There are several options for drawing simple lines.

  • line(x1, y1, x2, y2, c) will draw a two-point line from (x1,y1) to (x2,y2) of colour c.
  • hline(x, y, l, c) will draw a horizontal line from (x, y) of length l, colour c. Always draws left-to-right.
  • vline(x, y, l, c) is similar to hline, but draws vertical lines.

The following example draws horizontal and vertical lines from the same point and joins their ends with a two-point line. There is a small delay between the drawing of each line.

from PiicoDev_SSD1306 import *
from PiicoDev_Unified import sleep_ms # cross-platform compatible sleep function

display = create_PiicoDev_SSD1306()

display.hline(10,10, 80, 1) # horizontal line 80px long from (10,10)
display.show()
sleep_ms(500)

display.vline(10,10, 35, 1) # vertical line 35px long from (10,10)
display.show()
sleep_ms(500)

display.line(10,45, 90,10, 1) # two-point line from (10,45) to (90,10)
display.show()
sleep_ms(500)

Rectangle

rectangle-piicodev-oled-module-ssd1306Draw an unfilled rectangle with rect(x,y,width,height,colour)

The top-left corner is specified by (x,y) and width and height set the width and height in pixels. colour sets the line colour with white=1 and black=0

The following example draws an unfilled rectangle to the left of the display, and a filled white rectangle to the right. A filled black rectangle is then drawn over the top.

from PiicoDev_SSD1306 import *

display = create_PiicoDev_SSD1306()

display.rect(10, 10, 20, 50, 1) # unfilled rectangle
display.fill_rect(50, 10, 50, 40, 1) # filled rectangle (white)
display.fill_rect(60, 20, 30, 20, 0) # filled rectangle (black)
display.show()

The rect function was used in the first feature demonstration to make two bargraphs - the width of each rectangle is updated by a variable that changes over time.

Text

text-piicodev-oled-module-ssd1306Display alphanumeric text with text(string, x, y, colour), where;

  • string is a python string
  • x, y are the top-left co-ordinates
  • colour is 1 (white) or 0 (black)

The following example prints four lines. The first is a literal string, where the text to be printed is inserted into the function call. The second prints a string variable myString. The third and fourth print the value stored in a variable.

from PiicoDev_SSD1306 import *
display = create_PiicoDev_SSD1306()

myString = "this is me"
myNumber = 123.4567

display.text("Hello, World!", 0,0, 1) # literal string
display.text(myString, 0,15, 1) # string variable
display.text(str(myNumber), 0,30, 1) # print a variable
display.text("{:.2f}".format(myNumber), 0,45, 1) # use formatted-print
display.show()

Graph

graph-example-piicodev-oled-module-ssd1306Graphs are created with the graph2D() function allows plotting a single variable as it changes over time. The plot starts at the right-hand side of the display and shifts to the left every time it is updated.

updateGraph2D(graph, value) pushes the latest value onto a graph object. Multiple graphs can be shown at the same time and must be updated by making separate calls to updateGraph2D().

The following example graphs two functions independently.

from PiicoDev_SSD1306 import *
from math import sin, cos, pi

display = create_PiicoDev_SSD1306()
graph1 = display.graph2D(minValue=-1, maxValue=1) # create two graph2D objects
graph2 = display.graph2D(minValue=-4, maxValue=4)

for x in range(WIDTH):
    y = sin(2*pi*x/WIDTH) # sine wave with amplitude 1, and wavelength equal to the screen width
    z = 2*cos(6*pi*x/WIDTH) # cosine wave with amplitude 2, and wavelength one-third the screen width
    
    display.fill(0)
    display.updateGraph2D(graph1, y)
    display.updateGraph2D(graph2, z)
    display.hline(0,int(HEIGHT/2),128,1) # draw a zero-axis
    
    display.show()

Each graph is initialised with different max- and minValues which allow them to be scaled independently. Even though the high-frequency cosine function has a larger amplitude (2), it appears smaller on the graph because graph2 is initialised with a larger range between minValue and maxValue.

By default, a graph fills the whole display. See the PiicoDev documentation for other setup options.

Animation

This is where things get a lot more freestyle! An animation is created by drawing frames with each new frame changing a small amount. To animate a square moving around the screen, we just need to change the (x,y) location of that square by a small amount every frame. We can take advantage of some mathematical tricks to create smooth motion. The following example uses a single variable (theta) to move a square in a smooth, circular path. We can do this by converting the polar coordinates (radius, theta) into cartesian coordinates (x, y). By incrementing theta a small amount in each loop, we change the location on the circle that the rectangle will be drawn. Do this repeatedly and it looks like the square is moving smoothly.

In general, the steps to create an animation are:

  • Clear the display (we generally don't want to draw over old frames)
  • Draw a frame - this is usually generated by some variable that changes with time; like a frame counter, or in this case, the angle "theta".
  • Update the display

# Circular path animation

from PiicoDev_SSD1306 import *
from PiicoDev_Unified import sleep_ms # cross-platform compatible sleep function
from math import sin, cos
display = create_PiicoDev_SSD1306()

r = 20 # radius of the path (px)

theta = 0
while True:
    ### Clear the display ###
    display.fill(0) 
    
    ### Draw a frame ###    
    theta = theta + 0.02 # increment theta by a small amount
    
    # convert polar coordinates (r, theta) to cartesian coordinates (x, y)
    x = WIDTH/2  + r * cos(theta) 
    y = HEIGHT/2 + r * sin(theta)
    
    display.fill_rect(round(x), round(y), 10, 10, 1)
#     display.line(round(WIDTH/2)+5, round(HEIGHT/2)+5, round(x+5), round(y+5), 1) # uncomment for a bonus!

    
    ### Update display ###
    display.show()
    
#     sleep_ms(10) # sleep optional. Updating the display takes long enough

Of course, this is just one example of animation. Try playing with some of the parameters and observing how they change the motion path.

Bitmap Images

bitmap-example-piicodev-oled-module-ssd1306It is possible to display Portable Bitmap Images (.pbm) files on our OLED module. The source image needs to be uploaded to our Pico just like any other source file.

Before running the following example, download the PiicoDev Test Image and upload it to your Pico.

# Display a portable bitmap image (.pbm)
from PiicoDev_SSD1306 import *
display = create_PiicoDev_SSD1306()

display.load_pbm('piicodev-logo.pbm', 1)
display.show()


There are free online tools available to convert your own images to .pbm - it's best if your image size is already 128x64 pixels.

Since images are loaded into the frame buffer like any other drawing, you can still draw over the top of them if you want.

Using Multiple OLED Modules

It's possible to connect up to two OLED modules to the same PiicoDev bus and drive them independently - they each require a unique address. In the connect section, we set our OLED to the default address (0x3C): where the ADDR Jumper (or ASW Switch) is open. To connect and drive a second OLED module, its address switch/jumper needs to be closed as shown below - which will set its address to 0x3D.

piicodev-oled-module-ssd1306-multiple-displayspiicodev-oled-module-ssd1306-multiple-displays
Two OLED Modules are driven together, by setting each to a unique address using the ASW switch

With both OLED Modules set to a unique address, they can be daisy-chained and driven together using the following example code. Here, the initialisation function is given the asw argument which is the position of the address switch (0: open, 1: closed).

# This example drives two OLED modules independently.
# Each OLED module requires a unique address, set by the ADDR Jumper or ASW Switch
from PiicoDev_SSD1306 import *
oledA = create_PiicoDev_SSD1306(asw=0) 
oledB = create_PiicoDev_SSD1306(asw=1) # set up each device using the address-switch argument. 0:Open, 1:Closed

# advanced users may prefer using explicit I2C addresses, which can be set using the 'address' argument as follows:
# oledB = oledB = create_PiicoDev_SSD1306(address=0x3D)

# Load a different string onto each display
oledA.text("Display A", 0,0, 1) 
oledB.text("Display B", 0,40, 1)

oledA.show()
oledB.show()

Remix - Graph Distance

It's time to get creative! This remix uses a PiicoDev Distance Sensor as an interactive input. It reads the distance from the sensor in millimetres and plots the result to the OLED module. To follow along you will of course need this sensor, and to upload the device driver to your Pico: PiicoDev_VL53L1X.py (right-click, save-as). Read the PiicoDev Distance Sensor Guide for more help.

# Sample distance with a PiicoDev Distance Sensor and plot the data on a PiicoDev OLED

from PiicoDev_SSD1306 import *
from PiicoDev_VL53L1X import PiicoDev_VL53L1X
from PiicoDev_Unified import sleep_ms # cross-platform compatible sleep function

# Initialise distance sensor and display
display = create_PiicoDev_SSD1306()
distSensor = PiicoDev_VL53L1X()

distanceGraph = display.graph2D(height=HEIGHT-10, minValue=0, maxValue=500) # Initialise graph with scale 0 -> 500mm

while True:
    display.fill(0) # we need to clear the display every frame
    display.hline(0,HEIGHT-1,WIDTH,1); display.vline(0,0,HEIGHT,1) # draw some axes
    
    dist_mm = distSensor.read() # read the distance in millimetres
    display.text(str(dist_mm),95,3,1) # print the distance in the top right
    display.updateGraph2D(distanceGraph, dist_mm) # plot the distance
    display.show() # don't forget to show()!
    

Notice in this remix we're using some extra arguments in graph2D(). The height argument sets the maximum vertical size of our graph area. By making the graph area smaller than the maximum size, we can guarantee the displayed text is never drawn over. We also use the minValue and maxValue arguments to set the vertical scale of our graph to 0-500mm. It's the minValue and maxValue arguments that are most useful if you'd like to use this remix to plot data from any other sensor.

Remix - Tilt Table

In this remix, we'll use the PiicoDev Motion Sensor to change the location of a square shown on the display. As we tilt the motion sensor, the square will move in the corresponding direction. To follow along you'll need the Motion Sensor and to upload the device driver to your Pico: PiicoDev_MPU6050.py (right-click, save-as). Read the PiicoDev Motion Sensor Guide for more help.

# Draw a square to indicate the tilt angle of an IMU - requires a PiicoDev Motion Sensor
from PiicoDev_SSD1306 import *
from PiicoDev_MPU6050 import PiicoDev_MPU6050
from PiicoDev_Unified import sleep_ms # cross-platform compatible sleep function

display = create_PiicoDev_SSD1306() # initialise PiicoDev modules
motion = PiicoDev_MPU6050()

while True:
    display.fill(0) # clear the screen every frame
    
    tilt = motion.read_angle() # read the angle (radians)
    aX = tilt['x']
    aY = tilt['y']
    
    sensitivity = 50 # sets how far the square will move for a given tilt
    x = round( aY * sensitivity +  WIDTH/2) 
    y = round( aX * sensitivity + HEIGHT/2) # convert angles to x,y coordinates for the square
    print("{:.2f}   {:.2f}".format(aX , aY))
    
    display.rect(x-5,y-5,10,10,1) # draw the square
    display.show()


Conclusion

We've really taken our PiicoDev OLED module for a test-drive! From simple text, shapes and lines through to animations and even complex images - the sky is the limit! We even brought some other sensors in the mix to run some creative coding remixes. If you make anything cool from these examples - or just have some questions, be sure to leave a comment on this article. We're full-time makers and here to help!

Have a question? Ask the Author of this guide today!

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.

Comments


Feedback

Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.