Skip to content

Commit

Permalink
Add support for ST7789 (#141)
Browse files Browse the repository at this point in the history
* Documentation

* ST7789 support (#92)

Co-authored-by: Richard Hull <rm_hull@yahoo.co.uk>

* Move from numpy to python for `st7789.display`

* Make `flake` happy

* Add tests for st7789, change implementation to more in line with other displays

* QA remarks

* Contrast test

* Review remarks addressed

* Performance improvement for st7789

Co-authored-by: Richard Hull <rm_hull@yahoo.co.uk>
Co-authored-by: Philip Howard <phil@gadgetoid.com>
  • Loading branch information
3 people committed Mar 14, 2021
1 parent a7925cf commit 093e53f
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 11 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ Contributors
* Kevin Stone (@kevinastone)
* Dhrone (@dhrone)
* Matthew Lovell (@mattblovell)
* Maciej Sokolowski (@matemaciek)
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ luma.lcd **|**

luma.lcd
========
**HD44780, PCD8544, ST7735, ST7567, HT1621, UC1701X, ILI9341 Display Drivers**
**HD44780, PCD8544, ST7735, ST7789, ST7567, HT1621, UC1701X, ILI9341 Display Drivers**

.. image:: https://github.com/rm-hull/luma.lcd/workflows/luma.lcd/badge.svg?branch=master
:target: https://github.com/rm-hull/luma.lcd/actions?workflow=luma.lcd
Expand Down
2 changes: 1 addition & 1 deletion doc/hardware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Device Pin Name Remarks RPi Pin RPi Function
* If you're already using the listed GPIO pins for Data/Command and/or Reset,
you can select other pins and pass :py:attr:`gpio_DC` and/or :py:attr:`gpio_RST`
argument specifying the new *GPIO* pin numbers in your serial interface create
call (this applies to PCD8544, ST7567 and ST7735).
call (this applies to PCD8544, ST7567, ST7735 and ST7789).

* Because CE is connected to CE0, the display is available on SPI port 0. You
can connect it to CE1 to have it available on port 1. If so, pass
Expand Down
4 changes: 2 additions & 2 deletions doc/index.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Luma.LCD: Display drivers for PCD8544, ST7735, ST7567, HT1621, UC1701X, ILI9341, HD44780
========================================================================================
Luma.LCD: Display drivers for PCD8544, ST7735, ST7789, ST7567, HT1621, UC1701X, ILI9341, HD44780
================================================================================================
.. image:: https://github.com/rm-hull/luma.lcd/workflows/luma.lcd/badge.svg?branch=master
:target: https://github.com/rm-hull/luma.lcd/actions?workflow=luma.lcd

Expand Down
1 change: 1 addition & 0 deletions doc/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ The ST7567 display supports a resolution of 128 x 64 monochrome pixels:

- :download:`PCD8544 <tech-spec/PCD8544.pdf>`
- :download:`ST7735 <tech-spec/ST7735.pdf>`
- :download:`ST7789 <tech-spec/ST7789.pdf>`
- :download:`HT1621 <tech-spec/HT1621.pdf>`
- :download:`UC1701X <tech-spec/UC1701X.pdf>`
- :download:`ILI9341 <tech-spec/ILI9341.pdf>`
Expand Down
8 changes: 4 additions & 4 deletions doc/python-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ In this example, we are using an SPI interface with a pcd8544 display.
from luma.core.interface.serial import i2c, spi, parallel, pcf8574
from luma.core.render import canvas
from luma.lcd.device import pcd8544, st7735, st7567, uc1701x, ili9341, ili9486, hd44780
from luma.lcd.device import pcd8544, st7735, st7789, st7567, uc1701x, ili9341, ili9486, hd44780
serial = spi(port=0, device=0, gpio_DC=23, gpio_RST=24)
device = pcd8544(serial)
The display device should now be properly configured.

The :py:class:`~luma.lcd.device.pcd8544`, :py:class:`~luma.lcd.device.st7735`,
The :py:class:`~luma.lcd.device.pcd8544`, :py:class:`~luma.lcd.device.st7735`, :py:class:`~luma.lcd.device.st7789`,
:py:class:`~luma.lcd.device.st7567`, :py:class:`~luma.lcd.device.uc1701x`, :py:class:`~luma.lcd.device.ili9341`,
:py:class:`~luma.lcd.device.ili9486` and :py:class:`~luma.lcd.device.hd44780`
classes all expose a :py:meth:`~luma.lcd.device.pcd8544.display` method which
Expand Down Expand Up @@ -69,13 +69,13 @@ effect (see the *3d_box.py* example, below).
with canvas(device, dither=True) as draw:
draw.rectangle((10, 10, 30, 30), outline="white", fill="red")
The ST7735 and ILI9341 devices can display 262K colour RGB images. When supplying
The ST7735, ST7789 and ILI9341 devices can display 262K colour RGB images. When supplying
24-bit RGB images, they are automatically downscaled to 18-bit RGB to fit
these device's 262K color-space.

Landscape / Portrait Orientation
--------------------------------
By default the PCD8544, ST7735, UC1701X and ILI9341 displays will all be oriented
By default the PCD8544, ST7735, ST7789, UC1701X and ILI9341 displays will all be oriented
in landscape mode (84x48, 160x128, 128x64 and 320x240 pixels respectively). Should
you have an application that requires the display to be mounted in a portrait
aspect, then add a :py:attr:`rotate=N` parameter when creating the device:
Expand Down
Binary file added doc/tech-spec/ST7789.pdf
Binary file not shown.
5 changes: 5 additions & 0 deletions luma/lcd/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class uc1701x(object):
DISPLAYOFF = 0xAE


class st7789(object):
DISPLAYON = 0x29
DISPLAYOFF = 0x28


class hd44780(object):
"""
Values to be used by the hd44780 class during initialization of the display.
Expand Down
60 changes: 59 additions & 1 deletion luma/lcd/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from luma.core.virtual import character
from luma.core.bitmap_font import embedded_fonts

__all__ = ["pcd8544", "st7735", "ht1621", "uc1701x", "st7567", "ili9341", "ili9486", "hd44780"]
__all__ = ["pcd8544", "st7735", "st7789", "ht1621", "uc1701x", "st7567", "ili9341", "ili9486", "hd44780"]


class GPIOBacklight:
Expand Down Expand Up @@ -292,6 +292,64 @@ def contrast(self, value):
self.command(0x21, 0x14, value | 0x80, 0x20)


class st7789(backlit_device):
"""
Serial interface to a colour ST7789 240x240 pixel LCD display.
.. versionadded:: 2.9.0
"""
def __init__(self, serial_interface=None, rotate=0, **kwargs):
super(st7789, self).__init__(luma.lcd.const.st7789, serial_interface, **kwargs)
self.capabilities(240, 240, rotate, mode="RGB")

self.command(0x36, 0x70) # MADCTL (36h): Memory Data Access Control: Bottom to Top, Right to Left, Reverse Mode
self.command(0x3A, 0x06) # COLMOD (3Ah): Interface Pixel Format: 18bit/pixel
self.command(0xB2, # PORCTRL (B2h): Porch Setting: Disable separate porch control, 0xC in normal mode, 0x3 in idle and partial modes
0x0C, 0x0C, 0x00, 0x33, 0x33)
self.command(0xB7, 0x35) # GCTRL (B7h): Gate Control: VGH = 13.26V, VGL = -10.43V
self.command(0xBB, 0x19) # VCOMS (BBh): VCOM Setting: 0.725V
self.command(0xC0, 0x2C) # LCMCTRL (C0h): LCM Control: XBGR, XMX, XMH
self.command(0xC2, 0x01) # VDVVRHEN (C2h): VDV and VRH Command Enable: VDV and VRH register value comes from command write
self.command(0xC3, 0x12) # VRHS (C3h): VRH Set: 4.45V + (vcom + vcom offset + vdv)
self.command(0xC4, 0x20) # VDVS (C4h): VDV Set: 0V
self.command(0xC6, 0x0F) # FRCTRL2 (C6h): Frame Rate Control in Normal Mode: 60Hz
self.command(0xD0, # PWCTRL1 (D0h): Power Control 1: AVDD = 6.8V, AVCL = -4.8V, VDDS = 2.3V
0xA4, 0xA1)
self.command(0xE0, # PVGAMCTRL (E0h): Positive Voltage Gamma Control
0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23)
self.command(0xE1, # NVGAMCTRL (E1h): Negative Voltage Gamma Control
0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23)
self.command(0x21) # INVON (21h): Display Inversion On
self.command(0x11) # SLPOUT (11h): Sleep Out
self.command(0x29) # DISPON (29h): Display On

self.clear()
self.show()

def set_window(self, x1, y1, x2, y2):
self.command(0x2A, # CASET (2Ah): Column Address Set
x1 >> 8, x1 & 0xFF, (x2 - 1) >> 8, (x2 - 1) & 0xFF)
self.command(0x2B, # RASET (2Bh): Row Address Set
y1 >> 8, y1 & 0xFF, (y2 - 1) >> 8, (y2 - 1) & 0xFF)
self.command(0x2C) # RAMWR (2Ch): Memory Write

def display(self, image):
w, h = 240, 240
self.set_window(0, 0, w, h)

image = self.preprocess(image)
self.data(list(image.convert("RGB").tobytes()))

def contrast(self, level):
"""
NOT SUPPORTED
:param level: Desired contrast level in the range of 0-255.
:type level: int
"""
assert(0 <= level <= 255)


class st7567(backlit_device):
"""
Serial interface to a monochrome ST7567 128x64 pixel LCD display.
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[metadata]
name = luma.lcd
version = attr: luma.lcd.__version__
description = A library to drive PCD8544, HT1621, ST7735, ST7567, UC1701X and ILI9341-based LCD displays
description = A library to drive PCD8544, HT1621, ST7735, ST7789, ST7567, UC1701X and ILI9341-based LCD displays
long_description = file: README.rst, CONTRIBUTING.rst, CHANGES.rst
long_description_content_type = text/x-rst
keywords = raspberry pi, rpi, lcd, display, screen, rgb, monochrome, greyscale, color, nokia 5110, pcd8544, st7735, uc1701x, ht1621, ili9341, hd44780, spi, i2c, parallel, bitbang6800, pcf8574
keywords = raspberry pi, rpi, lcd, display, screen, rgb, monochrome, greyscale, color, nokia 5110, pcd8544, st7735, st7789, uc1701x, ht1621, ili9341, hd44780, spi, i2c, parallel, bitbang6800, pcf8574
author = Richard Hull
author_email = richard.hull@destructuring-bind.org
url = https://github.com/rm-hull/luma.lcd
Expand Down
1 change: 1 addition & 0 deletions tests/reference/data/demo_st7789.json

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions tests/test_st7789.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2021 Richard Hull and contributors
# See LICENSE.rst for details.

"""
Tests for the :py:class:`luma.lcd.device.st7789` device.
"""
import pytest

from luma.lcd.device import st7789
from luma.core.render import canvas
from luma.core.framebuffer import full_frame

from baseline_data import get_reference_data, primitives
from helpers import serial
from unittest.mock import Mock


def test_init_240x240():
recordings = []

def data(data):
recordings.extend(data)

def command(*cmd):
recordings.extend(['command', list(cmd)[0], 'data', *list(cmd)[1:]])

serial.command.side_effect = command
serial.data.side_effect = data

st7789(serial, gpio=Mock(), framebuffer=full_frame())

assert serial.data.called
assert serial.command.called

assert recordings == [
'command', 54, 'data', 112,
'command', 58, 'data', 6,
'command', 178, 'data', 12, 12, 0, 51, 51,
'command', 183, 'data', 53,
'command', 187, 'data', 25,
'command', 192, 'data', 44,
'command', 194, 'data', 1,
'command', 195, 'data', 18,
'command', 196, 'data', 32,
'command', 198, 'data', 15,
'command', 208, 'data', 164, 161,
'command', 224, 'data', 208, 4, 13, 17, 19, 43, 63, 84, 76, 24, 13, 11, 31, 35,
'command', 225, 'data', 208, 4, 12, 17, 19, 44, 63, 68, 81, 47, 31, 31, 32, 35,
'command', 33, 'data',
'command', 17, 'data',
'command', 41, 'data',
'command', 42, 'data', 0, 0, 0, 239,
'command', 43, 'data', 0, 0, 0, 239,
'command', 44, 'data', *([0] * (240 * 240 * 3)),
'command', 41, 'data'
]


def test_contrast():
device = st7789(serial, gpio=Mock())
serial.reset_mock()
with pytest.raises(AssertionError):
device.contrast(300)


def test_hide():
device = st7789(serial, gpio=Mock())
serial.reset_mock()
device.hide()
serial.command.assert_called_once_with(40)


def test_show():
device = st7789(serial, gpio=Mock())
serial.reset_mock()
device.show()
serial.command.assert_called_once_with(41)


def test_display():
device = st7789(serial, gpio=Mock(), framebuffer=full_frame())
serial.reset_mock()

recordings = []

def data(data):
recordings.extend(data)

def command(*cmd):
recordings.extend(['command', list(cmd)[0], 'data', *list(cmd)[1:]])

serial.command.side_effect = command
serial.data.side_effect = data

# Use the same drawing primitives as the demo
with canvas(device) as draw:
primitives(device, draw)

assert serial.data.called
assert serial.command.called

# To regenerate test data, uncomment the following (remember not to commit though)
# ================================================================================
# from baseline_data import save_reference_data
# save_reference_data("demo_st7789", recordings)

assert recordings == get_reference_data('demo_st7789')

0 comments on commit 093e53f

Please sign in to comment.