Last active
March 12, 2020 13:18
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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