Source code for philander.ssd1803a

"""Display driver module for text LCDs driven by the SSD1803A controller.
"""
__author__ = "Oliver Maye"
__version__ = "0.1"
__all__ = ["SSD1803A"]

import logging

from .display import TextDisplay
from .serialbus import SerialBusDevice, SerialBusType, SerialBus, SPIMode
from .systypes import ErrorCode


[docs] class SSD1803A( TextDisplay ): """Driver class for a text LCD driven by SSD1803A. Although, this module aims at being generic beyond the controller hardware, clearly the DOGM204-A text display by Display Visions was in mind when developing this driver. The DOGM204 is a 4x20 characters chip-on-glass display module driven by the SSD1803A display controller by Solomon Systech. The module can be controlled via SPI or I2C. More information on that hardware can be found at: https://www.lcd-module.de/fileadmin/pdf/doma/dogm204.pdf The SSD1803A supports display sizes of up to 80 characters (max. total). Character format may be 5x8 or 6x8. Detailed technical information on the controller can be found at: https://www.solomon-systech.com/product/SSD1803A """ ADDRESSES_ALLOWED = [0x3C, 0x3D] PART_ID = 0x1A # Internal part ID, can be used to check communication REG_CMD = 0 # Command register, RS=0, D/C#=0 REG_DATA = 1 # Data or RAM register, RS=1, D/C#=1 def __init__(self): """Initialize the instance with defaults. Also see: :meth:`.Params_init`, :meth:`.ShiftReg.Params_init` """ super().__init__() # Derived attributes self._widthChar = 20 self._heightChar = 4 # Own attributes self._serbusdev = None # # internal helper # """Serial communication for the SSD1803A is detailed in the datasheet, Rev. 2.0, chapter 7.10. SPI ("Serial Interface", 7.10.2) bit-order is interpreted as LSB-first. This driver encapsulates serial communication with only a handful functions and tries to accommodate both, MSB- and LSB-first configurations. """ @classmethod def _reverseBitOrder(cls, buffer): """Reverse bit order for each byte in the given buffer in-place. """ for idx in range( len(buffer) ): b = buffer[idx] invb = 0 for _ in range(8): invb = (invb << 1) | (b & 0x01) b >>= 1 buffer[idx] = invb return None def _writeCmd(self, data): ret = ErrorCode.errOk if self._serbusdev.serialBus.type == SerialBusType.SPI: # RS=0, R/W=0 buffer = [0x1F, data & 0x0F, (data & 0xF0)>>4] if self._serbusdev.serialBus.spiBitOrder == "MSB": self._reverseBitOrder(buffer) ret = self._serbusdev.writeBuffer( buffer ) elif self._serbusdev.serialBus.type == SerialBusType.I2C: # D/C#=0, Co=0 ret = self._serbusdev.writeByteRegister( 0x00, data ) else: logging.debug("SSD1803A._writeCmd: bus type %s unsupported.", self._serbusdev.serialBus.type) ret = ErrorCode.errNotImplemented return ret def _readInfo(self): bf, ac, cid, ret = False, 0, 0, ErrorCode.errOk if self._serbusdev.serialBus.type == SerialBusType.SPI: # RS=0, R/W=1 # Remember readWordRegister() reads little-endian first. if self._serbusdev.serialBus.spiBitOrder == "MSB": data, ret = self._serbusdev.readWordRegister( 0xFC ) data = [ data & 0xFF, (data & 0xFF00)>>4] self._reverseBitOrder( data ) b1 = data[0] b2 = data[1] else: data, ret = self._serbusdev.readWordRegister( 0x3F ) b1 = data & 0xFF b2 = (data & 0xFF00) >> 8 bf = b1 & 0x80 ac = b1 & 0x7F cid = b2 & 0x7F elif self._serbusdev.serialBus.type == SerialBusType.I2C: # D/C#=0, Co=0 data, ret = self._serbusdev.readWordRegister( 0x00 ) bf = data & 0x80 ac = data & 0x7F cid = (data & 0x7F00) >> 8 else: logging.debug("SSD1803A._readInfo: bus type %s unsupported.", self._serbusdev.serialBus.type) ret = ErrorCode.errNotImplemented # busy flag, AC = address counter, ID, error return bf, ac, cid, ret def _writeRAM(self, data): ret = ErrorCode.errOk if not data: ret = ErrorCode.errFewData elif self._serbusdev.serialBus.type == SerialBusType.SPI: # Start byte, RS=1, R/W=0 if self._serbusdev.serialBus.spiBitOrder == "MSB": ret = self._serbusdev.writeBuffer( [0xFA] ) else: ret = self._serbusdev.writeBuffer( [0x5F] ) for b in data: wbuf = [ (b & 0x0F), (b & 0xF0)>>4 ] if self._serbusdev.serialBus.spiBitOrder == "MSB": self._reverseBitOrder( wbuf ) ret = self._serbusdev.writeBuffer( wbuf ) if not ret.isOk(): break elif self._serbusdev.serialBus.type == SerialBusType.I2C: # D/C#=1, Co=0 ret = self._serbusdev.writeBufferRegister( 0x40, data ) else: logging.debug("SSD1803A._writeRAM: bus type %s unsupported.", self._serbusdev.serialBus.type) ret = ErrorCode.errNotImplemented return ret def _readRAM(self, num): ret = ErrorCode.errOk data = None if not isinstance( num, int ) or (num<=0): ret = ErrorCode.errInvalidParameter elif self._serbusdev.serialBus.type == SerialBusType.SPI: # RS=1, R/W=1 data = [0] * num if self._serbusdev.serialBus.spiBitOrder == "MSB": ret = self._serbusdev.writeBuffer( [0xFE,] ) else: ret = self._serbusdev.writeBuffer( [0x7F,] ) for idx in range(num): b, ret = self._serbusdev.readBuffer( 1 ) if ret.isOk(): data[idx] = b[0] else: break if ret.isOk() and (self._serbusdev.serialBus.spiBitOrder == "MSB"): self._reverseBitOrder(data) elif self._serbusdev.serialBus.type == SerialBusType.I2C: # D/C#=1, Co data, ret = self._serbusdev.readBufferRegister( 0x40, num ) else: logging.debug("SSD1803A._readRAM: bus type %s unsupported.", self._serbusdev.serialBus.type) ret = ErrorCode.errNotImplemented return data, ret # # Module API #
[docs] @classmethod def Params_init( cls, paramDict ): """Initialize parameters with default values. Supported key names and their meanings are: =========================== =============================================================================================== Key Meaning, Range, Default =========================== =============================================================================================== display.SerialBusDevice.* Serial bus device config; See :meth:`.SerialBusDevice.Params_init`. display.SerialBus.* Serial bus configuration; See :meth:`.SerialBus.Params_init`. =========================== =============================================================================================== :param dict(str, object) paramDict: Dictionary of configuration settings. :return: none :rtype: None """ prefix = cls.MODULE_PARAM_PREFIX + "." serDict = cls._extractParams( paramDict, prefix) SerialBus.Params_init(serDict) cls._aggregateParams( paramDict, serDict, cls.MODULE_PARAM_PREFIX + "." ) key = cls.MODULE_PARAM_PREFIX + "." + SerialBus.MODULE_PARAM_PREFIX + ".type" if key in paramDict: if paramDict[key] == SerialBusType.I2C: key = cls.MODULE_PARAM_PREFIX + "." + SerialBusDevice.MODULE_PARAM_PREFIX + ".address" if key in paramDict: if not paramDict[key] in cls.ADDRESSES_ALLOWED: paramDict[key] = cls.ADDRESSES_ALLOWED[0] else: paramDict[key] = cls.ADDRESSES_ALLOWED[0] prefix = cls.MODULE_PARAM_PREFIX + "." + SerialBusDevice.MODULE_PARAM_PREFIX serDict = cls._extractParams( paramDict, prefix) SerialBusDevice.Params_init(serDict) cls._aggregateParams( paramDict, serDict, cls.MODULE_PARAM_PREFIX + "." ) super().Params_init(paramDict)
def _drvOpen(self, paramDict): """Open the low-level driver and prepare it for use. :param dict(str, object) paramDict: Configuration parameters. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk if self._serbusdev: ret = ErrorCode.errResourceConflict if ret.isOk(): prefixMod = self.MODULE_PARAM_PREFIX + "." # Extract serial bus parameters sparams = self._extractParams( paramDict, prefixMod ) self._serbusdev = SerialBusDevice() ret = self._serbusdev.open(sparams) if ret.isOk(): self._writeCmd( 0x3A ) # DL=8 bit, RE=1; REV=0 self._writeCmd( 0x09 ) # Display has 4 lines self._writeCmd( 0x06 ) # Bottom view self._writeCmd( 0x1E ) # Bias self._writeCmd( 0x39 ) # RE=0, IS=1 self._writeCmd( 0x1B ) # Bias self._writeCmd( 0x6E ) # Follower control self._writeCmd( 0x57 ) # Power control, contrast self._writeCmd( 0x72 ) # Contrast low self._writeCmd( 0x38 ) # RE=0, IS=0 self._writeCmd( 0x0F ) # Display on, Cursor on, Blink on else: logging.debug("SSD1803A couldn't open serial bus device, error: %s.", ret) self._serbusdev = None logging.debug("SSD1803A.open() returns: %s.", ret) return ret def _drvClose(self): """Close this instance and release associated hardware resources. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk if self._serbusdev: ret = self._serbusdev.close() self._serbusdev = None logging.debug("SSD1803A.close() returns: %s.", ret) return ret def _drvSetRunLevel(self, level): """Select the power-saving operation mode. :param RunLevel level: The level to switch to. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del level return ErrorCode.errNotSupported # # Display API # def _drvSetOrientation(self, orientation): """Switch to a new display orientation. :param int orientation: The new orientation to set. One of the ``Display.ORIENTATION_*`` values. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del orientation return ErrorCode.errNotSupported def _drvSetBrightness(self, value): """Configure the brightness intensity. :param int value: The new value to set the brightness to. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del value return ErrorCode.errNotSupported def _drvSetContrast(self, value): """ Adjusts the contrast intensity. :param int value: The new value to set the contrast to. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del value return ErrorCode.errNotSupported def _drvSetInverse(self, inverseOn=True): """ Invert the display. :param bool inverseOn: `True` for inverse mode, `False` for normal mode. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del inverseOn return ErrorCode.errNotSupported # # TextDisplay API # def _drvSetCursorMode( self, mode ): """Switches the hardware cursor to a specific appearance. :param mode: The cursor mode to switch to. One of the `CURSOR_MODE_*` values. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del mode return ErrorCode.errNotSupported def _drvSetCursorSize( self, width, height ): """Sets the size of the cursor. :param width: The width of the cursor. One of the `CURSOR_SIZE_*` values. :param height: The height of the cursor. One of the `CURSOR_SIZE_*` values. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del width, height return ErrorCode.errNotSupported def _drvMoveCursorTo( self, x, y ): """Sets the new absolute position of the hardware cursor. :param int x: The horizontal cursor position, given in characters. :param int y: The vertical cursor position, given in characters. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del x, y return ErrorCode.errNotSupported def _drvGoTo( self, x, y ): """Move the internal ``current position`` to the specified absolute position. :param int x: The new horizontal position. :param int y: The new vertical position. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del x, y return ErrorCode.errOk def _drvClearScreen( self ): """Clear all contents from screen. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ return ErrorCode.errNotImplemented def _drvScrollV( self, numLines ): """Scroll the contents by the given number of lines. A positive argument makes the content scroll up, so the view port moves down, while for a negative argument it's vice versa. The content scrolled off the screen is not buffered. So, when scrolling back, it's the responsibility of the caller to reproduce that content, again. :param int numLines: The number of lines to scroll. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del numLines return ErrorCode.errNotSupported def _drvGetBuiltinFontNames(self): """Retrieve the names of the fonts possibly built-in the display hardware. The names of all built-fonts are returned as a list. If there is no built-in fonts, an empty list is returned. If there is a default font, its name is assumed to be the first in the list - at index zero. :return: The list of font names and an error code indicating either success or the reason of failure. :rtype: Tuple( list(str), ErrorCode) """ return [], ErrorCode.errNotSupported def _drvGetBuiltinFont(self, name=""): """Retrieve the built-in fonts given its name. If the name parameter is an empty string, the default font is retrieved. :param str name: The name of the font to retrieve. :return: The font and an error code indicating either success or the reason of failure. :rtype: Tuple( Font, ErrorCode) """ del name return None, ErrorCode.errNotSupported def _drvSetFont( self, font ): """Select a new font as current. Implementations will also want to update internal attributes like _widthChar, _currentX or _cursorX. :param font: The new character font to set as current. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del font return ErrorCode.errNotSupported def _drvPrintChar( self, code): """Print a character at the internal ``current position``. :param int code: The ASCII code of the character to print. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del code return ErrorCode.errNotImplemented