OLED Display Graphics & Animation with Raspberry Pi Pico
Wire a 128×64 SSD1306 OLED over I2C and program it with MicroPython. Display text, logos, and live timer animations — all simulated in Wokwi.
Learn how to wire and program a 128×64 SSD1306 OLED display with the Raspberry Pi Pico using I2C communication in the Wokwi simulator. This hands-on tutorial covers displaying text, graphics, logos, and animations using MicroPython — perfect for IoT dashboards, digital clocks, and embedded display systems.
What is an OLED Display?
An OLED (Organic Light Emitting Diode) display is a low-power screen that displays crisp text and graphics without any backlighting. The popular SSD1306 controller supports 128×64 and 128×32 resolutions over I2C or SPI.
Why Use SSD1306 OLED?
Components Used
Create a New Project in Wokwi
- Go to wokwi.com and sign in
- Click New Project
- Select Raspberry Pi Pico
Add the OLED Display Component
- Click the blue "+" button in Wokwi
- Search for SSD1306 or OLED
- Select SSD1306 128×64 OLED Display
- Add it to the workspace
OLED Default Settings
OLED Pin Configuration (I2C Mode)
| OLED Pin | Function | Connect To (Pico) | Wire Colour |
|---|---|---|---|
| GND | Ground | GND.8 | ⚫ Black |
| VCC | Power (3.3V) | 3V3_EN | 🔴 Red |
| SCL | I2C Clock | GP27 | 🟣 Purple |
| SDA | I2C Data | GP26 | 🟠 Orange |
Understanding I2C on Raspberry Pi Pico
The Raspberry Pi Pico has two I2C buses. This tutorial uses I2C1 on GP26/GP27:
0x3C
Wire the Circuit — diagram.json
Copy the diagram.json below and paste it directly into the Wokwi diagram editor. This sets up the Pico, mini breadboard, and OLED with all connections pre-wired.
{
"version": 1,
"author": "MakeMindz",
"editor": "wokwi",
"parts": [
{
"type": "wokwi-breadboard-mini",
"id": "bb1",
"top": 0, "left": 0,
"attrs": {}
},
{
"type": "wokwi-pi-pico",
"id": "pico",
"top": 112, "left": 50,
"rotate": 270,
"attrs": { "env": "micropython-20220618-v1.19.1" }
},
{
"type": "board-ssd1306",
"id": "oled1",
"top": 60, "left": 80,
"attrs": {}
}
],
"connections": [
[ "bb1:2b.f", "bb1:2t.e", "black", [ "v0" ] ],
[ "bb1:3b.f", "bb1:3t.e", "red", [ "v0" ] ],
[ "bb1:4b.f", "bb1:4t.e", "purple", [ "v0" ] ],
[ "bb1:5b.f", "bb1:5t.e", "orange", [ "v0" ] ],
[ "bb1:2t.d", "bb1:12t.d", "black", [ "v0" ] ],
[ "bb1:3t.c", "bb1:13t.c", "red", [ "v0" ] ],
[ "bb1:4t.b", "bb1:14t.b", "purple", [ "v0" ] ],
[ "bb1:5t.a", "bb1:15t.a", "orange", [ "v0" ] ],
[ "bb1:12t.e", "oled1:GND", "black", [ "v0" ] ],
[ "bb1:13t.e", "oled1:VCC", "red", [ "v0" ] ],
[ "bb1:14t.e", "oled1:SCL", "purple", [ "v0" ] ],
[ "bb1:15t.e", "oled1:SDA", "orange", [ "v0" ] ],
[ "pico:GND.8", "bb1:2b.j", "black", [ "v0" ] ],
[ "pico:3V3_EN", "bb1:3b.j", "red", [ "v0" ] ],
[ "pico:GP27", "bb1:4b.j", "purple", [ "v-15", "h-38.25" ] ],
[ "pico:GP26", "bb1:5b.j", "orange", [ "v-25", "h-36.92" ] ],
[ "bb1:2t.a", "bb1:17t.a", "black", [ "v-7.93", "h144" ] ],
[ "bb1:15t.d", "bb1:16t.d", "orange", [ "v0" ] ]
],
"dependencies": {}
}
Add the SSD1306 Driver — ssd1306.py
In Wokwi, create a new file called ssd1306.py and paste the driver code below. This is the official MicroPython SSD1306 library from the MicroPython repository.
ssd1306.py — it is imported by your main.py. Create it as a separate file in the Wokwi project.# MicroPython SSD1306 OLED driver, I2C and SPI interfaces # Source: https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py from micropython import const import framebuf import time # 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(framebuf.FrameBuffer): def __init__(self, width, height, external_vcc): self.width = width self.height = height self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) self.init_display() def init_display(self): for cmd in ( SET_DISP | 0x00, # display off SET_MEM_ADDR, 0x00, # horizontal addressing SET_DISP_START_LINE | 0x00, SET_SEG_REMAP | 0x01, SET_MUX_RATIO, self.height - 1, SET_COM_OUT_DIR | 0x08, SET_DISP_OFFSET, 0x00, SET_COM_PIN_CFG, 0x02 if self.width > 2 * self.height else 0x12, SET_DISP_CLK_DIV, 0x80, SET_PRECHARGE, 0x22 if self.external_vcc else 0xF1, SET_VCOM_DESEL, 0x30, SET_CONTRAST, 0xFF, SET_ENTIRE_ON, SET_NORM_INV, SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, SET_DISP | 0x01, # display on ): self.write_cmd(cmd) self.fill(0) self.show() def poweroff(self): self.write_cmd(SET_DISP | 0x00) def poweron(self): self.write_cmd(SET_DISP | 0x01) 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, x1 = 0, self.width - 1 if self.width == 64: 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_data(self.buffer) 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) self.write_list = [b"\x40", None] super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.temp[0] = 0x80 self.temp[1] = cmd self.i2c.writeto(self.addr, self.temp) def write_data(self, buf): self.write_list[1] = buf self.i2c.writevto(self.addr, self.write_list)
Write main.py — MicroPython Code
This script initialises I2C1 on GP26/GP27, creates the OLED object, then runs three display functions: logo, text, and a live timer animation.
from machine import Pin, I2C from ssd1306 import SSD1306_I2C import framebuf, sys import utime pix_res_x = 128 pix_res_y = 64 def init_i2c(scl_pin, sda_pin): # Initialise I2C1 at 200kHz on the specified pins i2c_dev = I2C(1, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=200000) i2c_addr = [hex(ii) for ii in i2c_dev.scan()] if not i2c_addr: print('No I2C Display Found') sys.exit() else: print("I2C Address : {}".format(i2c_addr[0])) print("I2C Configuration: {}".format(i2c_dev)) return i2c_dev def display_logo(oled): # Display the Raspberry Pi logo using a FrameBuffer bitmap buffer = bytearray( b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|?\x00" b"\x01\x86@\x80\x01\x01\x80\x80\x01\x11\x88\x80\x01\x05\xa0\x80" b"\x00\x83\xc1\x00\x00C\xe3\x00\x00~\xfc\x00\x00L'\x00\x00\x9c" b"\x11\x00\x00\xbf\xfd\x00\x00\xe1\x87\x00\x01\xc1\x83\x80\x02A" b"\x82@\x02A\x82@\x02\xc1\xc2@\x02\xf6>\xc0\x01\xfc=\x80\x01" b"\x18\x18\x80\x01\x88\x10\x80\x00\x8c!\x00\x00\x87\xf1\x00\x00" b"\x7f\xf6\x00\x008\x1c\x00\x00\x0c \x00\x00\x03\xc0\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00" ) fb = framebuf.FrameBuffer(buffer, 32, 32, framebuf.MONO_HLSB) oled.fill(0) oled.blit(fb, 96, 0) # draw logo at top-right oled.show() def display_text(oled): # Overlay text on the current screen oled.text("Raspberry Pi", 5, 5) oled.text("Pico", 5, 15) oled.show() def display_anima(oled): # Live timer animation — updates every second start_time = utime.ticks_ms() while True: elapsed = (utime.ticks_diff(utime.ticks_ms(), start_time) // 1000) + 1 # Clear only the timer line using a filled black rectangle oled.fill_rect(5, 40, oled.width - 5, 8, 0) oled.text("Timer:", 5, 30) oled.text(str(elapsed) + " sec", 5, 40) oled.show() utime.sleep_ms(1000) def main(): # Init I2C1: SCL=GP27, SDA=GP26 i2c_dev = init_i2c(scl_pin=27, sda_pin=26) oled = SSD1306_I2C(pix_res_x, pix_res_y, i2c_dev) display_logo(oled) # Step 1: draw RPi logo display_text(oled) # Step 2: overlay text display_anima(oled) # Step 3: live timer loop if __name__ == '__main__': main()
Code Breakdown
Run the Simulation
- Create ssd1306.py and paste the driver code
- Create / edit main.py and paste the main code
- Paste the diagram.json into the diagram editor
- Click the ▶ green Play button
- The OLED display will show the Raspberry Pi logo, then text, then a live timer
I2C Address : 0x3c — confirming the display is connected correctly.
Comments
Post a Comment