SSD1306 SPI OLED on Raspberry Pi Pico/CircuitPython using displayio

Raspberry Pi Pico (RP2040) running CircuitPython 7.3.2, to drive 0.96" 128x64 SSD1306 SPI OLED using displayio.


Connection:
    Raspberry Pi Pico
	    ---------+
	   	VBUS |
		VSYS |
		GND  |
	       3V3_EN|
		3V3  |---------------+
		.    |               |
		.    |               |
		.    |               |
		.    |               |
		.    |               |
		GP22 |---x           |
		GND  |               |
		GP21 |-----------+   |
		GP20 |---------+ |   |
		GP19 |-------+ | |   |
		GP18 |-----+ | | |   |
		GND  |--+  | | | |   |
		GP17 }	|  | | | |   |
		GP16 |	|  | | | |   |
	    ---------+	|  | | | |   |
			|  | | | |   |
        SSD1306         |  | | | |   |
        SPI OLED        |  | | | |   |
	    ---------+  |  | | | |   |
		GND  |--+  | | | |   |
		VCC  |-----|-|-|-|---+
		SCL  |-----+ | | |
		SDA  |-------+ | |
		RST  |---------+ |
		D/C  |-----------+
	    ---------+

SSD1306	Raspberry Pi Pico
GND		3V3
VCC		GND
SCL		GP18
SDA		GP19
RST		GP20
D/C		GP21
		GP22 (dummy)
My 0.96" 128x64 SSD1306 SPI OLED have no CS (chip select) pin, it's connected to GND. The adafruit_displayio_ssd1306.mpy used require CS pin, so it's dummy assigned to GP22, actually it have no connection.

Libraries needed:
- adafruit_display_shapes folder
- adafruit_display_text folder
- adafruit_displayio_ssd1306.mpy

If you don't know how to download Adafruit CircuitPython Library Bundle or run the examples code, read Download and install CircuitPython Library to CIRCUITPY drive's lib folder and XIAO nRF52840 Sense/CircuitPython examples for SSD1306 I2C OLE.

Exercise code:

cpy_rpiPico_displayio_ssd1306_simpletest.py, modified from Adafruit CircuitPython Library Bundle's displayio_ssd1306_simpletest.py example.
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

"""
This test will initialize the display using displayio and draw a solid white
background, a smaller black rectangle, and some white text.
"""

import board
import displayio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1306
import busio

displayio.release_displays()

#oled_reset = board.D9

# Use for I2C
#i2c = board.I2C()
#display_bus = displayio.I2CDisplay(i2c, device_address=0x3C, reset=oled_reset)

# Use for SPI
# spi = board.SPI()
# oled_cs = board.D5
# oled_dc = board.D6
# display_bus = displayio.FourWire(spi, command=oled_dc, chip_select=oled_cs,
#                                 reset=oled_reset, baudrate=1000000)
oled_clk = board.GP18
oled_mosi = board.GP19
oled_reset = board.GP20
oled_dc = board.GP21
oled_cs = board.GP22 # dummy, no connection

oled_spi = busio.SPI(oled_clk, MOSI=oled_mosi)
display_bus = displayio.FourWire(oled_spi, command=oled_dc, chip_select=oled_cs,
                                 reset=oled_reset, baudrate=1000000)

WIDTH = 128
HEIGHT = 64
BORDER = 5

display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT)

# Make the display context
splash = displayio.Group()
display.show(splash)

color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF  # White

bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

