Skip to content

Instantly share code, notes, and snippets.

@TheRayTracer
Last active March 12, 2020 13:18
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save TheRayTracer/dd12c498e3ecb9b8b47f to your computer and use it in GitHub Desktop.
Save TheRayTracer/dd12c498e3ecb9b8b47f to your computer and use it in GitHub Desktop.
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
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