Source code for luma.oled.device

# -*- coding: utf-8 -*-
# Copyright (c) 2014-17 Richard Hull and contributors
# See LICENSE.rst for details.

# Example usage:
#
#   from luma.core.serial import i2c, spi
#   from luma.core.render import canvas
#   from luma.oled.device import ssd1306, sh1106
#   from PIL import ImageDraw
#
#   serial = i2c(port=1, address=0x3C)
#   device = ssd1306(serial)
#
#   with canvas(device) as draw:
#      draw.rectangle(device.bounding_box, outline="white", fill="black")
#      draw.text(30, 40, "Hello World", fill="white")
#
# As soon as the with-block scope level is complete, the graphics primitives
# will be flushed to the device.
#
# Creating a new canvas is effectively 'carte blanche': If you want to retain
# an existing canvas, then make a reference like:
#
#    c = canvas(device)
#    for X in ...:
#        with c as draw:
#            draw.rectangle(...)
#
# As before, as soon as the with block completes, the canvas buffer is flushed
# to the device

from luma.core.device import device
import luma.core.error
import luma.oled.const


[docs]class sh1106(device): """ Encapsulates the serial interface to the monochrome SH1106 OLED display hardware. On creation, an initialization sequence is pumped to the display to properly configure it. Further control commands can then be called to affect the brightness and other settings. """ def __init__(self, serial_interface=None, width=128, height=64, rotate=0, **kwargs): super(sh1106, self).__init__(luma.oled.const.sh1106, serial_interface) self.capabilities(width, height, rotate) self._pages = self._h // 8 # FIXME: Delay doing anything here with alternate screen sizes # until we are able to get a device to test with. if width != 128 or height != 64: raise luma.core.error.DeviceDisplayModeError( "Unsupported display mode: {0} x {1}".format(width, height)) self.command( self._const.DISPLAYOFF, self._const.MEMORYMODE, self._const.SETHIGHCOLUMN, 0xB0, 0xC8, self._const.SETLOWCOLUMN, 0x10, 0x40, self._const.SETSEGMENTREMAP, self._const.NORMALDISPLAY, self._const.SETMULTIPLEX, 0x3F, self._const.DISPLAYALLON_RESUME, self._const.SETDISPLAYOFFSET, 0x00, self._const.SETDISPLAYCLOCKDIV, 0xF0, self._const.SETPRECHARGE, 0x22, self._const.SETCOMPINS, 0x12, self._const.SETVCOMDETECT, 0x20, self._const.CHARGEPUMP, 0x14) self.contrast(0x7F) self.clear() self.show()
[docs] def display(self, image): """ Takes a 1-bit :py:mod:`PIL.Image` and dumps it to the SH1106 OLED display. """ assert(image.mode == self.mode) assert(image.size == self.size) image = self.preprocess(image) set_page_address = 0xB0 image_data = image.getdata() pixels_per_page = self.width * 8 buf = bytearray(self.width) for y in range(0, int(self._pages * pixels_per_page), pixels_per_page): self.command(set_page_address, 0x02, 0x10) set_page_address += 1 offsets = [y + self.width * i for i in range(8)] for x in range(self.width): buf[x] = \ (image_data[x + offsets[0]] and 0x01) | \ (image_data[x + offsets[1]] and 0x02) | \ (image_data[x + offsets[2]] and 0x04) | \ (image_data[x + offsets[3]] and 0x08) | \ (image_data[x + offsets[4]] and 0x10) | \ (image_data[x + offsets[5]] and 0x20) | \ (image_data[x + offsets[6]] and 0x40) | \ (image_data[x + offsets[7]] and 0x80) self.data(list(buf))
[docs]class ssd1306(device): """ Encapsulates the serial interface to the monochrome SSD1306 OLED display hardware. On creation, an initialization sequence is pumped to the display to properly configure it. Further control commands can then be called to affect the brightness and other settings. """ def __init__(self, serial_interface=None, width=128, height=64, rotate=0, **kwargs): super(ssd1306, self).__init__(luma.oled.const.ssd1306, serial_interface) self.capabilities(width, height, rotate) # Supported modes settings = { (128, 64): dict(multiplex=0x3F, displayclockdiv=0x80, compins=0x12), (128, 32): dict(multiplex=0x1F, displayclockdiv=0x80, compins=0x02), (96, 16): dict(multiplex=0x0F, displayclockdiv=0x60, compins=0x02) }.get((width, height)) if settings is None: raise luma.core.error.DeviceDisplayModeError( "Unsupported display mode: {0} x {1}".format(width, height)) self._pages = height // 8 self._mask = [1 << (i // width) % 8 for i in range(width * height)] self._offsets = [(width * (i // (width * 8))) + (i % width) for i in range(width * height)] self.command( self._const.DISPLAYOFF, self._const.SETDISPLAYCLOCKDIV, settings['displayclockdiv'], self._const.SETMULTIPLEX, settings['multiplex'], self._const.SETDISPLAYOFFSET, 0x00, self._const.SETSTARTLINE, self._const.CHARGEPUMP, 0x14, self._const.MEMORYMODE, 0x00, self._const.SETSEGMENTREMAP, self._const.COMSCANDEC, self._const.SETCOMPINS, settings['compins'], self._const.SETPRECHARGE, 0xF1, self._const.SETVCOMDETECT, 0x40, self._const.DISPLAYALLON_RESUME, self._const.NORMALDISPLAY) self.contrast(0xCF) self.clear() self.show()
[docs] def display(self, image): """ Takes a 1-bit :py:mod:`PIL.Image` and dumps it to the SSD1306 OLED display. """ assert(image.mode == self.mode) assert(image.size == self.size) image = self.preprocess(image) self.command( # Column start/end address self._const.COLUMNADDR, 0x00, self._w - 1, # Page start/end address self._const.PAGEADDR, 0x00, self._pages - 1) buf = bytearray(self._w * self._pages) off = self._offsets mask = self._mask idx = 0 for pix in image.getdata(): if pix > 0: buf[off[idx]] |= mask[idx] idx += 1 self.data(list(buf))
[docs]class ssd1331(device): """ Encapsulates the serial interface to the 16-bit color (5-6-5 RGB) SSD1331 OLED display hardware. On creation, an initialization sequence is pumped to the display to properly configure it. Further control commands can then be called to affect the brightness and other settings. """ def __init__(self, serial_interface=None, width=96, height=64, rotate=0, **kwargs): super(ssd1331, self).__init__(luma.oled.const.ssd1331, serial_interface) self.capabilities(width, height, rotate, mode="RGB") self._buffer_size = width * height * 2 if width != 96 or height != 64: raise luma.core.error.DeviceDisplayModeError( "Unsupported display mode: {0} x {1}".format(width, height)) self.command( self._const.DISPLAYOFF, self._const.SETREMAP, 0x72, self._const.SETDISPLAYSTARTLINE, 0x00, self._const.SETDISPLAYOFFSET, 0x00, self._const.NORMALDISPLAY, self._const.SETMULTIPLEX, 0x3F, self._const.SETMASTERCONFIGURE, 0x8E, self._const.POWERSAVEMODE, 0x0B, self._const.PHASE12PERIOD, 0x74, self._const.CLOCKDIVIDER, 0xD0, self._const.SETPRECHARGESPEEDA, 0x80, self._const.SETPRECHARGESPEEDB, 0x80, self._const.SETPRECHARGESPEEDC, 0x80, self._const.SETPRECHARGEVOLTAGE, 0x3E, self._const.SETVVOLTAGE, 0x3E, self._const.MASTERCURRENTCONTROL, 0x0F) self.contrast(0xFF) self.clear() self.show()
[docs] def display(self, image): """ Takes a 24-bit RGB :py:mod:`PIL.Image` and dumps it to the SSD1331 OLED display. """ assert(image.mode == self.mode) assert(image.size == self.size) image = self.preprocess(image) self.command( self._const.SETCOLUMNADDR, 0x00, self._w - 1, self._const.SETROWADDR, 0x00, self._h - 1) i = 0 buf = bytearray(self._buffer_size) for r, g, b in image.getdata(): if not(r == g == b == 0): # 65K format 1 buf[i] = r & 0xF8 | g >> 5 buf[i + 1] = g << 5 & 0xE0 | b >> 3 i += 2 self.data(list(buf))
[docs] def contrast(self, level): """ Switches the display contrast to the desired level, in the range 0-255. Note that setting the level to a low (or zero) value will not necessarily dim the display to nearly off. In other words, this method is **NOT** suitable for fade-in/out animation. :param level: Desired contrast level in the range of 0-255. :type level: int """ assert(level >= 0) assert(level <= 255) self.command(self._const.SETCONTRASTA, level, self._const.SETCONTRASTB, level, self._const.SETCONTRASTC, level)
[docs]class ssd1322(device): """ Encapsulates the serial interface to the 4-bit greyscale SSD1322 OLED display hardware. On creation, an initialization sequence is pumped to the display to properly configure it. Further control commands can then be called to affect the brightness and other settings. """ def __init__(self, serial_interface=None, width=256, height=64, rotate=0, mode="RGB", **kwargs): super(ssd1322, self).__init__(luma.oled.const.ssd1322, serial_interface) self.capabilities(width, height, rotate, mode) self._buffer_size = width * height // 2 self._coladdr_start = (480 - width) // 8 self._coladdr_end = self._coladdr_start + (width // 4) - 1 if width <= 0 or width > 256 or \ height <= 0 or height > 64 or \ width % 16 != 0 or height % 16 != 0: raise luma.core.error.DeviceDisplayModeError( "Unsupported display mode: {0} x {1}".format(width, height)) self.command(0xFD, 0x12) # Unlock IC self.command(0xA4) # Display off (all pixels off) self.command(0xB3, 0xF2) # Display divide clockratio/freq self.command(0xCA, 0x3F) # Set MUX ratio self.command(0xA2, 0x00) # Display offset self.command(0xA1, 0x00) # Display start Line self.command(0xA0, 0x14, 0x11) # Set remap & dual COM Line self.command(0xB5, 0x00) # Set GPIO (disabled) self.command(0xAB, 0x01) # Function select (internal Vdd) self.command(0xB4, 0xA0, 0xFD) # Display enhancement A (External VSL) self.command(0xC7, 0x0F) # Master contrast (reset) self.command(0xB9) # Set default greyscale table self.command(0xB1, 0xF0) # Phase length self.command(0xD1, 0x82, 0x20) # Display enhancement B (reset) self.command(0xBB, 0x0D) # Pre-charge voltage self.command(0xB6, 0x08) # 2nd precharge period self.command(0xBE, 0x00) # Set VcomH self.command(0xA6) # Normal display (reset) self.command(0xA9) # Exit partial display self.contrast(0x7F) # Reset self.clear() self.show() def _render_mono(self, buf, image): i = 0 for pix in image.getdata(): if pix > 0: if i % 2 == 0: buf[i // 2] = 0xF0 else: buf[i // 2] |= 0x0F i += 1 def _render_greyscale(self, buf, image): i = 0 for r, g, b in image.getdata(): # RGB->Greyscale luma calculation into 4-bits grey = (r * 306 + g * 601 + b * 117) >> 14 if grey > 0: if i % 2 == 0: buf[i // 2] = (grey << 4) else: buf[i // 2] |= grey i += 1
[docs] def display(self, image): """ Takes a 1-bit monochrome or 24-bit RGB :py:mod:`PIL.Image` and dumps it to the SSD1322 OLED display, converting the image pixels to 4-bit greyscale using a simplified Luma calculation, based on *Y'=0.299R'+0.587G'+0.114B'*. """ assert(image.mode == self.mode) assert(image.size == self.size) image = self.preprocess(image) self.command(0x15, self._coladdr_start, self._coladdr_end) # set column addr self.command(0x75, 0x00, 0x7F) # Reset row addr self.command(0x5C) # Enable MCU to write data into RAM buf = bytearray(self._buffer_size) if self.mode == "1": self._render_mono(buf, image) else: self._render_greyscale(buf, image) self.data(list(buf))
[docs] def command(self, cmd, *args): """ Sends a command and an (optional) sequence of arguments through to the delegated serial interface. Note that the arguments are passed through as data. """ self._serial_interface.command(cmd) if len(args) > 0: self._serial_interface.data(list(args))
[docs]class ssd1325(device): """ Encapsulates the serial interface to the 4-bit greyscale SSD1325 OLED display hardware. On creation, an initialization sequence is pumped to the display to properly configure it. Further control commands can then be called to affect the brightness and other settings. """ def __init__(self, serial_interface=None, width=128, height=64, rotate=0, mode="RGB", **kwargs): super(ssd1325, self).__init__(luma.oled.const.ssd1325, serial_interface) self.capabilities(width, height, rotate, mode) self._buffer_size = width * height // 2 if width != 128 or height != 64: raise luma.core.error.DeviceDisplayModeError( "Unsupported display mode: {0} x {1}".format(width, height)) self.command( self._const.DISPLAYOFF, self._const.SETCLOCK, 0xF1, self._const.SETMULTIPLEX, 0x3F, self._const.SETOFFSET, 0x4C, self._const.SETSTARTLINE, 0x00, self._const.MASTERCONFIG, 0x02, self._const.SETREMAP, 0x50, self._const.SETCURRENT + 2, self._const.SETGRAYTABLE, 0x01, 0x11, 0x22, 0x32, 0x43, 0x54, 0x65, 0x76) self.contrast(0xFF) self.command( self._const.SETROWPERIOD, 0x51, self._const.SETPHASELEN, 0x55, self._const.SETPRECHARGECOMP, 0x02, self._const.SETPRECHARGECOMPENABLE, 0x28, self._const.SETVCOMLEVEL, 0x1C, self._const.SETVSL, 0x0F, self._const.NORMALDISPLAY) self.clear() self.show() def _render_mono(self, buf, image): i = 0 for pix in image.getdata(): if pix > 0: if i % 2 == 0: buf[i // 2] = 0x0F else: buf[i // 2] |= 0xF0 i += 1 def _render_greyscale(self, buf, image): i = 0 for r, g, b in image.getdata(): # RGB->Greyscale luma calculation into 4-bits grey = (r * 306 + g * 601 + b * 117) >> 14 if grey > 0: if i % 2 == 0: buf[i // 2] = grey else: buf[i // 2] |= (grey << 4) i += 1
[docs] def display(self, image): """ Takes a 1-bit monochrome or 24-bit RGB :py:mod:`PIL.Image` and dumps it to the SSD1325 OLED display, converting the image pixels to 4-bit greyscale using a simplified Luma calculation, based on *Y'=0.299R'+0.587G'+0.114B'*. """ assert(image.mode == self.mode) assert(image.size == self.size) image = self.preprocess(image) self.command( self._const.SETCOLUMNADDR, 0x00, self._w - 1, self._const.SETROWADDR, 0x00, self._h - 1) buf = bytearray(self._buffer_size) if self.mode == "1": self._render_mono(buf, image) else: self._render_greyscale(buf, image) self.data(list(buf))