# Draw a smaller inner rectangle
inner_bitmap = displayio.Bitmap(WIDTH - BORDER * 2, HEIGHT - BORDER * 2, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000  # Black
inner_sprite = displayio.TileGrid(
    inner_bitmap, pixel_shader=inner_palette, x=BORDER, y=BORDER
)
splash.append(inner_sprite)

# Draw a label
text = "Hello World!"
text_area = label.Label(
    terminalio.FONT, text=text, color=0xFFFFFF, x=28, y=HEIGHT // 2 - 1
)
splash.append(text_area)

while True:
    pass
cpy_rpiPico_displayio_ssd1306_aniCircle.py, modified from cpyX_nRF_displayio_ssd1306_aniCircle.py in my previouse exercise XIAO nRF52840 Sense/CircuitPython examples for SSD1306 I2C OLED, to display a animating label over random.
"""
coXXect:
Exercise to display on 128x64 SSD1306 SPI OLED with
Raspberry Pi Pico (RP2040) running CircuitPython.
Using displayio

Libs needed:
- adafruit_displayio_ssd1306.mpy
- adafruit_display_text folder
- adafruit_display_shapes folder

port from my previous exercise
XIAO nRF52840 Sense/CircuitPython examples for SSD1306 I2C OLED
using displayio, cpyX_nRF_displayio_ssd1306_aniCircle.py in
https://coxxect.blogspot.com/2022/08/xiao-nrf52840-sensecircuitpython.html
"""

import board
import displayio
import terminalio
from adafruit_display_text import label
from adafruit_display_shapes.circle import Circle
import adafruit_displayio_ssd1306
import time
import random
import busio

displayio.release_displays()

print(adafruit_displayio_ssd1306.__name__,
      adafruit_displayio_ssd1306.__version__)

#===========================================
# Use for I2C
# i2c = board.I2C()
# display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
#===========================================
# Use for SPI
oled_clk = board.GP18
oled_mosi = board.GP19
oled_reset = board.GP20
oled_dc = board.GP21
oled_cs = board.GP22 # dummy, no connection

oled_spi = busio.SPI(oled_clk, MOSI=oled_mosi)
display_bus = displayio.FourWire(oled_spi,
                                 command=oled_dc,
                                 chip_select=oled_cs,
                                 reset=oled_reset,
                                 baudrate=1000000)
#===========================================
DISP_WIDTH = 128
DISP_HEIGHT = 64
BORDER = 5

display = adafruit_displayio_ssd1306.SSD1306(
    display_bus,
    width=DISP_WIDTH,
    height=DISP_HEIGHT)

# Make the display context
splash = displayio.Group()
display.show(splash)

bg_bitmap = displayio.Bitmap(DISP_WIDTH, DISP_HEIGHT, 2)
bg_palette = displayio.Palette(2)
bg_palette[0] = 0x000000  # Black
bg_palette[1] = 0xFFFFFF  # White

bg_sprite = displayio.TileGrid(
    bg_bitmap,
    pixel_shader=bg_palette,
    x=0, y=0)

#===========================================
#prepare Label of title
title = " coXXect "
group_title = displayio.Group(scale=1)

label_title = label.Label(terminalio.FONT,
                        text=title,
                        color=0xFFFFFF)
label_title.anchor_point = (0.0, 0.0)
label_title.anchored_position = (0, 0)
label_title_width = label_title.bounding_box[2]
label_title_height = label_title.bounding_box[3]
shape_title_r = label_title_width//2

shape_title = Circle(x0=label_title_width//2,
                     y0=label_title_height//2,
                     r=shape_title_r,
                     fill=0x000000,
                     outline=0xFFFFFF, stroke=1)

group_title.x = (DISP_WIDTH-label_title_width)//2
group_title.y = (DISP_HEIGHT-label_title_height)//2
group_title.append(shape_title)
group_title.append(label_title)
#===========================================
splash.append(bg_sprite)
splash.append(group_title)
#===========================================
def background_random():
    global bg_bitmap
    x = random.randrange(DISP_WIDTH)
    y = random.randrange(DISP_HEIGHT)
    c = bg_bitmap[x, y]
    c = not c
    bg_bitmap[x, y] = c


aniXMove = +1
aniYMove = +1
aniXLim = DISP_WIDTH - 1 - shape_title.width

aniYdelta = (DISP_HEIGHT-1)//2 - shape_title_r
aniYLowLim = group_title.y - aniYdelta
aniYUppLim = group_title.y + aniYdelta

"""
# it's used to help me calculate the Y-limit
# just comment it

print("group_title.x", group_title.x)
print("group_title.y", group_title.y)
print(dir(group_title))

for x in range(-5, 5):
    bg_bitmap[x+group_title.x, group_title.y]=1
for y in range(-5, 5):
    bg_bitmap[group_title.x, y+group_title.y]=1

print("aniXLim", aniXLim)
print("aniYLowLim", aniYLowLim)
print("aniYUppLim", aniYUppLim)

for x in range(128):
  bg_bitmap[x, aniYLowLim] = 1
  bg_bitmap[x, aniYUppLim] = 1
"""
def title_animation():
    global group_title
    global aniXMove
    global aniYMove
    global aniXLim
    global aniYLim
    
    #Move Title group
    x = group_title.x + aniXMove
    group_title.x = x
    if aniXMove > 0:
        if x >= aniXLim:
            aniXMove = -1
    else:
        if x <= 0:
            aniXMove = +1
            
    y = group_title.y + aniYMove
    group_title.y = y
    if aniYMove > 0:
        if y >= aniYUppLim:
            aniYMove = -1
    else:
        if y <= aniYLowLim:
            aniYMove = +1


BG_CHANGE_DURATION = 0.05
bg_change_nx = time.monotonic() + BG_CHANGE_DURATION
TITLE_CHANGE_DURATION = 0.25
title_change_nx = time.monotonic() + TITLE_CHANGE_DURATION
while True:
    
    now = time.monotonic()
    
    if now >= bg_change_nx:
        bg_change_nx = now+BG_CHANGE_DURATION
        background_random()
    
    if now >= title_change_nx:
        title_change_nx = now+TITLE_CHANGE_DURATION
        title_animation()
    
    time.sleep(0.01)


cpy_rpiPico_displayio_ssd1306_aniCircle_2.py, further modified to keep changing the label text.
"""
coXXect:
Exercise to display on 128x64 SSD1306 SPI OLED with
Raspberry Pi Pico (RP2040) running CircuitPython.
Using displayio

Libs needed:
- adafruit_displayio_ssd1306.mpy
- adafruit_display_text folder
- adafruit_display_shapes folder

port from my previous exercise
XIAO nRF52840 Sense/CircuitPython examples for SSD1306 I2C OLED
using displayio, cpyX_nRF_displayio_ssd1306_aniCircle.py in
https://coxxect.blogspot.com/2022/08/xiao-nrf52840-sensecircuitpython.html
"""

import board
import displayio
import terminalio
from adafruit_display_text import label
from adafruit_display_shapes.circle import Circle
import adafruit_displayio_ssd1306
import time
import random
import busio

displayio.release_displays()

print(adafruit_displayio_ssd1306.__name__,
      adafruit_displayio_ssd1306.__version__)

#===========================================
# Use for I2C
# i2c = board.I2C()
# display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
#===========================================
# Use for SPI
oled_clk = board.GP18
oled_mosi = board.GP19
oled_reset = board.GP20
oled_dc = board.GP21
oled_cs = board.GP22 # dummy, no connection

oled_spi = busio.SPI(oled_clk, MOSI=oled_mosi)
display_bus = displayio.FourWire(oled_spi,
                                 command=oled_dc,
                                 chip_select=oled_cs,
                                 reset=oled_reset,
                                 baudrate=1000000)
#===========================================
DISP_WIDTH = 128
DISP_HEIGHT = 64
BORDER = 5

display = adafruit_displayio_ssd1306.SSD1306(
    display_bus,
    width=DISP_WIDTH,
    height=DISP_HEIGHT)

# Make the display context
splash = displayio.Group()
display.show(splash)

bg_bitmap = displayio.Bitmap(DISP_WIDTH, DISP_HEIGHT, 2)
bg_palette = displayio.Palette(2)
bg_palette[0] = 0x000000  # Black
bg_palette[1] = 0xFFFFFF  # White

bg_sprite = displayio.TileGrid(
    bg_bitmap,
    pixel_shader=bg_palette,
    x=0, y=0)

#===========================================
#prepare Label of title
title = " coXXect "
group_title = displayio.Group(scale=1)

label_title = label.Label(terminalio.FONT,
                        text=title,
                        color=0xFFFFFF)
label_title.anchor_point = (0.0, 0.0)
label_title.anchored_position = (0, 0)
label_title_width = label_title.bounding_box[2]
label_title_height = label_title.bounding_box[3]
shape_title_r = label_title_width//2

shape_title = Circle(x0=label_title_width//2,
                     y0=label_title_height//2,
                     r=shape_title_r,
                     fill=0x000000,
                     outline=0xFFFFFF, stroke=1)

group_title.x = (DISP_WIDTH-label_title_width)//2
group_title.y = (DISP_HEIGHT-label_title_height)//2
group_title.append(shape_title)
group_title.append(label_title)
#===========================================
splash.append(bg_sprite)
splash.append(group_title)
#===========================================
def background_random():
    global bg_bitmap
    x = random.randrange(DISP_WIDTH)
    y = random.randrange(DISP_HEIGHT)
    c = bg_bitmap[x, y]
    c = not c
    bg_bitmap[x, y] = c


aniXMove = +1
aniYMove = +1
aniXLim = DISP_WIDTH - 1 - shape_title.width

aniYdelta = (DISP_HEIGHT-1)//2 - shape_title_r
aniYLowLim = group_title.y - aniYdelta
aniYUppLim = group_title.y + aniYdelta

"""
# it's used to help me calculate the Y-limit
# just comment it

print("group_title.x", group_title.x)
print("group_title.y", group_title.y)
print(dir(group_title))

for x in range(-5, 5):
    bg_bitmap[x+group_title.x, group_title.y]=1
for y in range(-5, 5):
    bg_bitmap[group_title.x, y+group_title.y]=1

print("aniXLim", aniXLim)
print("aniYLowLim", aniYLowLim)
print("aniYUppLim", aniYUppLim)

for x in range(128):
  bg_bitmap[x, aniYLowLim] = 1
  bg_bitmap[x, aniYUppLim] = 1
"""
TOGGLE_TITLE_NUM = 5
toggle_title_cnt = TOGGLE_TITLE_NUM
TOG_TITLE = [[" coXXect ", 5],
             [" SSD1306 ", 5],
             [" SPI     ", 1],
             ["  SPI    ", 1],
             ["   SPI   ", 1],
             ["    SPI  ", 1],
             ["     SPI ", 1],
             ["  OLED   ", 5]]

toggle_title_st = 0

def title_animation():
    global group_title
    global aniXMove
    global aniYMove
    global aniXLim
    global aniYLim
    
    global label_title
    global toggle_title_cnt
    global toggle_title_st
    
    #Move Title group
    x = group_title.x + aniXMove
    group_title.x = x
    if aniXMove > 0:
        if x >= aniXLim:
            aniXMove = -1
    else:
        if x <= 0:
            aniXMove = +1
            
    y = group_title.y + aniYMove
    group_title.y = y
    if aniYMove > 0:
        if y >= aniYUppLim:
            aniYMove = -1
    else:
        if y <= aniYLowLim:
            aniYMove = +1
            
    toggle_title_cnt = toggle_title_cnt-1
    if toggle_title_cnt <= 0:
        
        toggle_title_st = toggle_title_st+1
        if toggle_title_st >= len(TOG_TITLE):
            toggle_title_st = 0
        label_title.text = TOG_TITLE[toggle_title_st][0]
        toggle_title_cnt = TOG_TITLE[toggle_title_st][1]

BG_CHANGE_DURATION = 0.05
bg_change_nx = time.monotonic() + BG_CHANGE_DURATION
TITLE_CHANGE_DURATION = 0.25
title_change_nx = time.monotonic() + TITLE_CHANGE_DURATION
while True:
    
    now = time.monotonic()
    
    if now >= bg_change_nx:
        bg_change_nx = now+BG_CHANGE_DURATION
        background_random()
    
    if now >= title_change_nx:
        title_change_nx = now+TITLE_CHANGE_DURATION
        title_animation()
    
    time.sleep(0.01)


Comments

Popular posts from this blog

MicroPython/ESP32-C3 + 1.8" 128x160 TFT ST7735 SPI, using boochow/MicroPython-ST7735 library.

CameraWebServe: ESP32-S3 (arduino-esp32) + OV5640 camera module