mirror of
https://github.com/evilsocket/pwnagotchi.git
synced 2026-01-23 02:24:15 +00:00
Add support for SSD1306 i2c Oled Display 128*64
- Tested for over a year on a pi-0 w
- Driver utilizes RPI.GPIO and smbus
- Adapted to work without any of the Adafruit dependancies
- Based on Adafruit's implementation of the SSD1306
Signed-off-by: MD Raqibul Islam <hello@eraj.dev>
This commit is contained in:
parent
ef0f35da0a
commit
be089667e2
7 changed files with 338 additions and 2 deletions
|
|
@ -49,6 +49,9 @@ class Display(View):
|
|||
def is_oledhat(self):
|
||||
return self._implementation.name == 'oledhat'
|
||||
|
||||
def is_adafruitssd1306i2c(self):
|
||||
return self._implementation.name == 'adafruitssd1306i2c'
|
||||
|
||||
def is_lcdhat(self):
|
||||
return self._implementation.name == 'lcdhat'
|
||||
|
||||
|
|
@ -69,7 +72,7 @@ class Display(View):
|
|||
|
||||
def is_waveshare213bc(self):
|
||||
return self._implementation.name == 'waveshare213bc'
|
||||
|
||||
|
||||
def is_waveshare213inb_v4(self):
|
||||
return self._implementation.name == 'waveshare213inb_v4'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from pwnagotchi.ui.hw.inky import Inky
|
||||
from pwnagotchi.ui.hw.papirus import Papirus
|
||||
from pwnagotchi.ui.hw.oledhat import OledHat
|
||||
from pwnagotchi.ui.hw.adafruitssd1306i2c import AdafruitSSD1306i2c
|
||||
from pwnagotchi.ui.hw.lcdhat import LcdHat
|
||||
from pwnagotchi.ui.hw.dfrobot1 import DFRobotV1
|
||||
from pwnagotchi.ui.hw.dfrobot2 import DFRobotV2
|
||||
|
|
@ -28,6 +29,9 @@ def display_for(config):
|
|||
if config['ui']['display']['type'] == 'oledhat':
|
||||
return OledHat(config)
|
||||
|
||||
if config['ui']['display']['type'] == 'adafruitssd1306i2c':
|
||||
return AdafruitSSD1306i2c(config)
|
||||
|
||||
if config['ui']['display']['type'] == 'lcdhat':
|
||||
return LcdHat(config)
|
||||
|
||||
|
|
|
|||
46
pwnagotchi/ui/hw/adafruitssd1306i2c.py
Normal file
46
pwnagotchi/ui/hw/adafruitssd1306i2c.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import logging
|
||||
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.ui.hw.base import DisplayImpl
|
||||
|
||||
|
||||
class AdafruitSSD1306i2c(DisplayImpl):
|
||||
def __init__(self, config):
|
||||
super(AdafruitSSD1306i2c, self).__init__(config, 'adafruitssd1306i2c')
|
||||
self._display = None
|
||||
|
||||
def layout(self):
|
||||
fonts.setup(8, 8, 8, 8, 25, 9)
|
||||
self._layout['width'] = 128
|
||||
self._layout['height'] = 64
|
||||
self._layout['face'] = (0, 32)
|
||||
self._layout['name'] = (0, 10)
|
||||
self._layout['channel'] = (0, 0)
|
||||
self._layout['aps'] = (25, 0)
|
||||
self._layout['uptime'] = (65, 0)
|
||||
self._layout['line1'] = [0, 9, 128, 9]
|
||||
self._layout['line2'] = [0, 53, 128, 53]
|
||||
self._layout['friend_face'] = (0, 41)
|
||||
self._layout['friend_name'] = (40, 43)
|
||||
self._layout['shakes'] = (0, 53)
|
||||
self._layout['mode'] = (103, 10)
|
||||
self._layout['status'] = {
|
||||
'pos': (30, 18),
|
||||
'font': fonts.status_font(fonts.Small),
|
||||
'max': 18
|
||||
}
|
||||
return self._layout
|
||||
|
||||
def initialize(self):
|
||||
logging.info("initializing adafruitssd1306i2c display")
|
||||
from pwnagotchi.ui.hw.libs.adafruit.adafruitssd1306i2c.epd import EPD
|
||||
logging.info("EPD loaded")
|
||||
self._display = EPD()
|
||||
self._display.init()
|
||||
self._display.Clear()
|
||||
|
||||
def render(self, canvas):
|
||||
self._display.display(canvas)
|
||||
|
||||
def clear(self):
|
||||
self._display.clear()
|
||||
252
pwnagotchi/ui/hw/libs/adafruit/adafruitssd1306i2c/SSD1306.py
Normal file
252
pwnagotchi/ui/hw/libs/adafruit/adafruitssd1306i2c/SSD1306.py
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# Adaption of the Python driver for 0.96" 128*64 i2c OLED displays,
|
||||
# based on the Adafruit driver without using any Adafruit dependency libraries,
|
||||
# instead it's using the usual smbus and RPi.GPIO
|
||||
# Adaptation made by: MD Raqibul Islam (github.com/erajtob)
|
||||
#
|
||||
# --
|
||||
# Copyright (c) 2014 Adafruit Industries
|
||||
# Author: Tony DiCola
|
||||
#
|
||||
# ******************************************************************************/
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
|
||||
import logging
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
from smbus import SMBus
|
||||
|
||||
width = 128 #LCD width
|
||||
height = 64 #LCD height
|
||||
|
||||
# Constants
|
||||
SSD1306_I2C_ADDRESS = 0x3C # 011110+SA0+RW - 0x3C or 0x3D
|
||||
SSD1306_SETCONTRAST = 0x81
|
||||
SSD1306_DISPLAYALLON_RESUME = 0xA4
|
||||
SSD1306_DISPLAYALLON = 0xA5
|
||||
SSD1306_NORMALDISPLAY = 0xA6
|
||||
SSD1306_INVERTDISPLAY = 0xA7
|
||||
SSD1306_DISPLAYOFF = 0xAE
|
||||
SSD1306_DISPLAYON = 0xAF
|
||||
SSD1306_SETDISPLAYOFFSET = 0xD3
|
||||
SSD1306_SETCOMPINS = 0xDA
|
||||
SSD1306_SETVCOMDETECT = 0xDB
|
||||
SSD1306_SETDISPLAYCLOCKDIV = 0xD5
|
||||
SSD1306_SETPRECHARGE = 0xD9
|
||||
SSD1306_SETMULTIPLEX = 0xA8
|
||||
SSD1306_SETLOWCOLUMN = 0x00
|
||||
SSD1306_SETHIGHCOLUMN = 0x10
|
||||
SSD1306_SETSTARTLINE = 0x40
|
||||
SSD1306_MEMORYMODE = 0x20
|
||||
SSD1306_COLUMNADDR = 0x21
|
||||
SSD1306_PAGEADDR = 0x22
|
||||
SSD1306_COMSCANINC = 0xC0
|
||||
SSD1306_COMSCANDEC = 0xC8
|
||||
SSD1306_SEGREMAP = 0xA0
|
||||
SSD1306_CHARGEPUMP = 0x8D
|
||||
SSD1306_EXTERNALVCC = 0x1
|
||||
SSD1306_SWITCHCAPVCC = 0x2
|
||||
|
||||
# Scrolling constants
|
||||
SSD1306_ACTIVATE_SCROLL = 0x2F
|
||||
SSD1306_DEACTIVATE_SCROLL = 0x2E
|
||||
SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3
|
||||
SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26
|
||||
SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27
|
||||
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
|
||||
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
|
||||
|
||||
class SSD1306Base(object):
|
||||
def __init__(self, width, height, rst, i2c_bus=None, i2c_address=SSD1306_I2C_ADDRESS, i2c=None):
|
||||
self._i2c = None
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._pages = height//8
|
||||
self._buffer = [0]*(width*self._pages)
|
||||
if i2c_bus is None:
|
||||
self._i2c = SMBus(1)
|
||||
else:
|
||||
self._i2c = SMBus(i2c_bus)
|
||||
self._gpio = GPIO
|
||||
self._gpio.setmode(GPIO.BCM)
|
||||
self._gpio.setwarnings(False)
|
||||
# Setup reset pin.
|
||||
self._rst = rst
|
||||
if not self._rst is None:
|
||||
self._gpio.setup(self._rst, GPIO.OUT)
|
||||
|
||||
def _initialize(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def write8(self, register, value):
|
||||
"""Write an 8-bit value to the specified register."""
|
||||
value = value & 0xFF
|
||||
self._i2c.write_byte_data(SSD1306_I2C_ADDRESS, register, value)
|
||||
|
||||
def command(self, c):
|
||||
"""Send command byte to display."""
|
||||
# I2C write.
|
||||
control = 0x00 # Co = 0, DC = 0
|
||||
self.write8(control, c)
|
||||
time.sleep(0.01)
|
||||
|
||||
def data(self, c):
|
||||
"""Send byte of data to display."""
|
||||
# I2C write.
|
||||
control = 0x40 # Co = 0, DC = 0
|
||||
self.write8(control, c)
|
||||
time.sleep(0.01)
|
||||
|
||||
def begin(self, vccstate=SSD1306_SWITCHCAPVCC):
|
||||
"""Initialize display."""
|
||||
# Save vcc state.
|
||||
self._vccstate = vccstate
|
||||
# Reset and initialize display.
|
||||
self.reset()
|
||||
self._initialize()
|
||||
# Turn on the display.
|
||||
self.command(SSD1306_DISPLAYON)
|
||||
|
||||
def reset(self):
|
||||
"""Reset the display."""
|
||||
if self._rst is None:
|
||||
return
|
||||
# Set reset high for a millisecond.
|
||||
GPIO.output(self._rst, GPIO.HIGH)
|
||||
time.sleep(0.001)
|
||||
# Set reset low for 10 milliseconds.
|
||||
GPIO.output(self._rst, GPIO.LOW)
|
||||
time.sleep(0.010)
|
||||
# Set reset high again.
|
||||
GPIO.output(self._rst, GPIO.HIGH)
|
||||
|
||||
def writeList(self, register, data):
|
||||
"""Write bytes to the specified register."""
|
||||
self._i2c.write_i2c_block_data(SSD1306_I2C_ADDRESS, register, data)
|
||||
|
||||
def display(self):
|
||||
"""Write display buffer to physical display."""
|
||||
self.command(SSD1306_COLUMNADDR)
|
||||
self.command(0) # Column start address. (0 = reset)
|
||||
self.command(self.width-1) # Column end address.
|
||||
self.command(SSD1306_PAGEADDR)
|
||||
self.command(0) # Page start address. (0 = reset)
|
||||
self.command(self._pages-1) # Page end address.
|
||||
# Write buffer data.
|
||||
for i in range(0, len(self._buffer), 16):
|
||||
control = 0x40 # Co = 0, DC = 0
|
||||
# data = bytearray(len(self._buffer[i:i+16]) + 1)
|
||||
# data[0] = control & 0xFF
|
||||
# data[1:] = self._buffer[i:i+16]
|
||||
self.writeList(control, self._buffer[i:i+16])
|
||||
|
||||
def image(self, image):
|
||||
"""Set buffer to value of Python Imaging Library image. The image should
|
||||
be in 1 bit mode and a size equal to the display size.
|
||||
"""
|
||||
if image.mode != '1':
|
||||
raise ValueError('Image must be in mode 1.')
|
||||
imwidth, imheight = image.size
|
||||
if imwidth != self.width or imheight != self.height:
|
||||
raise ValueError('Image must be same dimensions as display ({0}x{1}).' \
|
||||
.format(self.width, self.height))
|
||||
# Grab all the pixels from the image, faster than getpixel.
|
||||
pix = image.load()
|
||||
# Iterate through the memory pages
|
||||
index = 0
|
||||
for page in range(self._pages):
|
||||
# Iterate through all x axis columns.
|
||||
for x in range(self.width):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
bits = 0
|
||||
# Don't use range here as it's a bit slow
|
||||
for bit in [0, 1, 2, 3, 4, 5, 6, 7]:
|
||||
bits = bits << 1
|
||||
bits |= 0 if pix[(x, page*8+7-bit)] == 0 else 1
|
||||
# Update buffer byte and increment to next byte.
|
||||
self._buffer[index] = bits
|
||||
index += 1
|
||||
|
||||
def clear(self):
|
||||
"""Clear contents of image buffer."""
|
||||
self._buffer = [0]*(self.width*self._pages)
|
||||
|
||||
def set_contrast(self, contrast):
|
||||
"""Sets the contrast of the display. Contrast should be a value between
|
||||
0 and 255."""
|
||||
if contrast < 0 or contrast > 255:
|
||||
raise ValueError('Contrast must be a value from 0 to 255 (inclusive).')
|
||||
self.command(SSD1306_SETCONTRAST)
|
||||
self.command(contrast)
|
||||
|
||||
def dim(self, dim):
|
||||
"""Adjusts contrast to dim the display if dim is True, otherwise sets the
|
||||
contrast to normal brightness if dim is False.
|
||||
"""
|
||||
# Assume dim display.
|
||||
contrast = 0
|
||||
# Adjust contrast based on VCC if not dimming.
|
||||
if not dim:
|
||||
if self._vccstate == SSD1306_EXTERNALVCC:
|
||||
contrast = 0x9F
|
||||
else:
|
||||
contrast = 0xCF
|
||||
self.set_contrast(contrast)
|
||||
|
||||
class SSD1306_128_64(SSD1306Base):
|
||||
def __init__(self, rst, i2c_bus=None, i2c_address=SSD1306_I2C_ADDRESS,
|
||||
i2c=None):
|
||||
# Call base class constructor.
|
||||
super(SSD1306_128_64, self).__init__(128, 64, rst, i2c_bus, i2c_address, i2c)
|
||||
|
||||
def _initialize(self):
|
||||
# 128x64 pixel specific initialization.
|
||||
self.command(SSD1306_DISPLAYOFF) # 0xAE
|
||||
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
|
||||
self.command(0x80) # the suggested ratio 0x80
|
||||
self.command(SSD1306_SETMULTIPLEX) # 0xA8
|
||||
self.command(0x3F)
|
||||
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
|
||||
self.command(0x0) # no offset
|
||||
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
|
||||
self.command(SSD1306_CHARGEPUMP) # 0x8D
|
||||
if self._vccstate == SSD1306_EXTERNALVCC:
|
||||
self.command(0x10)
|
||||
else:
|
||||
self.command(0x14)
|
||||
self.command(SSD1306_MEMORYMODE) # 0x20
|
||||
self.command(0x00) # 0x0 act like ks0108
|
||||
self.command(SSD1306_SEGREMAP | 0x1)
|
||||
self.command(SSD1306_COMSCANDEC)
|
||||
self.command(SSD1306_SETCOMPINS) # 0xDA
|
||||
self.command(0x12)
|
||||
self.command(SSD1306_SETCONTRAST) # 0x81
|
||||
if self._vccstate == SSD1306_EXTERNALVCC:
|
||||
self.command(0x9F)
|
||||
else:
|
||||
self.command(0xCF)
|
||||
self.command(SSD1306_SETPRECHARGE) # 0xd9
|
||||
if self._vccstate == SSD1306_EXTERNALVCC:
|
||||
self.command(0x22)
|
||||
else:
|
||||
self.command(0xF1)
|
||||
self.command(SSD1306_SETVCOMDETECT) # 0xDB
|
||||
self.command(0x40)
|
||||
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
|
||||
self.command(SSD1306_NORMALDISPLAY) # 0xA6
|
||||
28
pwnagotchi/ui/hw/libs/adafruit/adafruitssd1306i2c/epd.py
Normal file
28
pwnagotchi/ui/hw/libs/adafruit/adafruitssd1306i2c/epd.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from . import SSD1306
|
||||
|
||||
RST = 24
|
||||
|
||||
disp = SSD1306.SSD1306_128_64(rst=RST, i2c_bus=1, i2c_address=0x3C)
|
||||
|
||||
class EPD(object):
|
||||
|
||||
def __init__(self, rst=RST, i2c_bus=None, i2c_address=0x3c):
|
||||
self.width = 128
|
||||
self.height = 64
|
||||
if i2c_bus is None:
|
||||
self.i2c_bus = 1
|
||||
else:
|
||||
self.i2c_bus = i2c_bus
|
||||
self.i2c_address = i2c_address
|
||||
self.disp = disp
|
||||
|
||||
def init(self):
|
||||
self.disp.begin()
|
||||
|
||||
def Clear(self):
|
||||
self.disp.clear()
|
||||
self.disp.display()
|
||||
|
||||
def display(self, image):
|
||||
self.disp.image(image)
|
||||
self.disp.display()
|
||||
|
|
@ -242,6 +242,9 @@ def load_config(args):
|
|||
elif config['ui']['display']['type'] in ('oledhat',):
|
||||
config['ui']['display']['type'] = 'oledhat'
|
||||
|
||||
elif config['ui']['display']['type'] in ('adafruitssd1306i2c',):
|
||||
config['ui']['display']['type'] = 'adafruitssd1306i2c'
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'):
|
||||
config['ui']['display']['type'] = 'waveshare_1'
|
||||
|
||||
|
|
@ -277,7 +280,7 @@ def load_config(args):
|
|||
|
||||
elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare_213bc', 'waveshare213bc'):
|
||||
config['ui']['display']['type'] = 'waveshare213bc'
|
||||
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_213bv4', 'ws213bv4', 'waveshare_213bv4', 'waveshare213inb_v4'):
|
||||
config['ui']['display']['type'] = 'waveshare213inb_v4'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue