Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
The below Python source files control an OLED display (size 96 x 64, 65K colours) using a SSD1331 chipset and the SPI interface. The source code initialises the chipset and includes hardware accelerated functions for drawing primitive shapes and a non-hardware accelerated full ASCII set. Examples include a basic Space Invaders game, and a clock.
import SSD1331
import datetime
import time
import math
SSD1331_PIN_CS = 23
SSD1331_PIN_DC = 24
SSD1331_PIN_RST = 25
if __name__ == '__main__':
device = SSD1331.SSD1331(SSD1331_PIN_DC, SSD1331_PIN_RST, SSD1331_PIN_CS)
try:
device.EnableDisplay(True)
device.Clear()
today_last_time = "Unknown"
while True:
my_now = datetime.datetime.now()
today_date = my_now.strftime("%Y-%B-%d %A")
today_time = my_now.strftime("%H:%M")
if today_time != today_last_time:
device.Clear()
time.sleep(0.01)
hours_angle = 270 + (30 * (my_now.hour + (my_now.minute / 60.0)))
hours_dx = int(math.cos(math.radians(hours_angle)) * 12)
hours_dy = int(math.sin(math.radians(hours_angle)) * 12)
minutes_angle = 270 + (6 * my_now.minute)
minutes_dx = int(math.cos(math.radians(minutes_angle)) * 18)
minutes_dy = int(math.sin(math.radians(minutes_angle)) * 18)
device.DrawCircle(30, 32, 20, SSD1331.COLOR_WHITE)
device.DrawLine(30, 32, 30 + hours_dx, 32 + hours_dy, SSD1331.COLOR_WHITE)
device.DrawLine(30, 32, 30 + minutes_dx, 32 + minutes_dy, SSD1331.COLOR_WHITE)
device.DrawString(60, 28, today_time, SSD1331.COLOR_WHITE)
today_last_time = today_time
time.sleep(0.5)
finally:
device.EnableDisplay(False)
device.Remove()
import SSD1331
import datetime
import time
import math
import random
arrow = [0x04, 0x02, 0x01, 0x02, 0x04]
alien1 = [0x4C, 0x1A, 0xB6, 0x5F, 0x5F, 0xB6, 0x1A, 0x4C]
alien2 = [0x18, 0xFD, 0xA6, 0x3C, 0x3C, 0xA6, 0xFD, 0x18]
alien3 = [0xFC, 0x98, 0x35, 0x7E, 0x7E, 0x35, 0x98, 0xFC]
ARMY_SIZE_ROWS = 2
ARMY_SIZE_COLS = 6
class Bullet:
def __init__(self, device, x, y):
self._device = device
self.x = x
self.y = y
self.Alive = False
def Render(self, on):
if self.Alive:
if on:
c = SSD1331.COLOR_WHITE
else:
c = SSD1331.COLOR_BLACK
self._device.DrawPixel(self.x, self.y + 0, c)
self._device.DrawPixel(self.x, self.y + 1, c)
self._device.DrawPixel(self.x, self.y + 2, c)
return
def Reset(self, x, y):
self.x = x
self.y = y
self.Alive = True
return
def Update(self, direction):
if self.Alive:
self.y = self.y + (direction * 4)
if self.y < 10:
self.Alive = False
return
class Player:
def __init__(self, device):
self._device = device
self.x = 48
self.y = 54
self.Bullets = []
for i in xrange(0, 4, 1):
self.Bullets.append(Bullet(device, 0, 0))
return
def Render(self, on):
if on:
c = SSD1331.COLOR_WHITE
else:
c = SSD1331.COLOR_BLACK
for i in xrange(0, len(arrow), 1):
line = arrow[i]
for j in xrange(0, 3, 1):
if line & 0x1:
self._device.DrawPixel(self.x - 2 + i, self.y + j, c)
line >>= 1
for i in xrange(0, len(self.Bullets), 1):
self.Bullets[i].Render(on)
return
def Update(self, direction):
t = self.x + (direction * 2)
if t > 4 and t < 92:
self.x = t
for i in xrange(0, len(self.Bullets), 1):
self.Bullets[i].Update(-1)
return
def Shoot(self):
for i in xrange(0, len(self.Bullets), 1):
if self.Bullets[i].Alive == False:
self.Bullets[i].Reset(self.x, self.y)
break
return
class Invader:
def __init__(self, device, minx, maxx, x, y):
self._device = device
self.x = x
self.y = y
self._direction = 1
self.Alive = True
self.Score = 10
self._minx = minx
self._maxx = maxx
return
def Render(self, on):
if self.Alive:
if on:
c = SSD1331.COLOR_GREEN
else:
c = SSD1331.COLOR_BLACK
for i in xrange(0, len(alien2), 1):
line = alien2[i]
for j in xrange(0, 8, 1):
if line & 0x1:
self._device.DrawPixel(self.x - 4 + i, self.y - 4 + j, c)
line >>= 1
return
def Update(self):
invaded = False
if self.Alive:
t = self.x + self._direction
if t > self._minx and t < self._maxx:
self.x = self.x + self._direction
else:
self._direction = self._direction * -1
self.y = self.y + 2
if self.y > 44:
invaded = True
return invaded
class Army:
def __init__(self, device):
self._device = device
self.Invaded = False
self.Invaders = []
for i in xrange(0, ARMY_SIZE_ROWS, 1):
row = []
for j in xrange(0, ARMY_SIZE_COLS, 1):
row.append(Invader(device, 4 + (j * 12) , 30 + (j * 12), 4 + (j * 12), 14 + (i * 12)))
self.Invaders.append(row)
return
def Render(self, on):
for i in xrange(0, len(self.Invaders), 1):
for j in xrange(0, len(self.Invaders[i]), 1):
self.Invaders[i][j].Render(on)
return
def Update(self, bullets):
for i in xrange(0, len(self.Invaders), 1):
for j in xrange(0, len(self.Invaders[i]), 1):
if self.Invaders[i][j].Update():
self.Invaded = True
for i in xrange(0, len(self.Invaders), 1):
for j in xrange(0, len(self.Invaders[i]), 1):
if self.Invaders[i][j].Alive:
for k in xrange(0, len(bullets), 1):
if bullets[k].Alive:
t = (self.Invaders[i][j].x - bullets[k].x) * (self.Invaders[i][j].x - bullets[k].x) + (self.Invaders[i][j].y - bullets[k].y) * (self.Invaders[i][j].y - bullets[k].y)
# if point is in circle
if t < 25: # 5 * 5 = r * r
self.Invaders[i][j].Alive = False
bullets[k].Alive = False
return
def Size(self):
size = 0
for i in xrange(0, len(self.Invaders), 1):
for j in xrange(0, len(self.Invaders[i]), 1):
if self.Invaders[i][j].Alive:
size = size + 1
return size
def Score(self):
score = 0
for i in xrange(0, len(self.Invaders), 1):
for j in xrange(0, len(self.Invaders[i]), 1):
if self.Invaders[i][j].Alive == False:
score = score + self.Invaders[i][j].Score
return score
def AiLogicShoot(army, plyr):
for i in xrange(0, len(army.Invaders), 1):
for j in xrange(0, len(army.Invaders[i]), 1):
if army.Invaders[i][j].Alive != False:
if plyr.x > (army.Invaders[i][j].x - 2) and plyr.x < (army.Invaders[i][j].x + 2):
if random.random() < 0.75:
plyr.Shoot()
return
return
def AiLogicMove(army, plyr, rows):
for i in xrange(0, len(rows), 1):
for j in xrange(0, len(rows[i]), 1):
if army.Invaders[i][rows[i][j]].Alive != False:
if plyr.x < army.Invaders[i][rows[i][j]].x:
plyr.Update(1)
return
elif plyr.x > army.Invaders[i][rows[i][j]].x:
plyr.Update(-1)
return
return
SSD1331_PIN_CS = 23
SSD1331_PIN_DC = 24
SSD1331_PIN_RST = 25
if __name__ == '__main__':
device = SSD1331.SSD1331(SSD1331_PIN_DC, SSD1331_PIN_RST, SSD1331_PIN_CS)
plyr = Player(device)
army = Army(device)
rows = []
rows.append(random.sample(range(6), 6))
rows.append(random.sample(range(6), 6))
try:
data_splash = SSD1331.UnpackDataFromBmp24File("splash.bmp")
device.EnableDisplay(True)
device.Clear()
time.sleep(0.1)
device.DrawFullScreenBitMap(data_splash) # Splash screen.
time.sleep(3)
device.Clear()
time.sleep(0.1)
device.DrawLine(0, 61, 95, 61, SSD1331.COLOR_WHITE)
device.DrawLine(0, 63, 95, 63, SSD1331.COLOR_WHITE)
time.sleep(0.1)
while army.Invaded == False and army.Size() > 0:
plyr.Render(False)
army.Render(False)
AiLogicShoot(army, plyr)
AiLogicMove(army, plyr, rows)
army.Update(plyr.Bullets)
army.Render(True)
plyr.Render(True)
device.DrawStringBg(8, 0, "Score:" + str(army.Score()), SSD1331.COLOR_BLUE, SSD1331.COLOR_BLACK)
time.sleep(0.8)
if army.Size() == 0:
device.DrawStringBg(27, 28, "Victory", SSD1331.COLOR_BLUE, SSD1331.COLOR_BLACK)
else:
device.DrawStringBg(30, 28, "Defeat", SSD1331.COLOR_RED, SSD1331.COLOR_BLACK)
time.sleep(10)
finally:
device.EnableDisplay(False)
device.Remove()
import struct
import spidev
import sys
import time
import random
import RPi.GPIO as gpio
ascii = [
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x55, 0x00, 0x55, 0x00, 0x55 ],
[ 0x00, 0x00, 0x00, 0x00, 0x00 ], # sp
[ 0x00, 0x00, 0x2f, 0x00, 0x00 ], # !
[ 0x00, 0x07, 0x00, 0x07, 0x00 ], # "
[ 0x14, 0x7f, 0x14, 0x7f, 0x14 ], # #
[ 0x24, 0x2a, 0x7f, 0x2a, 0x12 ], # $
[ 0x62, 0x64, 0x08, 0x13, 0x23 ], # %
[ 0x36, 0x49, 0x55, 0x22, 0x50 ], # &
[ 0x00, 0x05, 0x03, 0x00, 0x00 ], # '
[ 0x00, 0x1c, 0x22, 0x41, 0x00 ], # (
[ 0x00, 0x41, 0x22, 0x1c, 0x00 ], # )
[ 0x14, 0x08, 0x3E, 0x08, 0x14 ], # *
[ 0x08, 0x08, 0x3E, 0x08, 0x08 ], # +
[ 0x00, 0x00, 0xA0, 0x60, 0x00 ], # ,
[ 0x08, 0x08, 0x08, 0x08, 0x08 ], # -
[ 0x00, 0x60, 0x60, 0x00, 0x00 ], # .
[ 0x20, 0x10, 0x08, 0x04, 0x02 ], # /
[ 0x3E, 0x51, 0x49, 0x45, 0x3E ], # 0
[ 0x00, 0x42, 0x7F, 0x40, 0x00 ], # 1
[ 0x42, 0x61, 0x51, 0x49, 0x46 ], # 2
[ 0x21, 0x41, 0x45, 0x4B, 0x31 ], # 3
[ 0x18, 0x14, 0x12, 0x7F, 0x10 ], # 4
[ 0x27, 0x45, 0x45, 0x45, 0x39 ], # 5
[ 0x3C, 0x4A, 0x49, 0x49, 0x30 ], # 6
[ 0x01, 0x71, 0x09, 0x05, 0x03 ], # 7
[ 0x36, 0x49, 0x49, 0x49, 0x36 ], # 8
[ 0x06, 0x49, 0x49, 0x29, 0x1E ], # 9
[ 0x00, 0x36, 0x36, 0x00, 0x00 ], # :
[ 0x00, 0x56, 0x36, 0x00, 0x00 ], # ;
[ 0x08, 0x14, 0x22, 0x41, 0x00 ], # <
[ 0x14, 0x14, 0x14, 0x14, 0x14 ], # =
[ 0x00, 0x41, 0x22, 0x14, 0x08 ], # >
[ 0x02, 0x01, 0x51, 0x09, 0x06 ], # ?
[ 0x32, 0x49, 0x59, 0x51, 0x3E ], # @
[ 0x7C, 0x12, 0x11, 0x12, 0x7C ], # A
[ 0x7F, 0x49, 0x49, 0x49, 0x36 ], # B
[ 0x3E, 0x41, 0x41, 0x41, 0x22 ], # C
[ 0x7F, 0x41, 0x41, 0x22, 0x1C ], # D
[ 0x7F, 0x49, 0x49, 0x49, 0x41 ], # E
[ 0x7F, 0x09, 0x09, 0x09, 0x01 ], # F
[ 0x3E, 0x41, 0x49, 0x49, 0x7A ], # G
[ 0x7F, 0x08, 0x08, 0x08, 0x7F ], # H
[ 0x00, 0x41, 0x7F, 0x41, 0x00 ], # I
[ 0x20, 0x40, 0x41, 0x3F, 0x01 ], # J
[ 0x7F, 0x08, 0x14, 0x22, 0x41 ], # K
[ 0x7F, 0x40, 0x40, 0x40, 0x40 ], # L
[ 0x7F, 0x02, 0x0C, 0x02, 0x7F ], # M
[ 0x7F, 0x04, 0x08, 0x10, 0x7F ], # N
[ 0x3E, 0x41, 0x41, 0x41, 0x3E ], # O
[ 0x7F, 0x09, 0x09, 0x09, 0x06 ], # P
[ 0x3E, 0x41, 0x51, 0x21, 0x5E ], # Q
[ 0x7F, 0x09, 0x19, 0x29, 0x46 ], # R
[ 0x46, 0x49, 0x49, 0x49, 0x31 ], # S
[ 0x01, 0x01, 0x7F, 0x01, 0x01 ], # T
[ 0x3F, 0x40, 0x40, 0x40, 0x3F ], # U
[ 0x1F, 0x20, 0x40, 0x20, 0x1F ], # V
[ 0x3F, 0x40, 0x38, 0x40, 0x3F ], # W
[ 0x63, 0x14, 0x08, 0x14, 0x63 ], # X
[ 0x07, 0x08, 0x70, 0x08, 0x07 ], # Y
[ 0x61, 0x51, 0x49, 0x45, 0x43 ], # Z
[ 0x00, 0x7F, 0x41, 0x41, 0x00 ], # [
[ 0x02, 0x04, 0x08, 0x10, 0x20 ], # \
[ 0x00, 0x41, 0x41, 0x7F, 0x00 ], # ]
[ 0x04, 0x02, 0x01, 0x02, 0x04 ], # ^
[ 0x40, 0x40, 0x40, 0x40, 0x40 ], # _
[ 0x00, 0x03, 0x02, 0x04, 0x00 ], # '
[ 0x20, 0x54, 0x54, 0x54, 0x78 ], # a
[ 0x7F, 0x48, 0x44, 0x44, 0x38 ], # b
[ 0x38, 0x44, 0x44, 0x44, 0x20 ], # c
[ 0x38, 0x44, 0x44, 0x48, 0x7F ], # d
[ 0x38, 0x54, 0x54, 0x54, 0x18 ], # e
[ 0x08, 0x7E, 0x09, 0x01, 0x02 ], # f
[ 0x18, 0xA4, 0xA4, 0xA4, 0x7C ], # g
[ 0x7F, 0x08, 0x04, 0x04, 0x78 ], # h
[ 0x00, 0x44, 0x7D, 0x40, 0x00 ], # i
[ 0x40, 0x80, 0x84, 0x7D, 0x00 ], # j
[ 0x7F, 0x10, 0x28, 0x44, 0x00 ], # k
[ 0x00, 0x41, 0x7F, 0x40, 0x00 ], # l
[ 0x7C, 0x04, 0x18, 0x04, 0x78 ], # m
[ 0x7C, 0x08, 0x04, 0x04, 0x78 ], # n
[ 0x38, 0x44, 0x44, 0x44, 0x38 ], # o
[ 0xFC, 0x24, 0x24, 0x24, 0x18 ], # p
[ 0x18, 0x24, 0x24, 0x18, 0xFC ], # q
[ 0x7C, 0x08, 0x04, 0x04, 0x08 ], # r
[ 0x48, 0x54, 0x54, 0x54, 0x20 ], # s
[ 0x04, 0x3F, 0x44, 0x40, 0x20 ], # t
[ 0x3C, 0x40, 0x40, 0x20, 0x7C ], # u
[ 0x1C, 0x20, 0x40, 0x20, 0x1C ], # v
[ 0x3C, 0x40, 0x30, 0x40, 0x3C ], # w
[ 0x44, 0x28, 0x10, 0x28, 0x44 ], # x
[ 0x1C, 0xA0, 0xA0, 0xA0, 0x7C ], # y
[ 0x44, 0x64, 0x54, 0x4C, 0x44 ], # z
[ 0x00, 0x08, 0x36, 0x41, 0x00 ], # {
[ 0x00, 0x00, 0x77, 0x00, 0x00 ], # |
[ 0x00, 0x41, 0x36, 0x08, 0x00 ], # }
[ 0x02, 0x01, 0x02, 0x04, 0x02 ], # ~
[ 0x55, 0x00, 0x55, 0x00, 0x55 ]
]
def Color656(r, g, b):
c = 0
c = r >> 3
c = c << 6
c = c | (g >> 2)
c = c << 5
c = c | (b >> 3)
return c
def UnpackDataFromBmp24File(name):
with open(name, "rb") as f:
signature = f.read(2)
if signature == "BM":
f.read(4) # Ignore the size of the bmp file
f.read(2) # Ignore data - reserved
f.read(2) # Ignore data - reserved
offset, = struct.unpack('<i', f.read(4)) # Read the offset to the image data.
f.seek(0x0E) # Seek to the start of the bitmap information header.
f.read(4) # Ignore the size of the header
width, height, plane, bpp = struct.unpack('<iihh', f.read(12))
raw = list()
if width == MAX_WIDTH:
if height == MAX_HEIGHT:
if plane == 1:
if bpp == 24:
f.seek(offset)
byte = f.read(1)
while byte:
raw.append(ord(byte))
byte = f.read(1)
else:
raise Exception("Image colour depth detected: " + str(bpp) + ". Image colour depth must equal 24.")
else:
raise Exception("Colour planes detected: " + str(plane) + ". Number of colour planes must equal 1.")
else:
raise Exception("Image hight detected: " + str(height) + ". Image height must equal " + str(MAX_HEIGHT) + ".")
else:
raise Exception("Image width detected: " + str(width) + ". Image width must equal " + str(MAX_WIDTH) + ".")
i = 0
data = list() # Rows.
for y in xrange(0, MAX_HEIGHT, 1):
data.append(list()) # Columns.
for x in xrange(0, MAX_WIDTH, 1):
data[y].append(list()) # Channels.
data[y][x].append(raw[i + 2]) # Swap from BGR to RGB formatted channels.
data[y][x].append(raw[i + 1])
data[y][x].append(raw[i + 0])
i = i + 3
data.reverse() # Bitmaps are stored up-side-down so let's flip the rows.
return data
COLOR_BLACK = Color656( 0, 0, 0)
COLOR_GREY = Color656(192, 192, 192)
COLOR_WHITE = Color656(255, 255, 255)
COLOR_RED = Color656(255, 0, 0)
COLOR_PINK = Color656(240, 100, 225)
COLOR_YELLOW = Color656(255, 255, 0)
COLOR_GOLDEN = Color656(255, 215, 0)
COLOR_BROWN = Color656(128, 42, 42)
COLOR_BLUE = Color656( 0, 0, 255)
COLOR_CYAN = Color656( 0, 255, 255)
COLOR_GREEN = Color656( 0, 255, 0)
COLOR_PURPLE = Color656(160, 32, 240)
MAX_WIDTH = 0x60
MAX_HEIGHT = 0x40
FILL_RECT_DISABLE = 0x00
FILL_RECT_ENABLE = 0x01
H_SCROLL_DISABLE = 0x00
H_SCROLL_ENABLE = 0x01
V_SCROLL_DISABLE = 0x00
V_SCROLL_ENABLE = 0x01
LOCK_MODE_DISABLE = 0x12
LOCK_MODE_ENABLE = 0x16
SET_COLUMN_ADDRESS = 0x15
SET_ROW_ADDRESS = 0x75
DRAW_LINE = 0x21
DRAW_RECT = 0x22
CLEAR_WINDOW = 0x25
FILL_RECT = 0x26
CONTINUOUS_SCROLLING_SETUP = 0x27
DEACTIVE_SCROLLING = 0x2E
ACTIVE_SCROLLING = 0x2F
SET_CONTRAST_A = 0x81
SET_CONTRAST_B = 0x82
SET_CONTRAST_C = 0x83
MASTER_CURRENT_CONTROL = 0x87
SET_PRECHARGE_SPEED_A = 0x8A
SET_PRECHARGE_SPEED_B = 0x8B
SET_PRECHARGE_SPEED_C = 0x8C
SET_REMAP = 0xA0
SET_DISPLAY_START_LINE = 0xA1
SET_DISPLAY_OFFSET = 0xA2
NORMAL_DISPLAY = 0xA4
ENTIRE_DISPLAY_ON = 0xA5
ENTIRE_DISPLAY_OFF = 0xA6
INVERSE_DISPLAY = 0xA7
SET_MULTIPLEX_RATIO = 0xA8
DISPLAY_ON_DIM = 0xAC
SET_MASTER_CONFIGURE = 0xAD
DISPLAY_OFF = 0xAE
DISPLAY_ON = 0xAF
POWER_SAVE_MODE = 0xB0
PHASE_1_2_PERIOD = 0xB1
CLOCK_DIVIDER = 0xB3
SET_PRECHARGE_VOLTAGE = 0xBB
SET_V_VOLTAGE = 0xBE
LOCK_MODE = 0xFD
class SSD1331:
COMMAND = gpio.LOW
DATA = gpio.HIGH
def __init__(self, dc, rst, cs):
self.rst = rst
self.dc = dc
self.cs = cs
# Setup GPIO.
gpio.setmode(gpio.BCM)
gpio.setup(self.dc, gpio.OUT)
gpio.output(self.dc, gpio.LOW)
gpio.setup(self.rst, gpio.OUT)
gpio.output(self.rst, gpio.HIGH)
gpio.setup(self.cs, gpio.OUT)
gpio.output(self.cs, gpio.HIGH)
self.__OpenSPI() # Setup SPI.
self.__Setup() # Setup device screen.
self.Clear() # Blank the screen.
return
def __OpenSPI(self):
self.spi = spidev.SpiDev()
self.spi.open(0, 0)
self.spi.mode = 3
self.spi.max_speed_hz = 6000000
self.spi.cshigh = False
return
def __WriteCommand(self, data):
if isinstance(data, list) or isinstance(data, tuple):
gpio.output(self.dc, self.COMMAND)
self.spi.xfer(data)
return
def __WriteData(self, data):
if isinstance(data, list) or isinstance(data, tuple):
gpio.output(self.dc, self.DATA)
self.spi.xfer(data)
return
def __Setup(self):
self.spi.cshigh = True
self.spi.xfer([0])
gpio.output(self.cs, gpio.LOW)
time.sleep(0.1)
gpio.output(self.rst, gpio.LOW)
time.sleep(0.5)
gpio.output(self.rst, gpio.HIGH)
time.sleep(0.5)
self.spi.cshigh = False
self.spi.xfer([0])
self.__WriteCommand([DISPLAY_OFF])
self.__WriteCommand([SET_REMAP, 0x72])
self.__WriteCommand([SET_DISPLAY_START_LINE, 0x00])
self.__WriteCommand([SET_DISPLAY_OFFSET, 0x00])
self.__WriteCommand([NORMAL_DISPLAY])
self.__WriteCommand([SET_MULTIPLEX_RATIO, 0x3F])
self.__WriteCommand([SET_MASTER_CONFIGURE, 0x8E])
self.__WriteCommand([POWER_SAVE_MODE, 0x0B]) # Disabled.
self.__WriteCommand([PHASE_1_2_PERIOD, 0x74]) # Default value.
self.__WriteCommand([CLOCK_DIVIDER, 0xD0]) # Default value.
self.__WriteCommand([SET_PRECHARGE_SPEED_A, 0x80])
self.__WriteCommand([SET_PRECHARGE_SPEED_B, 0x80])
self.__WriteCommand([SET_PRECHARGE_SPEED_C, 0x80])
self.__WriteCommand([SET_PRECHARGE_VOLTAGE, 0x3E]) # Default value.
self.__WriteCommand([SET_V_VOLTAGE, 0x3E]) # Default value.
self.__WriteCommand([MASTER_CURRENT_CONTROL, 0x0F])
self.__WriteCommand([SET_CONTRAST_A, 0xFF])
self.__WriteCommand([SET_CONTRAST_B, 0xFF])
self.__WriteCommand([SET_CONTRAST_C, 0xFF])
self.__WriteCommand([DISPLAY_ON])
return
def Remove(self):
gpio.cleanup()
self.spi.close()
return
def DrawPixel(self, x, y, c):
self.__WriteCommand([SET_COLUMN_ADDRESS, x, 0x5F, SET_ROW_ADDRESS, y, 0x3F])
self.__WriteData([(c >> 8) & 0xFF, c & 0xFF])
return
def DrawLine(self, x0, y0, x1, y1, c):
self.__WriteCommand([DRAW_LINE, x0 & 0xFF, y0 & 0xFF, x1 & 0xFF, y1 & 0xFF])
self.__WriteCommand([(c >> 11) << 1, (c >> 5) & 0x3F, (c << 1) & 0x3F])
return
# Bresenham's line algorithm.
def DrawLineBresenham(self, x0, y0, x1, y1, c):
dx = x1 - x0
if dx < 0:
dx = x0 - x1
sx = -1
if x0 < x1:
sx = 1
dy = y1 - y0
if dy < 0:
dy = y0 - y1
sy = -1
if y0 < y1:
sy = 1
err = -dy / 2
if dy < dx:
err = dx / 2
self.DrawPixel(x0, y0, c)
while x0 != x1 or y0 != y1:
e2 = err
if e2 > -dx:
err = err - dy
x0 = x0 + sx
if e2 < dy:
err = err + dx
y0 = y0 + sy
self.DrawPixel(x0, y0, c)
return
def DrawTriangle(self, x0, y0, x1, y1, x2, y2, c):
self.DrawLine(x0, y0, x1, y1, c)
self.DrawLine(x1, y1, x2, y2, c)
self.DrawLine(x0, y0, x2, y2, c)
return
def DrawRect(self, x0, y0, x1, y1, c, bg = 0):
self.__WriteCommand([DRAW_RECT, x0 & 0xFF, y0 & 0xFF, x1 & 0xFF, y1 & 0xFF])
self.__WriteCommand([(c >> 11) << 1, (c >> 5) & 0x3F, (c << 1) & 0x3F])
self.__WriteCommand([(bg >> 11) << 1, (bg >> 5) & 0x3F, (bg << 1) & 0x3F])
return
def DrawCircle(self, x0, y0, r0, c):
x = r0
y = 0
decision_over2 = 1 - x # Decision criterion divided by 2 evaluated at x = r, y = 0.
while y <= x:
self.DrawPixel( x + x0, y + y0, c) # Octant 1.
self.DrawPixel( y + x0, x + y0, c) # Octant 2.
self.DrawPixel(-x + x0, y + y0, c) # Octant 4.
self.DrawPixel(-y + x0, x + y0, c) # Octant 3.
self.DrawPixel(-x + x0, -y + y0, c) # Octant 5.
self.DrawPixel(-y + x0, -x + y0, c) # Octant 6.
self.DrawPixel( x + x0, -y + y0, c) # Octant 8.
self.DrawPixel( y + x0, -x + y0, c) # Octant 7.
y = y + 1
if decision_over2 <= 0:
decision_over2 = decision_over2 + 2 * y + 1 # Change in decision criterion for y -> y + 1.
else:
x = x - 1
decision_over2 = decision_over2 + 2 * (y - x) + 1 # Change for y -> y + 1, x -> x - 1.
return
def DrawChar(self, x, y, ch, c):
for i in xrange(0, 5, 1):
line = ascii[ord(ch) & 0x7F][i]
for j in xrange(0, 8, 1):
if line & 0x1:
self.DrawPixel(x + i, y + j, c)
line >>= 1
return
def DrawCharBg(self, x, y, ch, c, bg):
for i in xrange(0, 5, 1):
line = ascii[ord(ch) & 0x7F][i]
self.DrawPixel(x + i, y, bg)
y = y + 1
for j in xrange(0, 8, 1):
if line & 0x1:
self.DrawPixel(x + i, y + j, c)
else:
self.DrawPixel(x + i, y + j, bg)
line >>= 1
y = y + 8
self.DrawPixel(x + i, y, bg)
y = y - 9
return
def DrawString(self, x, y, str, c):
for i in str:
if x > 0x5F:
break
self.DrawChar(x, y, i, c)
x = x + 6
return
def DrawStringBg(self, x, y, str, c, bg):
self.DrawLine(x, y, x, y + 9, bg)
x = x + 1
for i in str:
if x > 0x5F:
break
self.DrawCharBg(x, y, i, c, bg)
self.DrawLine(x + 5, y, x + 5, y + 9, bg)
x = x + 6
return
def DrawFullScreenBitMap(self, data):
self.__WriteCommand([SET_COLUMN_ADDRESS, 0, 0x5F, SET_ROW_ADDRESS, 0, 0x3F]) # Set the address to 0, 0.
for y in xrange(0, len(data), 1):
d = list() # Build up a list of pixel data to render an entire row per data write command.
for x in xrange(0, len(data[y]), 1):
c = Color656(data[y][x][0], data[y][x][1], data[y][x][2])
d.append((c >> 8) & 0xFF)
d.append(c & 0xFF)
self.__WriteData(d)
return
def Blank(self):
self.EnableFillMode(True)
self.DrawRect(0x00, 0x00, 0x5F, 0x3F, COLOR_BLACK)
self.EnableFillMode(False)
return
def Clear(self):
self.__WriteCommand([CLEAR_WINDOW, 0x00, 0x00, 0x5F, 0x3F])
return
def TestEntireDisplay(self, enable):
if enable:
self.__WriteCommand([ENTIRE_DISPLAY_ON])
else:
self.__WriteCommand([ENTIRE_DISPLAY_OFF])
return
def EnableDisplay(self, enable):
if enable:
self.__WriteCommand([DISPLAY_ON])
else:
self.__WriteCommand([DISPLAY_OFF])
return
def EnableFillMode(self, enable):
if enable:
self.__WriteCommand([FILL_RECT, FILL_RECT_ENABLE])
else:
self.__WriteCommand([FILL_RECT, FILL_RECT_DISABLE])
return
def EnableLockMode(self, enable):
if enable:
self.__WriteCommand([LOCK_MODE, LOCK_MODE_ENABLE])
else:
self.__WriteCommand([LOCK_MODE, LOCK_MODE_DISABLE])
return
def SetScrollMode(self, horizontal, vertical):
self.EnableScrollMode(False)
self.__WriteCommand([CONTINUOUS_SCROLLING_SETUP, horizontal, 0x00, 0x3F, vertical, 0x00])
return
def EnableScrollMode(self, enable):
if enable:
self.__WriteCommand([ACTIVE_SCROLLING])
else:
self.__WriteCommand([DEACTIVE_SCROLLING])
return
def RectTest(self):
self.Clear()
time.sleep(0.01)
self.EnableFillMode(True)
for z in xrange(0, 10, 1):
self.Clear()
time.sleep(0.01)
for x in xrange(0, 6, 1):
for y in xrange(0, 4, 1):
r0 = random.randint(0, 255)
g0 = random.randint(0, 255)
b0 = random.randint(0, 255)
r1 = random.randint(0, 255)
g1 = random.randint(0, 255)
b1 = random.randint(0, 255)
self.DrawRect(1 + x * 16, 1 + y * 16, (x * 16) + 14, (y * 16) + 14, Color656(r0, g0, b0), Color656(r1, g1, b1))
time.sleep(1)
self.EnableFillMode(False)
for z in xrange(0, 10, 1):
self.Clear()
time.sleep(0.01)
for x in xrange(0, 6, 1):
for y in xrange(0, 4, 1):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
self.DrawRect(1 + x * 16, 1 + y * 16, (x * 16) + 14, (y * 16) + 14, Color656(r, g, b))
time.sleep(1)
self.EnableFillMode(True)
for z in xrange(0, 10, 1):
self.Clear()
time.sleep(0.01)
for x in xrange(0, 6, 1):
for y in xrange(0, 4, 1):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
self.DrawRect(1 + x * 16, 1 + y * 16, (x * 16) + 14, (y * 16) + 14, Color656(r, g, b), Color656(r, g, b))
time.sleep(1)
return
def LineTest(self):
self.Clear()
time.sleep(0.01)
for y in xrange(0, 32, 1):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
self.DrawLine(0x00, y * 2, 0x5F, y * 2, Color656(r, g, b))
time.sleep(10)
return
def LockTest(self):
self.Clear()
time.sleep(0.01)
self.DrawString(21, 16, "Lock Test", COLOR_WHITE)
self.EnableLockMode(True)
self.DrawString(0, 16, "Lock Test Failed", COLOR_RED)
time.sleep(10)
self.EnableLockMode(False)
return
def CharTest(self):
self.Clear()
time.sleep(0.01)
i = 32
for j in xrange(0, 6, 1):
for k in xrange(0, 16, 1):
self.DrawChar(k * 6, j * 8 + 4, chr(i), Color656(k * 16, 128, j * 36))
i = i + 1
time.sleep(10)
return
def ShapeTest(self):
self.Clear()
time.sleep(0.01)
self.DrawStringBg(18, 4, "Shape Test", COLOR_BLACK, COLOR_WHITE)
self.DrawCircle(16, 32, 10, COLOR_RED)
self.DrawTriangle(38, 42, 58, 42, 48, 22, COLOR_GREEN)
self.DrawRect(70, 22, 90, 42, COLOR_BLUE)
time.sleep(10)
return
def BitMapTest(self, data):
self.DrawFullScreenBitMap(data)
time.sleep(10)
return
def ScrollTest(self):
self.SetScrollMode(H_SCROLL_ENABLE, V_SCROLL_DISABLE)
self.EnableScrollMode(True)
time.sleep(10)
self.EnableScrollMode(False)
return
SSD1331_PIN_CS = 23
SSD1331_PIN_DC = 24
SSD1331_PIN_RST = 25
if __name__ == '__main__':
device = SSD1331(SSD1331_PIN_DC, SSD1331_PIN_RST, SSD1331_PIN_CS)
try:
data_balloon = UnpackDataFromBmp24File("balloon.bmp")
device.EnableDisplay(True)
device.LockTest()
device.LineTest()
device.RectTest()
device.ScrollTest()
device.ShapeTest()
device.CharTest()
device.BitMapTest(data_balloon)
device.EnableDisplay(False)
finally:
device.Remove()
@Wally-B

This comment has been minimized.

Copy link

Wally-B commented Nov 21, 2019

Fantastic Tutorial!!!
Big Thanks for this.
That OLED module was (to me) mis-labeled as SDA SCL so I though it was an I2C display.
I Struggled for 2 days and almost threw it out (as defective)
Your Photos and Code Examples gave me the Clue to keep going and attempt SPI hookup.
When I saw the Clock show up, I was the happiest you can imagine.
Thank you so much. Now my project will have a really Cool Color Display.
WallyB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.