Let's get started with the PiicoDev® OLED Module SSD1306. In this guide, we'll connect the PiicoDev OLED to our Raspberry Pi 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 (model 3, 4, Zero W) This tutorial will use a Raspberry Pi 4, Model B
- A PiicoDev OLED Module SSD1306
- A PiicoDev Adapter for Raspberry Pi
- A PiicoDev Cable (100mm or longer is best for Raspberry Pi projects)
- (Optional) A PiicoDev platform helps secure everything together.
- (Optional) To run the code remixes at the end of this article, you will need a PiicoDev Motion Sensor and PiicoDev Distance Sensor
If you prefer not to use the PiicoDev Adapter for Raspberry Pi, there are other connection options in our PiicoDev Connection Guide.
If you haven't set up a Raspberry Pi to be used as a desktop computer before, head over to our Raspberry Pi Workshop for Beginners to get started.
Contents
- Connect the OLED to your Pi
- Enable I2C
- Install/Upgrade PiicoDev
- Line
- Rectangle
- Text
- Graph
- Animation
- Bitmap Images
- Using Multiple Modules
- Remix - Graph distance
- Remix - Tilt Table
- Conclusion
Connect the PiicoDev OLED Module to your Pi
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.
Mount the PiicoDev Adapter on your Raspberry Pi and plug connect your OLED to the Adapter via the PiicoDev cable.
The adapter connects to the 40-pin GPIO header on the Raspberry Pi - ensure the header is mounted correctly, with the "Ethernet" label on the same side as the Pi's ethernet adapter.
If you're unfamiliar with connecting PiicoDev modules, read the PiicoDev Connection Guide before proceeding.
Enable I2C
Power on your Raspberry Pi. Open the Raspberry Pi Configuration Menu, select the Interfaces tab and ensure I2C is enabled.
You should only need to do this step for your first PiicoDev project.
Install/Upgrade PiicoDev
Open Thonny (Pi Start Menu > Programming > Thonny IDE) and open the Manage Packages menu (Tools > Manage Packages)
Search for 'piicodev' and install or upgrade if necessary.
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
Download the font file font-pet-me-128.dat (right-click, "save link as"). Save this file in your working directory.
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.
Before running the following example, download the PiicoDev Test Image and save it to your working directory.
# 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. 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. 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!