Source code for luma.oled.device

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

"""
Collection of serial interfaces to OLED devices.
"""

# Example usage:
#
#   from luma.core.interface.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
from luma.oled.device.color import color_device
from luma.oled.device.greyscale import greyscale_device
import luma.core.error
import luma.core.framebuffer
import luma.oled.const


__all__ = ["ssd1306", "ssd1309", "ssd1322", "ssd1325", "ssd1327", "ssd1331", "ssd1351", "sh1106"]


[docs]class sh1106(device): """ Serial interface to a monochrome SH1106 OLED display. 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 settings = { (128, 128): dict(multiplex=0xFF, displayoffset=0x02), (128, 64): dict(multiplex=0x3F, displayoffset=0x00), (128, 32): dict(multiplex=0x20, displayoffset=0x0F) }.get((width, height)) if settings is None: 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, settings['multiplex'], self._const.DISPLAYALLON_RESUME, self._const.SETDISPLAYOFFSET, settings['displayoffset'], 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): """ Serial interface to a monochrome SSD1306 OLED display. 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. :param serial_interface: the serial interface (usually a :py:class:`luma.core.interface.serial.i2c` instance) to delegate sending data and commands through. :param width: the number of horizontal pixels (optional, defaults to 128). :type width: int :param height: the number of vertical pixels (optional, defaults to 64). :type height: int :param rotate: an integer value of 0 (default), 1, 2 or 3 only, where 0 is no rotation, 1 is rotate 90° clockwise, 2 is 180° rotation and 3 represents 270° rotation. :type rotate: int """ 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), (64, 48): dict(multiplex=0x2F, displayclockdiv=0x80, compins=0x12), (64, 32): dict(multiplex=0x1F, displayclockdiv=0x80, compins=0x12) }.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._colstart = (0x80 - self._w) // 2 self._colend = self._colstart + self._w 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 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, self._colstart, self._colend - 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 ssd1309(ssd1306): """ Serial interface to a monochrome SSD1309 OLED display. 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. :param serial_interface: the serial interface (usually a :py:class:`luma.core.interface.serial.spi` instance) to delegate sending data and commands through. :param width: the number of horizontal pixels (optional, defaults to 128). :type width: int :param height: the number of vertical pixels (optional, defaults to 64). :type height: int :param rotate: an integer value of 0 (default), 1, 2 or 3 only, where 0 is no rotation, 1 is rotate 90° clockwise, 2 is 180° rotation and 3 represents 270° rotation. :type rotate: int .. versionadded:: 3.1.0 """
[docs]class ssd1331(color_device): """ Serial interface to a 16-bit color (5-6-5 RGB) SSD1331 OLED display. 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. :param serial_interface: the serial interface (usually a :py:class:`luma.core.interface.serial.spi` instance) to delegate sending data and commands through. :param width: the number of horizontal pixels (optional, defaults to 96). :type width: int :param height: the number of vertical pixels (optional, defaults to 64). :type height: int :param rotate: an integer value of 0 (default), 1, 2 or 3 only, where 0 is no rotation, 1 is rotate 90° clockwise, 2 is 180° rotation and 3 represents 270° rotation. :type rotate: int :param framebuffer: Framebuffering strategy, currently values of ``diff_to_previous`` or ``full_frame`` are only supported. :type framebuffer: str """ def __init__(self, serial_interface=None, width=96, height=64, rotate=0, framebuffer="diff_to_previous", **kwargs): super(ssd1331, self).__init__(serial_interface, width, height, rotate, framebuffer, **kwargs) def _supported_dimensions(self): return [(96, 64)] def _init_sequence(self): self.command( 0xAE, # Display off 0xA0, 0x72, # Seg remap 0xA1, 0x00, # Set Display start line 0xA2, 0x00, # Set display offset 0xA4, # Normal display 0xA8, 0x3F, # Set multiplex 0xAD, 0x8E, # Master configure 0xB0, 0x0B, # Power save mode 0xB1, 0x74, # Phase12 period 0xB3, 0xD0, # Clock divider 0x8A, 0x80, # Set precharge speed A 0x8B, 0x80, # Set precharge speed B 0x8C, 0x80, # Set precharge speed C 0xBB, 0x3E, # Set pre-charge voltage 0xBE, 0x3E, # Set voltage 0x87, 0x0F) # Master current control def _set_position(self, top, right, bottom, left): self.command( 0x15, left, right - 1, # Set column addr 0x75, top, bottom - 1) # Set row addr
[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(0 <= level <= 255) self.command(0x81, level, # Set contrast A 0x82, level, # Set contrast B 0x83, level) # Set contrast C
[docs]class ssd1351(color_device): """ Serial interface to the 16-bit color (5-6-5 RGB) SSD1351 OLED display. 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. :param serial_interface: the serial interface (usually a :py:class:`luma.core.interface.serial.spi` instance) to delegate sending data and commands through. :param width: the number of horizontal pixels (optional, defaults to 128). :type width: int :param height: the number of vertical pixels (optional, defaults to 128). :type height: int :param rotate: an integer value of 0 (default), 1, 2 or 3 only, where 0 is no rotation, 1 is rotate 90° clockwise, 2 is 180° rotation and 3 represents 270° rotation. :type rotate: int :param framebuffer: Framebuffering strategy, currently values of ``diff_to_previous`` or ``full_frame`` are only supported. :type framebuffer: str :param bgr: Set to ``True`` if device pixels are BGR order (rather than RGB). :type bgr: bool :param h_offset: horizontal offset (in pixels) of screen to device memory (default: 0) :type h_offset: int :param v_offset: vertical offset (in pixels) of screen to device memory (default: 0) :type h_offset: int .. versionadded:: 2.3.0 """ def __init__(self, serial_interface=None, width=128, height=128, rotate=0, framebuffer="diff_to_previous", h_offset=0, v_offset=0, bgr=False, **kwargs): # RGB or BGR order self._color_order = 0x04 if bgr else 0x00 if h_offset != 0 or v_offset != 0: def offset(bbox): left, top, right, bottom = bbox return (left + h_offset, top + v_offset, right + h_offset, bottom + v_offset) self._apply_offsets = offset super(ssd1351, self).__init__(serial_interface, width, height, rotate, framebuffer, **kwargs) def _supported_dimensions(self): return [(96, 96), (128, 128)] def _init_sequence(self): self.command(0xFD, 0x12) # Unlock IC MCU interface self.command(0xFD, 0xB1) # Command A2,B1,B3,BB,BE,C1 accessible if in unlock state self.command(0xAE) # Display off self.command(0xB3, 0xF1) # Clock divider self.command(0xCA, 0x7F) # Mux ratio self.command(0x15, 0x00, self.width - 1) # Set column address self.command(0x75, 0x00, self.height - 1) # Set row address self.command(0xA0, 0x70 | self._color_order) # Segment remapping self.command(0xA1, 0x00) # Set Display start line self.command(0xA2, 0x00) # Set display offset self.command(0xB5, 0x00) # Set GPIO self.command(0xAB, 0x01) # Function select (internal - diode drop) self.command(0xB1, 0x32) # Precharge self.command(0xB4, 0xA0, 0xB5, 0x55) # Set segment low voltage self.command(0xBE, 0x05) # Set VcomH voltage self.command(0xC7, 0x0F) # Contrast master self.command(0xB6, 0x01) # Precharge2 self.command(0xA6) # Normal display def _set_position(self, top, right, bottom, left): self.command(0x15, left, right - 1) # Set column addr self.command(0x75, top, bottom - 1) # Set row addr self.command(0x5C) # Write RAM
[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(0 <= level <= 255) self.command(0xC1, level, level, level)
[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 ssd1322(greyscale_device): """ Serial interface to a 4-bit greyscale SSD1322 OLED display. 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. :param serial_interface: the serial interface (usually a :py:class:`luma.core.interface.serial.spi` instance) to delegate sending data and commands through. :param width: the number of horizontal pixels (optional, defaults to 96). :type width: int :param height: the number of vertical pixels (optional, defaults to 64). :type height: int :param rotate: an integer value of 0 (default), 1, 2 or 3 only, where 0 is no rotation, 1 is rotate 90° clockwise, 2 is 180° rotation and 3 represents 270° rotation. :type rotate: int :param mode: Supplying "1" or "RGB" effects a different rendering mechanism, either to monochrome or 4-bit greyscale. :type mode: str :param framebuffer: Framebuffering strategy, currently values of ``diff_to_previous`` or ``full_frame`` are only supported :type framebuffer: str """ def __init__(self, serial_interface=None, width=256, height=64, rotate=0, mode="RGB", framebuffer="diff_to_previous", **kwargs): self._column_offset = (480 - width) // 2 super(ssd1322, self).__init__(luma.oled.const.ssd1322, serial_interface, width, height, rotate, mode, framebuffer, nibble_order=0, **kwargs) def _supported_dimensions(self): return [(256, 64), (256, 48), (256, 32), (128, 64), (128, 48), (128, 32), (64, 64), (64, 48), (64, 32)] def _init_sequence(self): 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 def _set_position(self, top, right, bottom, left): width = right - left pix_start = self._column_offset + left coladdr_start = pix_start >> 2 coladdr_end = (pix_start + width >> 2) - 1 self.command(0x15, coladdr_start, coladdr_end) # set column addr self.command(0x75, top, bottom - 1) # Reset row addr self.command(0x5C) # Enable MCU to write data into RAM
[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(greyscale_device): """ Serial interface to a 4-bit greyscale SSD1325 OLED display. 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", framebuffer="full_frame", **kwargs): super(ssd1325, self).__init__(luma.core.const.common, serial_interface, width, height, rotate, mode, framebuffer, nibble_order=1, **kwargs) def _supported_dimensions(self): return [(128, 64)] def _init_sequence(self): self.command( 0xAE, # Diplay off (all pixels off) 0xB3, 0xF2, # Display divide clockratio/freq 0xA8, 0x3F, # Set MUX ratio 0xA2, 0x4C, # Display offset 0xA1, 0x00, # Display start line 0xAD, 0x02, # Master configuration (external Vcc) 0xA0, 0x50, # Set remap (enable COM remap & split odd/even) 0x86, # Set current range (full) 0xB8, 0x01, 0x11, # Set greyscale table 0x22, 0x32, 0x43, # .. cont 0x54, 0x65, 0x76, # .. cont 0xB2, 0x51, # Set row period 0xB1, 0x55, # Set phase length 0xB4, 0x03, # Set pre-charge compensation level 0xB0, 0x28, # Set pre-charge compensation enable 0xBC, 0x01, # Pre-charge voltage 0xBE, 0x00, # Set VcomH 0xBF, 0x02, # Set VSL (not connected) 0xA4) # Normal dislay def _set_position(self, top, right, bottom, left): self.command( 0x15, left, right - 1, # set column addr 0x75, top, bottom - 1) # set row addr
[docs]class ssd1327(greyscale_device): """ Serial interface to a 4-bit greyscale SSD1327 OLED display. 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. .. versionadded:: 2.4.0 """ def __init__(self, serial_interface=None, width=128, height=128, rotate=0, mode="RGB", framebuffer="full_frame", **kwargs): super(ssd1327, self).__init__(luma.core.const.common, serial_interface, width, height, rotate, mode, framebuffer, nibble_order=1, **kwargs) def _supported_dimensions(self): return [(128, 128)] def _init_sequence(self): self.command( 0xAE, # Display off (all pixels off) 0xA0, 0x53, # gment remap (com split, com remap, nibble remap, column remap) 0xA1, 0x00, # Display start line 0xA2, 0x00, # Display offset 0xA4, # regular display 0xA8, 0x7F) # set multiplex ratio: 127 self.command( 0xB8, 0x01, 0x11, # Set greyscale table 0x22, 0x32, 0x43, # .. cont 0x54, 0x65, 0x76) # .. cont self.command( 0xB3, 0x00, # Front clock divider: 0, Fosc: 0 0xAB, 0x01, # Enable Internal Vdd 0xB1, 0xF1, # Set phase periods - 1: 1 clk, 2: 15 clks 0xBC, 0x08, # Pre-charge voltage: Vcomh 0xBE, 0x07, # COM deselect voltage level: 0.86 x Vcc 0xD5, 0x62, # Enable 2nd pre-charge 0xB6, 0x0F) # 2nd Pre-charge period: 15 clks def _set_position(self, top, right, bottom, left): self.command( 0x15, left, right - 1, # set column addr 0x75, top, bottom - 1) # set row addr