diff --git a/README.md b/README.md index 2f75eea..c93de22 100755 --- a/README.md +++ b/README.md @@ -20,8 +20,17 @@ This library runs on both the Raspberry Pi and BeagleBone Black. However some classes have not yet been ported or tested on both platforms, so refer to the table below for compatibility information. -Important - this is Gaugette version 2, which at present ONLY supports Python 3. -I have not yet tested it on the BeagleBone. +Here's the current compatibility matrix: + +| Class | RPi + Python 2.7 | RPi + Python 3 | BBB + Python 2.7 | BBB + Python 3 | +|:--------------|:----------------:|:----------------:|:----------------:|:----------------:| +| CapSwitch | yes | yes | yes | no | +| RgbLed | yes | yes | no | no | +| RotaryEncoder | yes | yes | yes | no | +| SSD1306 | yes | no | yes | no | +| SSD1351 | yes | yes | ?? | ?? | +| Switch | yes | yes | yes | no | +Important - this is Gaugette version 2, which at present ONLY supports Python 3. I have not yet tested it on the BeagleBone. Prerequisites for the Raspberry Pi ================================== diff --git a/gaugette/capswitch.py b/gaugette/capswitch.py index 0bbd2a8..0a28f59 100755 --- a/gaugette/capswitch.py +++ b/gaugette/capswitch.py @@ -1,4 +1,3 @@ - class CapSwitchwhich: def __init__(self, gpio, pin): self.gpio = gpio diff --git a/gaugette/gpio.py b/gaugette/gpio.py index 84fc5ee..d0d2d80 100755 --- a/gaugette/gpio.py +++ b/gaugette/gpio.py @@ -9,6 +9,7 @@ # On the BBB, we use Adafruit_BBIO.GPIO # #---------------------------------------------------------------------- +import gaugette import gaugette.platform class GPIO: @@ -49,7 +50,6 @@ def __init__(self): raise NotImplementedError("Platform is not supported.") #---------------------------------------------------------------------- - # Implement the setup call via the wiringpi API def wiringpi_setup(self, channel, direction, pull_up_down=None): self.gpio.pinMode(channel, direction) diff --git a/gaugette/rgbled.py b/gaugette/rgbled.py index 0f6fbfd..5cd88ef 100755 --- a/gaugette/rgbled.py +++ b/gaugette/rgbled.py @@ -39,7 +39,6 @@ def fade(self, red, green, blue, delay=500, step=5): g = self.green + (green-self.green) * f wiringpi.softPwmWrite(self.g_pin, int(g)) b = self.blue + (blue -self.blue) * f - wiringpi.softPwmWrite(self.b_pin, int(b)) wiringpi.delay(step) self.red = red diff --git a/gaugette/spi.py b/gaugette/spi.py index b6023eb..e343373 100755 --- a/gaugette/spi.py +++ b/gaugette/spi.py @@ -14,7 +14,6 @@ class SPI: def __init__(self, bus, device): - if gaugette.platform.isRaspberryPi: import spidev self.spi = spidev.SpiDev() diff --git a/gaugette/ssd1306.py b/gaugette/ssd1306.py index 35db5eb..ade1004 100755 --- a/gaugette/ssd1306.py +++ b/gaugette/ssd1306.py @@ -54,6 +54,7 @@ import gaugette.gpio import gaugette.spi import gaugette.font5x8 +import gaugette.platform import time import sys diff --git a/gaugette/ssd1351.py b/gaugette/ssd1351.py new file mode 100755 index 0000000..8616f76 --- /dev/null +++ b/gaugette/ssd1351.py @@ -0,0 +1,383 @@ +#---------------------------------------------------------------------- +# ssd1351.py from https://github.com/boxysean/py-gaugette +# ported by boxysean +# +# This library works with +# Adafruit's 128x128 SPI RGB OLED https://www.adafruit.com/products/1431 +# +# The code is based heavily on Adafruit's Arduino library +# https://github.com/adafruit/Adafruit-SSD1351-library +# written by Limor Fried/Ladyada for Adafruit Industries. +# +# Some important things to know about this device and SPI: +# +# SPI and GPIO calls are made through an abstraction library that calls +# the appropriate library for the platform. +# For the RaspberryPi: +# wiring2 +# spidev +# +# Presently untested / not supported for BBBlack +# +#---------------------------------------------------------------------- + +import gaugette.gpio +import gaugette.spi +import gaugette.platform +import gaugette.font5x8 +import time +import sys + +class SSD1351: + + # Class constants are externally accessible as gaugette.ssd1351.SSD1351.CONST + # or my_instance.CONST + + DELAYS_HWFILL = 3 + DELAYS_HWLINE = 1 + + # SSD1351 Commands + CMD_SETCOLUMN = 0x15 + CMD_SETROW = 0x75 + CMD_WRITERAM = 0x5C + CMD_READRAM = 0x5D + CMD_SETREMAP = 0xA0 + CMD_STARTLINE = 0xA1 + CMD_DISPLAYOFFSET = 0xA2 + CMD_DISPLAYALLOFF = 0xA4 + CMD_DISPLAYALLON = 0xA5 + CMD_NORMALDISPLAY = 0xA6 + CMD_INVERTDISPLAY = 0xA7 + CMD_FUNCTIONSELECT = 0xAB + CMD_DISPLAYOFF = 0xAE + CMD_DISPLAYON = 0xAF + CMD_PRECHARGE = 0xB1 + CMD_DISPLAYENHANCE = 0xB2 + CMD_CLOCKDIV = 0xB3 + CMD_SETVSL = 0xB4 + CMD_SETGPIO = 0xB5 + CMD_PRECHARGE2 = 0xB6 + CMD_SETGRAY = 0xB8 + CMD_USELUT = 0xB9 + CMD_PRECHARGELEVEL = 0xBB + CMD_VCOMH = 0xBE + CMD_CONTRASTABC = 0xC1 + CMD_CONTRASTMASTER = 0xC7 + CMD_MUXRATIO = 0xCA + CMD_COMMANDLOCK = 0xFD + CMD_HORIZSCROLL = 0x96 + CMD_STOPSCROLL = 0x9E + CMD_STARTSCROLL = 0x9F + + SSD1351WIDTH = 128 + SSD1351HEIGHT = 128 + + # Device name will be /dev/spidev-{bus}.{device} + # dc_pin is the data/commmand pin. This line is HIGH for data, LOW for command. + # We will keep d/c low and bump it high only for commands with data + # reset is normally HIGH, and pulled LOW to reset the display + + def __init__(self, bus=0, device=0, dc_pin="P9_15", reset_pin="P9_13", buffer_rows=128, buffer_cols=128, rows=32, cols=128, debug=False): + self.cols = cols + self.rows = rows + self.debug = debug + self.buffer_rows = buffer_rows + self.mem_bytes = self.buffer_rows * self.cols / 8 # total bytes in SSD1306 display ram + self.dc_pin = dc_pin + self.reset_pin = reset_pin + self.spi = gaugette.spi.SPI(bus, device) + self.spi.mode = 3 # necessary! + self.gpio = gaugette.gpio.GPIO() + self.gpio.setup(self.reset_pin, self.gpio.OUT) + self.gpio.output(self.reset_pin, self.gpio.HIGH) + self.gpio.setup(self.dc_pin, self.gpio.OUT) + self.gpio.output(self.dc_pin, self.gpio.LOW) + self.font = gaugette.font5x8.Font5x8 + self.col_offset = 0 + self.bitmap = self.SimpleBitmap(buffer_cols, buffer_rows, self.debug) + self.flipped = False + + def reset(self): + self.gpio.output(self.reset_pin, self.gpio.LOW) + time.sleep(0.010) # 10ms + self.gpio.output(self.reset_pin, self.gpio.HIGH) + + def command(self, cmd, cmddata=None): + # already low + #self.gpio.output(self.dc_pin, self.gpio.LOW) + + if type(cmd) == list: + self.spi.writebytes(cmd) + else: + self.spi.writebytes([cmd]) + + if cmddata != None: + if type(cmddata) == list: + self.data(cmddata) + else: + self.data([cmddata]) + + def data(self, bytes): + self.gpio.output(self.dc_pin, self.gpio.HIGH) + # chunk data to work around 255 byte limitation in adafruit implementation of writebytes + # revisit - change to 1024 when Adafruit_BBIO is fixed. + max_xfer = 255 if gaugette.platform.isBeagleBoneBlack else 1024 + start = 0 + remaining = len(bytes) + while remaining>0: + count = remaining if remaining <= max_xfer else max_xfer + remaining -= count + self.spi.writebytes(bytes[start:start+count]) + start += count + self.gpio.output(self.dc_pin, self.gpio.LOW) + + def begin(self): + time.sleep(0.001) # 1ms + self.reset() + + self.command(self.CMD_COMMANDLOCK, 0x12) # Unlock OLED driver IC MCU interface from entering command + self.command(self.CMD_COMMANDLOCK, 0xB1) # Command A2,B1,B3,BB,BE,C1 accessible if in unlock state + self.command(self.CMD_DISPLAYOFF) + self.command([self.CMD_CLOCKDIV, 0xF1]) # 7:4 = Oscillator Frequency, 3:0 = CLK Div Ratio (A[3:0]+1 = 1..16) + self.command(self.CMD_MUXRATIO, 127) + self.command(self.CMD_SETREMAP, 0x74) + self.command(self.CMD_SETCOLUMN, [0x00, 0x7F]) + self.command(self.CMD_SETROW, [0x00, 0x7F]) + self.command(self.CMD_STARTLINE, 0x00) + self.command(self.CMD_DISPLAYOFFSET, 0x00) + self.command(self.CMD_SETGPIO, 0x00) + self.command(self.CMD_FUNCTIONSELECT, 0x01) + self.command([self.CMD_PRECHARGE, 0x32]) + self.command([self.CMD_VCOMH, 0x05]) + self.command(self.CMD_NORMALDISPLAY) + self.command(self.CMD_CONTRASTABC, [0xC8, 0x80, 0xC8]) + self.command(self.CMD_CONTRASTMASTER, 0x0F) + self.command(self.CMD_SETVSL, [0xA0, 0xB5, 0x55]) + self.command(self.CMD_PRECHARGE2, 0x01) + self.command(self.CMD_DISPLAYON) + + def clear_display(self): + self.bitmap.clear() + + def invert_display(self): + self.command(self.CMD_INVERTDISPLAY) + + def flip_display(self, flipped=True): + self.flipped = flipped + if flipped: + self.command(self.COM_SCAN_INC) + self.command(self.SEG_REMAP | 0x00) + else: + self.command(self.COM_SCAN_DEC) + self.command(self.SET_COM_PINS, 0x02) + + def normal_display(self): + self.command(self.CMD_NORMALDISPLAY) + + def set_contrast(self, contrast=0x7f): + self.command(self.SET_CONTRAST, contrast) + + def goTo(self, x, y): + if x >= self.SSD1351WIDTH or y >= self.SSD1351HEIGHT: + return + + # set x and y coordinate + self.command(self.CMD_SETCOLUMN, [x, self.SSD1351WIDTH-1]) + self.command(self.CMD_SETROW, [y, self.SSD1351HEIGHT-1]) + self.command(self.CMD_WRITERAM) + + def scale(self, x, inLow, inHigh, outLow, outHigh): + return ((x - inLow) / float(inHigh) * outHigh) + outLow + + def encode_color(self, color): + red = (color >> 16) & 0xFF + green = (color >> 8) & 0xFF + blue = color & 0xFF + + redScaled = int(self.scale(red, 0, 0xFF, 0, 0x1F)) + greenScaled = int(self.scale(green, 0, 0xFF, 0, 0x3F)) + blueScaled = int(self.scale(blue, 0, 0xFF, 0, 0x1F)) + + # print color, redScaled, greenScaled, blueScaled + + return (((redScaled << 6) | greenScaled) << 5) | blueScaled + + def color565(self, r, g, b): # ints + c = r >> 3 + c <<= 6 + c |= g >> 2 + c <<= 5 + c |= b >> 3 + return c + + def fillScreen(self, fillcolor): # int + self.fillRect(0, 0, self.SSD1351WIDTH, self.SSD1351HEIGHT, fillcolor) + + def fillRect(self, x, y, w, h, fillcolor): + # Bounds check + if x >= self.SSD1351WIDTH or y >= self.SSD1351HEIGHT: + return + + if y+h > self.SSD1351HEIGHT: + h = self.SSD1351HEIGHT - y - 1 + + if x+w > self.SSD1351WIDTH: + w = self.SSD1351WIDTH - x - 1 + + # set location + self.command(self.CMD_SETCOLUMN, [x, x+w-1]) + self.command(self.CMD_SETROW, [y, y-h-1]) + # fill! + self.command(self.CMD_WRITERAM) + + fillcolor = self.encode_color(fillcolor) + + self.data([fillcolor >> 8, fillcolor] * (w*h)) + + def drawPixel(self, x, y, color): + if x >= self.SSD1351WIDTH or y >= self.SSD1351HEIGHT: + return + + if x < 0 or y < 0: + return + + color = self.encode_color(color) + + # set location + self.goTo(x, y) + self.data([color >> 8, color]) + + def drawBitmap(self, x, y, bitmap): + h = len(bitmap) + w = len(bitmap[0]) + + self.command(self.CMD_SETCOLUMN, [x, w]) + self.command(self.CMD_SETROW, [y, h]) + self.command(self.CMD_WRITERAM) + + pixels = [] + + for r in range(y, y+h): + if len(pixels) + 4*w >= 1024: + if self.debug: + print("pixels!", pixels) + self.data(pixels) + pixels = [] + + for x in bitmap[r]: + pixels = pixels + [(x >> 8) & 0xFF, x & 0xFF] + + if self.debug: + print("pixels!", pixels) + self.data(pixels) + + # Diagnostic print of the memory buffer to stdout + def dump_buffer(self): + self.bitmap.dump() + + def draw_text(self, x, y, string, color=0xFFFFFF): + font_bytes = self.font.bytes + font_rows = self.font.rows + font_cols = self.font.cols + + for c in string: + p = ord(c) * font_cols + for col in range(font_cols): + mask = font_bytes[p] + p += 1 + for row in range(8): + if (mask & 1) != 0: + # self.drawPixel(x, y+row, color) + self.bitmap.draw_pixel(x, y+row, self.encode_color(color)) + else: + # self.drawPixel(x, y+row, 0) + self.bitmap.draw_pixel(x, y+row, 0) + mask >>= 1 + x += 1 + + def draw_text2(self, x, y, string, color=0xFFFFFF, size=2, space=1): + font_bytes = self.font.bytes + font_rows = self.font.rows + font_cols = self.font.cols + for c in string: + p = ord(c) * font_cols + for col in range(0,font_cols): + mask = font_bytes[p] + p+=1 + py = y + for row in range(0,8): + for sy in range(0,size): + px = x + for sx in range(0,size): + if mask & 1: + self.bitmap.draw_pixel(px, py, self.encode_color(color)) + else: + self.bitmap.draw_pixel(px, py, 0) + px += 1 + py += 1 + mask >>= 1 + x += size + x += space + + def clear_block(self, x0,y0,dx,dy): + self.bitmap.clear_block(x0,y0,dx,dy) + + def draw_text3(self, x, y, string, font): + return self.bitmap.draw_text(x,y,string,font) + + def text_width(self, string, font): + return self.bitmap.text_width(string, font) + + class SimpleBitmap: + def __init__(self, cols, rows, debug): + self.rows = rows + self.cols = cols + self.debug = debug + if self.debug: + print(rows, cols) + self.data = [([0] * self.cols) for i in range(self.rows)] + + def clear(self): + for r in range(len(self.data)): + for c in range(len(self.data[r])): + self.data[r][c] = 0 + + # Diagnostic print of the memory buffer to stdout + def dump(self): + for row in self.data: + for col in row: + sys.stdout.write('X' if col else '.') + sys.stdout.write('\n') + + def draw_pixel(self, x, y, color): + if (x<0 or x>=self.cols or y<0 or y>=self.rows): + return + + self.data[y][x] = color + + def clear_block(self, x0,y0,dx,dy): + for x in range(x0,x0+dx): + for y in range(y0,y0+dy): + self.draw_pixel(x,y,0) + + def display(self, ssd1351): + ssd1351.command(ssd1351.CMD_SETCOLUMN, [0, ssd1351.SSD1351WIDTH]) + ssd1351.command(ssd1351.CMD_SETROW, [0, ssd1351.SSD1351HEIGHT]) + ssd1351.command(ssd1351.CMD_WRITERAM) + + pixels = [] + + ## something is wrong in here... not sure what! + + for r in range(ssd1351.SSD1351WIDTH): + if len(pixels) + ssd1351.SSD1351HEIGHT >= 513: # dump it out! + if self.debug: + print("pixels!", pixels) + ssd1351.data(pixels) + pixels = [] + + pixels = pixels + self.data[r] + + if self.debug: + print("pixels!", pixels) + ssd1351.data(pixels) diff --git a/gaugette/switch.py b/gaugette/switch.py index b079be6..cc8dbfc 100755 --- a/gaugette/switch.py +++ b/gaugette/switch.py @@ -5,6 +5,12 @@ class Switch: def __init__(self, gpio, pin, pull_up=True): self.gpio = gpio self.pin = pin + self.gpio = wiringpi.GPIO(wiringpi.GPIO.WPI_MODE_PINS) + self.gpio.pinMode(self.pin, self.gpio.INPUT) + if self.pullUp: + self.gpio.pullUpDnControl(self.pin, self.gpio.PUD_UP) + else: + self.gpio.pullUpDnControl(self.pin, self.gpio.PUD_DOWN) self.pull_up = pull_up pull_up_mode = gpio.PUD_UP if pull_up else gpio.PUD_DOWN self.gpio.setup(self.pin, self.gpio.IN, pull_up_mode) diff --git a/samples/capswitch_test.py b/samples/capswitch_test.py index de66446..5b22ba9 100755 --- a/samples/capswitch_test.py +++ b/samples/capswitch_test.py @@ -1,6 +1,7 @@ import gaugette.platform import gaugette.gpio import gaugette.capswitch +import gaugette.platform import gaugette.rgbled if gaugette.platform.isRaspberryPi: diff --git a/samples/rotary_test.py b/samples/rotary_test.py index 95c7d09..9a3085d 100755 --- a/samples/rotary_test.py +++ b/samples/rotary_test.py @@ -16,6 +16,7 @@ import gaugette.gpio import gaugette.rotary_encoder import gaugette.switch +import gaugette.pltform import math if gaugette.platform.isRaspberryPi: diff --git a/samples/ssd1306_test.py b/samples/ssd1306_test.py index f85096f..358e907 100755 --- a/samples/ssd1306_test.py +++ b/samples/ssd1306_test.py @@ -1,9 +1,13 @@ +#!/usr/bin/python3 + +import gaugette.ssd1306 +import gaugette.platform import gaugette.ssd1306 import gaugette.platform import gaugette.gpio import time -ROWS = 32 +ROWS = 64 if gaugette.platform.isRaspberryPi: RESET_PIN = 1 diff --git a/samples/ssd1351_test.py b/samples/ssd1351_test.py new file mode 100755 index 0000000..6e2bdbc --- /dev/null +++ b/samples/ssd1351_test.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 + +import gaugette.ssd1351 +import gaugette.platform +import gaugette.ssd1351 +import time +import sys +from random import randint + +from PIL import Image + +ROWS = 96 +COLS = 128 + +if gaugette.platform.isRaspberryPi: + RESET_PIN = 15 + DC_PIN = 16 +else: # beagebone (have not implemented at all) + pass +# RESET_PIN = "P9_15" +# DC_PIN = "P9_13" + +print("init") +led = gaugette.ssd1351.SSD1351(reset_pin=RESET_PIN, dc_pin=DC_PIN, rows=ROWS, cols=COLS) +print("begin") +led.begin() + +#led.fillScreen(led.encode_color(int(randint(0, 0xFFFFFF)))) +led.fillScreen(0) +# led.drawPixel(0, 0, led.encode_color(0xFF0000)) +# led.drawPixel(50, 50, led.encode_color(0x00FFFF)) + +R = led.encode_color(0xFF0000) +G = led.encode_color(0x00FF00) +B = led.encode_color(0x0000FF) +# R = G +# G = R +# B = R + +# led.drawBitmap(0, 0, [[R for i in range(64)]]) + +def drawImage(imageName="test.png"): + image = Image.open(imageName) + rgb_image = image.convert("RGB") + + imageWidth, imageHeight = image.size + + imagePixels = [] + for y in range(imageHeight): + row = [] + imagePixels.append(row) + for x in range(imageWidth): + red, green, blue = image.getpixel((x, y)) + row.append(led.encode_color((red << 16) | (green << 8) | blue)) + + led.drawBitmap(0, 0, imagePixels) + +def drawCircle(x, y, r): + pixels = [] + + for yy in range(20): + row = [] + pixels.append(row) + for xx in range(20): + if ((x - xx) ** 2 + (y - yy) ** 2) - (r ** 2) < 2: + row.append(led.encode_color(0xFFFFFF)) + else: + row.append(led.encode_color(0x0)) + + led.drawBitmap(0, 0, pixels) + +drawCircle(10, 10, 10) + + +offset = 0 # flips between 0 and 32 for double buffering diff --git a/test.png b/test.png new file mode 100644 index 0000000..a033ef4 Binary files /dev/null and b/test.png differ