separate I2C Oled display

Separate files for ggeneric I2C Oled display with SSD1306 driver
128x64 resolution is tested, 128x32 and 96x16 resolutions is not tested yet
This commit is contained in:
RasTacsko
2024-04-02 17:46:11 +02:00
parent a71a90ba3d
commit ab83de4905
3 changed files with 340 additions and 17 deletions

View File

@ -1,18 +1,9 @@
# workinprogress based on the displayhatmini driver # Created for the Pwnagotchi project by RasTacsko
# LCD support OK # HW libraries are based on the adafruit python SSD1306 repo:
# OLED support ongoing # https://github.com/adafruit/Adafruit_Python_SSD1306
# board GPIO: # SMBus parts coming from BLavery's lib_oled96 repo:
# Key1: GPIO4 / pin7 # https://github.com/BLavery/lib_oled96
# Key2: GPIO17 / pin11
# Key3: GPIO23 / pin16
# Key4: GPIO24 / pin18
# OLED SDA: GPIO2 / pin3
# OLED SCL: GPIO3 / pin5
# OLED info:
# driver: SSD1315 (I2C)
# resolution: 128x64
# I2C address: 0x3C 0x3D
# HW datasheet: https://www.waveshare.com/wiki/OLED/LCD_HAT_(A)
import logging import logging
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
@ -45,8 +36,9 @@ class I2COled(DisplayImpl):
return self._layout return self._layout
def initialize(self): def initialize(self):
logging.info("initializing I2C Oled Display on address 0x3C") logging.info("initializing 128x64 I2C Oled Display on address 0x3C")
from pwnagotchi.ui.hw.libs.waveshare.oled.oledlcd.epd import EPD logging.info("To change resolution or address check pwnagotchi/ui/hw/libs/i2coled/epd.py")
from pwnagotchi.ui.hw.libs.i2coled.epd import EPD
self._display = EPD() self._display = EPD()
self._display.Init() self._display.Init()
self._display.Clear() self._display.Clear()

View File

@ -0,0 +1,304 @@
# 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 FOR 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.
#
# SMBus parts coming from BLavery's lib_oled96 repo:
# https://github.com/BLavery/lib_oled96
#
# Modified for the Pwnagotchi project by RasTacsko
# Using SMBus, spidev RPi.GPIO for I2C communication instead of Adafruit libraries
# spidev maybe not necessary... needs some checking!!!
# ToDo:
# rotation support for vertical layouts
# checking luma oled library for support other chipsets/resolutions
from __future__ import division
import logging
import time
import RPi.GPIO as GPIO
# import spidev
from smbus import SMBus
i2cbus = SMBus(1)
# 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):
"""Base class for SSD1306-based OLED displays. Implementors should subclass
and provide an implementation for the _initialize function.
"""
def __init__(self, width, height, address=SSD1306_I2C_ADDRESS, bus=None):
self._log = logging.getLogger('Adafruit_SSD1306.SSD1306Base')
self.cmd_mode = 0x00
self.data_mode = 0x40
self.bus = i2cbus
self.addr = address
self.width = width
self.height = height
self._pages = height//8
self._buffer = [0]*(width*self._pages)
def _initialize(self):
raise NotImplementedError
def command(self, *cmd):
"""Send command byte to display."""
# I2C write.
assert(len(cmd) <= 31)
self.bus.write_i2c_block_data(self.addr, self.cmd_mode, list(cmd))
def data(self, data):
"""Send byte of data to display."""
# I2C write.
for i in range(0, len(data), 31):
self.bus.write_i2c_block_data(self.addr, self.data_mode, list(data[i:i+31]))
def begin(self, vccstate=SSD1306_SWITCHCAPVCC):
"""Initialize display."""
# Save vcc state.
self._vccstate = vccstate
# Reset and initialize display.
self._initialize()
# Turn on the display.
self.command(SSD1306_DISPLAYON)
def ShowImage(self):
"""
The image on the "canvas" is flushed through to the hardware display.
Takes the 1-bit image and dumps it to the SSD1306 OLED 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.
for i in range(0, len(self._buffer), 16):
self.bus.write_i2c_block_data(self.addr, self.data_mode, self._buffer[i:i+16])
def getbuffer(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, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_128_64, self).__init__(128, 64, address, bus)
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
class SSD1306_128_32(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_128_64, self).__init__(128, 64, address, bus)
def _initialize(self):
# 128x32 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(0x1F)
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(0x02)
self.command(SSD1306_SETCONTRAST) # 0x81
self.command(0x8F)
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
class SSD1306_96_16(SSD1306Base):
def __init__(self, width, height, address=None, bus=None):
# Call base class constructor.
super(SSD1306_96_16, self).__init__(96, 16, address, bus)
def _initialize(self):
# 128x32 pixel specific initialization.
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x60) # the suggested ratio 0x60
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x0F)
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(0x02)
self.command(SSD1306_SETCONTRAST) # 0x81
self.command(0x8F)
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

View File

@ -0,0 +1,27 @@
from . import SSD1306
# Display resolution, change if the screen resolution is changed!
EPD_WIDTH = 128
EPD_HEIGHT = 64
# Available screen resolutions:
# disp = SSD1306.SSD1306_128_32(128, 32, address=0x3C)
# disp = SSD1306.SSD1306_96_16(96, 16, address=0x3C)
# If you change for different resolution, you have to modify the layout in pwnagotchi/ui/hw/i2coled.py
disp = SSD1306.SSD1306_128_64(128, 64, address=0x3C)
class EPD(object):
def __init__(self):
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def Init(self):
disp.begin()
def Clear(self):
disp.clear()
def display(self, image):
disp.getbuffer(image)
disp.ShowImage()