This guide shows how to use the SSD1306 OLED display with the Raspberry Pi Pico programmed with MicroPython firmware. You’ll learn how to display text and other useful functions to interact with the OLED display.
We have a similar tutorial using Raspberry Pi Pico with Arduino IDE: Raspberry Pi Pico: SSD1306 OLED Display (Arduino IDE).
Table of Contents
- Introducing the OLED Display
- Wire the OLED to the Raspberry Pi Pico
- Installing Libraries
- Write Text to the OLED Display
- Other Useful OLED Functions
Prerequisites – MicroPython Firmware
To follow this tutorial you need MicroPython firmware installed in your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board.
The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.
Introducing 0.96 inch OLED Display
The OLED display that we’ll use in this tutorial is the SSD1306 model: a monocolor, 0.96 inch display with 128×64 pixels as shown in the following figure.
The OLED display doesn’t require backlight, which results in a very nice contrast in dark environments. Additionally, its pixels consume energy only when they are on, so the OLED display consumes less power when compared to other displays.
The model we’re using has four pins and communicates with any microcontroller using I2C communication protocol. There are models that come with an extra RESET pin or that communicate using SPI communication protocol.
Parts Required
Here’s a list of parts you need for this project:
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
OLED Display SSD1306 Pin Wiring
The OLED display uses I2C communication protocol, so wiring is pretty straightforward. You can use the following table as a reference. We’re using GPIO 4 (SDA) and GPIO 5 (SCL) as I2C pins, but you can use any other pair of I2C pins (make sure you check the Raspberry Pi Pico pinout diagram).
Pin | Raspberry Pi Pico |
Vin | 3.3V(OUT) |
GND | GND |
SCL | GPIO 5 |
SDA | GPIO 4 |
You can also use the following schematic diagram as a reference.
SSD1306 OLED Library
The library to write to the OLED display isn’t part of the standard MicroPython library by default. So, you need to upload the library to your Raspberry Pi Pico board.
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces created by Adafruit
import time
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xa4)
SET_NORM_INV = const(0xa6)
SET_DISP = const(0xae)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xa0)
SET_MUX_RATIO = const(0xa8)
SET_COM_OUT_DIR = const(0xc0)
SET_DISP_OFFSET = const(0xd3)
SET_COM_PIN_CFG = const(0xda)
SET_DISP_CLK_DIV = const(0xd5)
SET_PRECHARGE = const(0xd9)
SET_VCOM_DESEL = const(0xdb)
SET_CHARGE_PUMP = const(0x8d)
class SSD1306:
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
# Note the subclass must initialize self.framebuf to a framebuffer.
# This is necessary because the underlying data buffer is different
# between I2C and SPI implementations (I2C needs an extra byte).
self.poweron()
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR, 0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO, self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET, 0x00,
SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV, 0x80,
SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
SET_VCOM_DESEL, 0x30, # 0.83*Vcc
# display
SET_CONTRAST, 0xff, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_framebuf()
def fill(self, col):
self.framebuf.fill(col)
def pixel(self, x, y, col):
self.framebuf.pixel(x, y, col)
def scroll(self, dx, dy):
self.framebuf.scroll(dx, dy)
def text(self, string, x, y, col=1):
self.framebuf.text(string, x, y, col)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
# Add an extra byte to the data buffer to hold an I2C data/command byte
# to use hardware-compatible I2C transactions. A memoryview of the
# buffer is used to mask this byte from the framebuffer operations
# (without a major memory hit as memoryview doesn't copy to a separate
# buffer).
self.buffer = bytearray(((height // 8) * width) + 1)
self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1
self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_framebuf(self):
# Blast out the frame buffer using a single I2C transaction to support
# hardware I2C interfaces.
self.i2c.writeto(self.addr, self.buffer)
def poweron(self):
pass
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
self.buffer = bytearray((height // 8) * width)
self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs.high()
self.dc.low()
self.cs.low()
self.spi.write(bytearray([cmd]))
self.cs.high()
def write_framebuf(self):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs.high()
self.dc.high()
self.cs.low()
self.spi.write(self.buffer)
self.cs.high()
def poweron(self):
self.res.high()
time.sleep_ms(1)
self.res.low()
time.sleep_ms(10)
self.res.high()
Follow the next steps to upload the library file to the Raspberry Pi Pico.
1. Create a new file in Thonny IDE and copy the library code. The OLED library code can be found here.
2. Go to File > Save as and select Raspberry Pi Pico.
3. Name the file ssd1306.py and click OK to save the file on the Raspberry Pi Pico.
And that’s it. The library was uploaded to your board. Now, you can use the library functionalities in your code by importing the library.
Finding the OLED I2C Address
By default, the OLED library we’re using will assume that your OLED I2C address is 0x3c. For most I2C SSD1306 displays, that will be their address. However, some displays might have a different address, so it’s important to check the I2C address before continuing.
I2C Scanner – MicroPython
Open Thonny IDE, or the IDE of your choice, and copy the following code.
# I2C Scanner MicroPython
from machine import Pin, SoftI2C
# You can choose any other combination of I2C pins
i2c = SoftI2C(scl=Pin(5), sda=Pin(4))
print('I2C SCANNER')
devices = i2c.scan()
if len(devices) == 0:
print("No i2c device !")
else:
print('i2c devices found:', len(devices))
for device in devices:
print("I2C hexadecimal address: ", hex(device))
After copying the code:
- connect the Raspberry Pi Pico to your computer, if it isn’t already;
- make sure you have the OLED display properly connected to your board on the right I2C pins (SCL=GPIO5 ; SDA =GPIO4).
- run the previous code (I2C scanner sketch). If you’re using Thonny IDE, you just need to click on the green run icon.
The I2C address of your display will be printed on the shell. In my case, it’s the default 0x3c. Yours might be different.
Write Text – OLED Display
The Adafruit library for the OLED display comes with several functions to write text. In this section, you’ll learn how to write text using the library functions.
The following sketch displays the Hello, world! message three times in the OLED display.
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-ssd1306-oled-micropython/
from machine import Pin, SoftI2C
import ssd1306
#You can choose any other combination of I2C pins
i2c = SoftI2C(scl=Pin(5), sda=Pin(4))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
oled.text('Hello, World 1!', 0, 0)
oled.text('Hello, World 2!', 0, 10)
oled.text('Hello, World 3!', 0, 20)
oled.show()
Let’s take a quick look at how the code works.
Start by importing the necessary modules to interact with the GPIOs and send data to the OLED via I2C communication. You need to import the Pin and SoftI2C classes from the machine module.
from machine import Pin, SoftI2C
You also need to import the OLED library that you previously uploaded to the board as ssd1306.py file.
import ssd1306
We’ll use GPIO 4 (SDA) and GPIO 5 (SCL) to communicate with the display using I2C communication protocol, but you can use any other I2C pins.
#You can choose any other combination of I2C pins
i2c = SoftI2C(scl=Pin(5), sda=Pin(4))
Define the OLED width and height on the following variables:
oled_width = 128
oled_height = 64
After that, create an SSD1306_I2C object called oled. This object accepts the OLED width, height, and the I2C pins you defined earlier.
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
By default, the code assumes your OLED display has the default 0x3c I2C address. If you have a different I2C address, change the previous line to:
oled = SSD1306_I2C(oled_width, oled_height, i2c, addr = YOUR_I2C_ADDRESS)
For example, if your I2C address is 0x3d, it will look as follows (you can check your I2C address here):
oled = SSD1306_I2C(oled_width, oled_height, i2c, addr = 0x3d)
After initializing the OLED display, you just need to use the text() function on the oled object to write text. After the text() function, you need to call the show() method to update the OLED.
oled.text('Hello, World 1!', 0, 0)
oled.text('Hello, World 2!', 0, 10)
oled.text('Hello, World 3!', 0, 20)
oled.show()
The text() method accepts the following arguments (in this order):
- Message: must be of type String
- X position: where the text starts
- Y position: where the text is displayed vertically
- Text color: it can be either black or white. The default color is white and this parameter is optional.
- 0 = black
- 1 = white
For example, the following line writes the ‘Hello, World 1!’ message in white color. The text starts on x = 0 and y = 0.
oled.text('Hello, World 1!', 0, 0)
The next line of code writes the text on the next line (y =10).
oled.text('Hello, World 2!', 0, 10)
Finally, for the changes to take effect, use the show() method on the oled object.
oled.show()
Demonstration
Save the code to your Raspberry Pi Pico board using Thonny IDE or any other MicroPython IDE of your choice.
Copy the code provided to a new file on Thonny IDE.
With the code copied to the file, click on the Save icon. Then, select Raspberry Pi Pico.
Save the file with the following name: main.py. Overwrite any existing files with the same name. Notice that you should also have the ssd1306.py file that you saved previously.
Note: When you name a file main.py, the Raspberry Pi Pico will run that file automatically on boot. If you call it a different name, it will still be saved on the board filesystem, but it will not run automatically on boot.
Reset your board (unplug and plug it into your computer). Click the little green button “Run Current Script” or press F5.
The OLED should display the three “Hello World” messages that we’ve written on the code.
Toubleshooting: if you get weird error messages and your OLED display doesn’t work, try to power it with 5V instead of 3V3. In that case, connect the VCC pin to the Raspberry Pi Pico VBUS pin.
Other OLED functions
The library provides other methods to interact with the OLED display.
Fill the screen
To fill the entire screen with white, use the fill() function as follows:
oled.fill(1)
oled.show()
To clear the screen use the fill() method and pass 0 as argument. (Sets all pixels to black):
oled.fill(0)
oled.show()
Draw a pixel
To draw a pixel, use the pixel() method, followed by the show() method. The pixel() method accepts the following arguments:
- X coordinate: pixel location horizontally
- Y coordinate: pixel location vertically
- Pixel color: can be black or white
- 0 = black
- 1 = white
For example, to draw a white pixel on the top left corner:
oled.pixel(0, 0, 1)
oled.show()
Invert colors
You can also invert the OLED colors: white with black and vice versa, using the invert() method:
oled.invert(True)
To get back to the original colors, use:
oled.invert(False)
Displaying data from sensors
The text() function only accepts variables of type String as a message. Sensor readings are usually stored in int or float variables.
If you want to display sensor readings and they are stored in int or float variables, they should be converted to a String. To convert the data to a string you can use the str() function:
temperature = 12.34
temperature_string = str(temperature)
Then, you can display the temperature_string variable on the OLED using the text() and show() methods:
oled.text(temperature_string, 0, 0)
oled.show()
Wrapping Up
In this guide, we’ve shown you how to display text in the OLED display with the Raspberry Pi Pico programmed with MicroPython firmware. We’ve also shown you other useful functions to control the OLED display.
Learn more about the Raspberry Pi Pico with our exclusive eBook:
If you’re getting started with the Raspberry Pi Pico, you may also like reading some of the following tutorials:
- Getting Started with Raspberry Pi Pico (and Pico W)
- Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained
- Raspberry Pi Pico: Control Digital Outputs and Read Digital Inputs (MicroPython)
- Raspberry Pi Pico: Read Analog Inputs (MicroPython)
Check out all our Raspberry Pi Pico Guides »
If you prefer to program the Raspberry Pi Pico using Arduino IDE, you can get started with the following tutorial:
Thanks for reading.
Hello, I asked this question on Nov 27, but I still don’t have an answer. My question is if you received my email for the query on the page where “Firebase: Control ESP32 GPIOs from Anywhere” is located. Thank you.
Hi Sara, I have a problem with the Firebase_ESP_Client library, today I updated the FirebaseESP32 library.
The problem is what the streamCallback(FirebaseStream) tells me “variable or field ‘streamCallback’ declared null
56 | void streamCallback (FirebaseStream data)”
It’s right?
I use the code on this page and update the call with the new library.
//#include <Firebase_ESP_Client.h>
#include <FirebaseESP32.h>
Thanks for your future response for me.
Hi.
Please send me an email with your issue.
Make sure to be specific.
https://randomnerdtutorials.com/contact/
Regards,
Sara
Hello Sara.
thanks for another great tutorial
It helped and inspired me to
make this python script,
which displays data from the bme280 and retrieves the time
from an NTP server on first run and displays it.
the script is updated every 5 seconds
import machine
import bme280
import utime
from machine import Pin, SoftI2C
import ssd1306
import network
import socket
import time
import struct
NTP_DELTA = 2208988800 – 3600
host = “pool.ntp.org”
led = Pin(‘LED’, Pin.OUT)
ssid = ‘your ssid’
password = ‘your password’
def set_time():
NTP_QUERY = bytearray(48)
NTP_QUERY[0] = 0x1B
addr = socket.getaddrinfo(host, 123)[0][-1]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.settimeout(1)
res = s.sendto(NTP_QUERY, addr)
msg = s.recv(48)
finally:
s.close()
val = struct.unpack(“!I”, msg[40:44])[0]
t = val – NTP_DELTA
tm = time.gmtime(t)
machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0))
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print(‘waiting for connection…’)
time.sleep(1)
if wlan.status() != 3:
#raise RuntimeError(‘network connection failed’)
led.on()
print(‘not connected’)
time.sleep(1)
led.off()
wlan.deinit()
else:
print(‘connected’)
status = wlan.ifconfig()
print( ‘ip = ‘ + status[0] )
led.on()
set_time()
print(time.localtime())
led.off()
wlan.deinit()
#You can choose any other combination of I2C pins
i2c = machine.I2C(id=0, scl=machine.Pin(1), sda=machine.Pin(0))
bme = bme280.BME280(i2c=i2c)
i2c = SoftI2C(scl=Pin(5), sda=Pin(4))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
rtc=machine.RTC()
running = True
while running:
try:
#print(bme.values)
oled.fill(0)
oled.text(bme.values[0], 0, 0)
oled.text(bme.values[2], 0, 10)
oled.text(bme.values[1], 0, 20)
timestamp=rtc.datetime()
#timestring=”%04d-%02d-%02d %02d:%02d:%02d”%(timestamp[0:3] + timestamp[4:7])
timestring=”%02d-%02d-%04d %02d:%02d”%(timestamp[2:3] + timestamp[1:2] +timestamp[0:1] + timestamp[4:6])
oled.text(timestring, 0, 40)
oled.show()
utime.sleep(5)
except KeyboardInterrupt:
print(‘Program stopped’)
running = False
the python indents have unfortunately been lost in the comment.
Last but not least, remember to install the micropython_bme280 package
Since it doesn’t work with copy and paste
it is located here under the pico w folder
drive.google.com/drive/folders/1uoEoTmaNWEUUwqxlLrDgYFrzqw4Nw-W2?usp=sharing
Last but not least, remember to install the micropython bme280 package
Doesn’t work. I get the following errors:
Traceback (most recent call last):
File “”, line 9, in
File “ssd1306.py”, line 116, in init
File “ssd1306.py”, line 36, in init
File “ssd1306.py”, line 61, in init_display
File “ssd1306.py”, line 121, in write_cmd
OSError: [Errno 19] ENODEV
Not quite sure what it all means, but the oled display doesn’t turn on or display anything. Wondering if you’ve seen this before and have any help.
Thanks.
Hi.
Check your I2C connections.
Additionally, double-check your OLED I2C address.
I’ve just added a section about it after realizing some people were having issues with it.
Regards,
Sara
hoping someone can help me understand this error message. I’ve loaded the library into the lib folder of the pico several different ways- manager as shown, from GitHub version, other tutorials copy and paste. Tried the whole MicroPython library changed, cables, added a sleep 200 ms without success. Nuked and reloaded several times by various methods. This is the best I get. I’m using the adafruit OLEDs so I’m sure I have the SSD and not the SH version. And I have changed cords and tried two different displays that both work on the Arduino and ESP32 sketches. I have placed the library in the lib/ folder( also tried it without a lib folder. Not running any other libraries on either of the picos I’ve tried.
MPY: soft reboot
Traceback (most recent call last):
File “”, line 24, in
File “/lib/ssd1306.py”, line 110, in init
File “/lib/ssd1306.py”, line 36, in init
File “/lib/ssd1306.py”, line 71, in init_display
File “/lib/ssd1306.py”, line 115, in write_cmd
OSError: [Errno 5] EIO
for the most simple code:
from machine import Pin, SoftI2C
import ssd1306
from time import sleep
#You can choose any other combination of I2C pins
i2c = SoftI2C(scl=Pin(5), sda=Pin(4))
also tried 0,1 and 2,3 as well
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
oled.text(‘Hello, World 1!’, 0, 0)
oled.text(‘Hello, World 2!’, 0, 10)
oled.text(‘Hello, World 3!’, 0, 20)
oled.show()
oled.fill(0)
oled.show()
Have you Flashed MicroPython Firmware on your Raspberry Pi Pico
https://randomnerdtutorials.com/getting-started-raspberry-pi-pico-w/?preview=true&_thumbnail_id=130915
Hi.
I found two similar issues.
Take a look to see if any of the suggestions help:
forums.raspberrypi.com/viewtopic.php?t=343639
forums.raspberrypi.com/viewtopic.php?p=2046466&hilit=1306#p2046466
Regards,
Sara
It probably can’t help you,
but are you sure you connected sda to gpio 4
pin six from top and scl on gpio 5 pin 7 from top
and + on 3v3 and not on 3v3_en
I solved it. Ran the I2C scanner, found my OLED is on 0x3d.
I didn’t know how to specify the address which I guess in the example finds it by default.
What worked for me is:
oled = SSD1306_I2C(128,64,i2c,addr = 0x3d)
I hope this spares others the pain I had. I thank you all for your help. Much appreciated.
Nice to know if I get one with a different address.
Thanks for sharing the solution. I’ll add a note on the tutorial about finding the I2C address of the OLED.
Regards,
Sara
Sara, I have an additional tip. I have been using a 128 X64 OLED display by Adafruit. After much frustration, I discovered that the only way to get consistent response is to use the 5 volt VBus on the pico. I have read about 25 tutorials, stackover threads, MicroPython threads and none of them mentioned that. I was receiving both errno 15, errno 8 and the no device error aa well as a wide range of errors messages referring to the ssd1306 library. 5 Volts fixed them all !!
Hi.
I’ll add a note about that.
My display works well with 3.3V, but other models might require 5V.
Thanks for your contribution.
Regards,
Sara
My I2C scanner keeps reporting that its on 60 (0x3c), but this address doesn’t work, and returns the “OSError: [Errno 5] EIO”. I even set up a script to try to initialize the oled object with every hex address from 0 to 5000 (arbitrary number, I have no idea how many addresses are even capable of supporting the oled), but none worked. I’m combing through the ssd1306 module to see if something’s misconfigured in it now.
Hi.
Are you sure you have an SSD1306? Some OLED displays come with a different driver..
Regards,
Sara