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:
- A Raspberry Pi Pico with pins soldered (pointing down)
- A PiicoDev OLED Module SSD1306
- A PiicoDev Expansion Board for Raspberry Pi Pico
- A PiicoDev Cable
- (Optional) A PiicoDev platform helps secure everything together.
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 OLED to your Pico
- Download MicroPython Modules
- Example Code - Feature Demo
- Line
- Rectangle
- Text
- Graph
- Animation
- Bitmap Images
- Using Multiple Modules
- Remix - Graph distance
- Remix - Tilt Table
- Conclusion
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.
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.
Download MicroPython modules
We will need three files to easily drive the OLED module:
- Download the PiicoDev Unified Library: PiicoDev_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
There 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
Draw 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
Display 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
Graphs 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
It 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.
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!