micropython_ssd1306  on the Raspberry Pi Pico 2 to control SSD1306 OLED

Installing  micropython_ssd1306  on the Raspberry Pi Pico 2 with Thonny

This video explains how to install the  micropython_ssd1306  library (the SSD1306 OLED driver for MicroPython) on the Raspberry Pi Pico 2 using the Thonny Package Manager.


  1. Connect your Raspberry Pi Pico 2 (with MicroPython firmware) to your computer.
  2. Open Thonny and configure the interpreter for the Raspberry Pi Pico 2 (MicroPython).
  3. In the top menu, go to Tools > Manage packages...
  4. Search for  micropython-ssd1306  and click Install.
Connection:



Exercise code:
# mpy_pico2_ssd1306.py
# MicroPython exercise running on Raspberry Pi Pico 2 with RP2350
# to control 128x64 SSD1306 OLED.
#
# https://coxxect.blogspot.com/2026/10/installing-micropythonssd1306-on.html
# import time from machine import Pin, I2C from ssd1306 import SSD1306_I2C from framebuf import FrameBuffer i2c = I2C(0) print(i2c) print(i2c.scan()) oled = SSD1306_I2C(128, 64, i2c) print(oled.width, "x", oled.height) print("type(oled):", type(oled)) # whether oled is an instance of framebuf.FrameBuffer. print("isinstance(oled, FrameBuffer):", isinstance(oled, FrameBuffer)) print() # valid attributes, methods, or variables for oled print("dir(oled):\n", dir(oled)) oled.fill(0) oled.show() time.sleep(0.5) oled.fill(1) oled.show() time.sleep(0.5) oled.fill(0) oled.show() time.sleep(0.5)


SSD1306 OLED demos for Raspberry Pi Pico 2 / MicroPython



# mpy_pico2_ssd1306_ex.py
# SSD1306 OLED demos for Raspberry Pi Pico 2 / MicroPython
# 128x64 I2C OLED
#
# https://coxxect.blogspot.com/2026/10/installing-micropythonssd1306-on.html

import time
import math
import random
import framebuf

from machine import Pin, I2C
from ssd1306 import SSD1306_I2C

WIDTH = 128
HEIGHT = 64

# Your original init line:
i2c = I2C(0)

# If I2C(0) does not work on your wiring, comment the line above
# and use explicit pins. Example:
# i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400_000)

# Optional: check I2C address.
# Usually [60] means 0x3C.
# print("I2C devices:", i2c.scan())

# Most SSD1306 OLEDs use address 0x3C.
# If yours is different, try 0x3D.
oled = SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=0x3C)


# ------------------------------------------------------------
# Basic helpers
# ------------------------------------------------------------

def clear(oled, show=True):
    """Clear the display buffer."""
    oled.fill(0)
    if show:
        oled.show()


def text_center(oled, s, y, color=1):
    """Center built-in 8x8 font text horizontally."""
    x = (WIDTH - len(s) * 8) // 2
    if x < 0:
        x = 0
    oled.text(s, x, y, color)


def scaled_text(oled, s, x, y, scale=2, color=1):
    """
    Draw larger text by rendering the built-in 8x8 font
    into a small framebuffer and scaling it up.

    This is not a real font engine, but it is useful for
    simple larger text without adding external font files.
    """
    if not s:
        return

    w = len(s) * 8
    h = 8

    buf = bytearray((w * h + 7) // 8)
    fb = framebuf.FrameBuffer(buf, w, h, framebuf.MONO_HLSB)

    fb.fill(0)
    fb.text(s, 0, 0, 1)

    for yy in range(h):
        for xx in range(w):
            if fb.pixel(xx, yy):
                oled.fill_rect(
                    x + xx * scale,
                    y + yy * scale,
                    scale,
                    scale,
                    color
                )


# ------------------------------------------------------------
# Shape helpers
# ------------------------------------------------------------

def draw_circle(oled, cx, cy, r, color=1, fill=False):
    """
    Draw circle outline or filled circle.
    """
    if r < 0:
        return

    if r == 0:
        oled.pixel(cx, cy, color)
        return

    if fill:
        # Simple filled circle using horizontal lines.
        r2 = r * r
        for dy in range(-r, r + 1):
            dx = int(math.sqrt(r2 - dy * dy))
            oled.hline(cx - dx, cy + dy, 2 * dx + 1, color)
    else:
        # Midpoint circle algorithm for outline.
        x = r
        y = 0
        err = 0

        while x >= y:
            oled.pixel(cx + x, cy + y, color)
            oled.pixel(cx + y, cy + x, color)
            oled.pixel(cx - y, cy + x, color)
            oled.pixel(cx - x, cy + y, color)
            oled.pixel(cx - x, cy - y, color)
            oled.pixel(cx - y, cy - x, color)
            oled.pixel(cx + y, cy - x, color)
            oled.pixel(cx + x, cy - y, color)

            y += 1
            err += 1 + 2 * y
            if 2 * (err - x) + 1 > 0:
                x -= 1
                err += 1 - 2 * x


def draw_poly(oled, points, color=1, closed=True):
    """
    Draw a polygon from a list of (x, y) tuples.
    """
    for i in range(len(points) - 1):
        x0, y0 = points[i]
        x1, y1 = points[i + 1]
        oled.line(x0, y0, x1, y1, color)

    if closed and len(points) >= 3:
        x0, y0 = points[-1]
        x1, y1 = points[0]
        oled.line(x0, y0, x1, y1, color)


# ------------------------------------------------------------
# Demo 1: text and simple fonts
# ------------------------------------------------------------

def demo_text(oled):
    clear(oled, False)

    text_center(oled, "SSD1306", 0)
    text_center(oled, "MicroPython", 12)

    oled.hline(0, 22, WIDTH, 1)

    oled.text("8x8 font", 0, 26)

    # Inverse text: draw filled rectangle, then draw text with color 0.
    oled.fill_rect(0, 36, WIDTH, 10, 1)
    text_center(oled, "Inverse", 37, 0)

    oled.show()
    time.sleep_ms(2000)

    # Scaled text examples.
    for scale in (2, 3):
        clear(oled, False)
        text_center(oled, "Scaled", 0)
        scaled_text(oled, "Pico", 8, 20, scale)
        oled.show()
        time.sleep_ms(1500)


# ------------------------------------------------------------
# Demo 2: scrolling text
# ------------------------------------------------------------

def demo_scroll(oled, loops=1):
    # Normal 8x8 font horizontal scroll.
    msg = "Scrolling demo"
    text_w = len(msg) * 8

    for _ in range(loops):
        for x in range(WIDTH, -text_w, -1):
            # Clear only the text line area.
            oled.fill_rect(0, 24, WIDTH, 8, 0)

            oled.text(msg, x, 24, 1)
            oled.show()

            time.sleep_ms(25)

    # Bigger scrolled text.
    # This is slower because scaled_text() re-renders every frame.
    msg2 = "Pico 2"
    scale = 3
    tw = len(msg2) * 8 * scale
    th = 8 * scale
    y = (HEIGHT - th) // 2

    # Step by 2 to make it faster.
    for x in range(WIDTH, -tw, -2):
        oled.fill_rect(0, y, WIDTH, th, 0)
        scaled_text(oled, msg2, x, y, scale)
        oled.show()
        time.sleep_ms(35)


# ------------------------------------------------------------
# Demo 3: geometric shapes
# ------------------------------------------------------------

def demo_shapes(oled):
    clear(oled, False)

    text_center(oled, "Shapes", 0)

    # Rectangle outline.
    oled.rect(8, 14, 30, 20, 1)

    # Filled rectangle.
    oled.fill_rect(48, 14, 30, 20, 1)

    # Line.
    oled.line(88, 14, 118, 34, 1)

    # Horizontal and vertical lines.
    oled.hline(8, 40, 112, 1)
    oled.vline(64, 38, 24, 1)

    # Circle outline.
    draw_circle(oled, 24, 52, 8, 1, False)

    # Filled circle.
    draw_circle(oled, 56, 52, 8, 1, True)

    # Triangle.
    draw_poly(
        oled,
        [
            (88, 44),
            (104, 60),
            (72, 60)
        ],
        1,
        True
    )

    oled.show()
    time.sleep_ms(2000)

    # Growing circle animation.
    for r in range(2, 26, 2):
        clear(oled, False)
        draw_circle(oled, WIDTH // 2, HEIGHT // 2, r, 1, False)
        oled.show()
        time.sleep_ms(80)


# ------------------------------------------------------------
# Demo 4: bouncing ball animation
# ------------------------------------------------------------

def demo_bounce(oled, frames=180):
    x = WIDTH // 2
    y = HEIGHT // 2
    dx = 3
    dy = 2
    r = 5

    for _ in range(frames):
        oled.fill(0)

        # Border.
        oled.rect(0, 0, WIDTH, HEIGHT, 1)

        # Ball.
        draw_circle(oled, x, y, r, 1, True)

        oled.show()

        x += dx
        y += dy

        # Bounce horizontally.
        if x - r <= 1:
            x = r + 1
            dx = abs(dx)
        elif x + r >= WIDTH - 2:
            x = WIDTH - r - 2
            dx = -abs(dx)

        # Bounce vertically.
        if y - r <= 1:
            y = r + 1
            dy = abs(dy)
        elif y + r >= HEIGHT - 2:
            y = HEIGHT - r - 2
            dy = -abs(dy)

        time.sleep_ms(20)


# ------------------------------------------------------------
# Demo 5: radar / rotating line animation
# ------------------------------------------------------------

def demo_radar(oled, frames=180):
    cx = WIDTH // 2
    cy = HEIGHT // 2
    r = 28

    blips = [
        (cx + 10, cy - 12),
        (cx - 18, cy + 8),
        (cx + 2, cy + 20)
    ]

    for i in range(frames):
        oled.fill(0)

        # Radar rings.
        draw_circle(oled, cx, cy, r, 1, False)
        draw_circle(oled, cx, cy, r - 10, 1, False)

        # Center point.
        oled.pixel(cx, cy, 1)

        # Rotating line.
        angle = i * 0.12
        x = int(cx + (r - 2) * math.cos(angle))
        y = int(cy + (r - 2) * math.sin(angle))
        oled.line(cx, cy, x, y, 1)

        # Blips.
        for bx, by in blips:
            oled.pixel(bx, by, 1)

        oled.show()
        time.sleep_ms(25)


# ------------------------------------------------------------
# Demo 6: starfield animation
# ------------------------------------------------------------

def demo_starfield(oled, frames=220):
    stars = []

    for _ in range(40):
        stars.append([
            random.randint(0, WIDTH - 1),
            random.randint(0, HEIGHT - 1),
            random.randint(1, 3)
        ])

    for _ in range(frames):
        oled.fill(0)

        for s in stars:
            # Move star left. Bigger speed value = faster star.
            s[0] -= s[2]

            # If star leaves screen, respawn on right side.
            if s[0] < 0:
                s[0] = WIDTH - 1
                s[1] = random.randint(0, HEIGHT - 1)
                s[2] = random.randint(1, 3)

            oled.pixel(s[0], s[1], 1)

        oled.show()
        time.sleep_ms(25)


# ------------------------------------------------------------
# Demo 7: sprite animation using FrameBuffer and blit()
# ------------------------------------------------------------

def make_sprite(step):
    """
    Create a small 16x16 sprite.
    The sprite is a filled circle with a small animated cut.
    """
    buf = bytearray(16 * 16 // 8)
    spr = framebuf.FrameBuffer(buf, 16, 16, framebuf.MONO_HLSB)

    spr.fill(0)

    # Body.
    draw_circle(spr, 8, 8, 6, 1, True)

    # Eye hole.
    spr.pixel(10, 6, 0)

    # Simple wing / motion cut.
    if step == 0:
        spr.line(3, 12, 8, 8, 0)
    else:
        spr.line(3, 4, 8, 8, 0)

    return spr


def demo_sprite(oled, loops=2):
    sprites = [
        make_sprite(0),
        make_sprite(1)
    ]

    for _ in range(loops):
        x = 0
        frame = 0

        while x < WIDTH - 16:
            oled.fill(0)

            # Draw current sprite frame.
            #
            # If your MicroPython version supports the key argument,
            # you can try:
            #
            #   oled.blit(sprites[frame % 2], x, 24, 0)
            #
            # where 0 is treated as transparent.
            oled.blit(sprites[frame % 2], x, 24)

            oled.show()

            x += 3
            frame += 1

            time.sleep_ms(30)


# ------------------------------------------------------------
# Run all demos
# ------------------------------------------------------------

def run_all_demos(oled):
    try:
        while True:
            demo_text(oled)
            demo_scroll(oled, loops=1)
            demo_shapes(oled)
            demo_bounce(oled, 120)
            demo_radar(oled, 120)
            demo_starfield(oled, 120)
            demo_sprite(oled, 1)

    except KeyboardInterrupt:
        clear(oled, False)
        text_center(oled, "Stopped", 28)
        oled.show()


# Run everything.
run_all_demos(oled)

# Or comment the line above and call only one demo:
# demo_text(oled)
# demo_scroll(oled)
# demo_shapes(oled)
# demo_bounce(oled)
# demo_radar(oled)
# demo_starfield(oled)
# demo_sprite(oled)



Next:
~ Raspberry Pi Pico 2/MicroPython to handle two SSD1306 OLEDs, in main thread and background thread.

Comments

Popular posts from this blog

Python/PyQt6 slideshow run on Windows 11

480x320 TFT/ILI9488 SPI wih EP32C3 (arduino-esp32) using Arduino_GFX Library