From 67d423b760cb518bbfd1aa47be159235f5ca2912 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Mon, 26 Apr 2021 07:53:24 +0100 Subject: [PATCH] ST7735 replace offset arg with display. --- DRIVERS.md | 64 ++++++++++++++++++------------ color_setup/color_setup_ttgo.py | 14 ++----- color_setup/ssd7789.py | 3 +- drivers/st7789/st7789_4bit.py | 70 +++++++++++++++++++-------------- 4 files changed, 84 insertions(+), 67 deletions(-) diff --git a/DRIVERS.md b/DRIVERS.md index 8513e93..7580e10 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -380,18 +380,19 @@ colors. The resultant buffer size for the Adafruit displays is 28800 bytes. See [Color handling](./DRIVERS.md#11-color-handling) for the implications of 4-bit color. -[Tested display](https://www.adafruit.com/product/4313). The Adafruit -[1.54 inch](https://www.adafruit.com/product/3787) has identical resolution and -uses the same CircuitPython driver so can be expected to work. +[Tested display: Adafruit 1.3 inch](https://www.adafruit.com/product/4313). The +Adafruit [1.54 inch](https://www.adafruit.com/product/3787) has identical +resolution and uses the same CircuitPython driver so can be expected to work. + +The driver also supports the +[TTGO T-Display](http://www.lilygo.cn/claprod_view.aspx?TypeId=62&Id=1274). +This is an inexpensive ESP32 with a 135x240 color TFT display. The `color_setup.py` file should initialise the SPI bus with a baudrate of 30_000_000. Args `polarity`, `phase`, `bits`, `firstbit` are defaults. Hard or soft SPI may be used but hard may be faster. 30MHz is a conservative value: see below. An example file for the Pi Pico is in `color_setup/ssd7789.py`. -Note to existing users: the `disp_mode` args now behave as expected. This may -mean changing the constructor arg in your `color_setup.py`. - #### ST7789 Constructor args: * `spi` An initialised SPI bus instance. The chip supports clock rates of upto 62.5MHz (datasheet table 6). I have tested 60MHz. High speeds are sensitive to @@ -403,17 +404,30 @@ mean changing the constructor arg in your `color_setup.py`. `height` and `width` values: this ensures that `nano-gui` gets the correct aspect ratio. * `width=240` - * `disp_mode=0` By default the driver operates in landscape mode. This arg - enables portrait mode and other configurations. See below. + * `disp_mode=LANDSCAPE` This arg enables portrait mode and other + configurations. See below. * `init_spi=False` For shared SPI bus applications. See note below. - * `offset=(0, 0, 0)` This is intended for display hardware where the display - hardware's coordinate system is offset relative to the chip's RAM origin and - the case where the display hardware is configured in portrait mode. Elements - are `(x, y, p)` where `x` and `y` (being positive integers) represent the RAM - offset. `p==1` indicates display hardware which is in portrait mode. This - currently only applies to the TTGO T-Display. In practice, when other display - hardware is supported, this doc will specify the values to be used. Adafruit - uses the `(0, 0, 0)` default, TTGO uses `(52, 40, 1)`. + * `display=GENERIC` The `display` arg is an opaque type defining the display + hardware. Current options (exported by the driver) are `GENERIC` for Adafruit + displays and `TDISPLAY` for the TTGO board. + +#### Constants exported by the driver + +The `color_setup.py` file should invoke the driver as follows: +```python +from drivers.st7789.st7789_4bit import * +SSD = ST7789 +``` +The following constants are available: +Orientation (values for `disp_mode`): +`LANDSCAPE` Normal display, text is parallel to long axis. +`PORTRAIT` Text is parallel to short axis. +`USD` Upside down rendering. +`REFLECT` Mirror image rendering. + +Display types (values for `display`): +`GENERIC` For Adafruit displays. +`TDISPLAY` For the TTGO T-Display. ### init_spi @@ -432,20 +446,18 @@ def spi_init(spi): #### Display mode This is provided mainly to support asymmetrical displays. It also enables the -Adafruit display image to be rotated. +Adafruit display image to be rotated. Any of the orientation constants listed +above may be applied, and multiple options may be combined using the bitwise-or +`|` operator. + +When choosing `LANDSCAPE` or `PORTRAIT` mode it is essential that `height` and +`width` constructor args match the mode. -The driver exports the following constants: -```python -LANDSCAPE = 0 # Normal display -PORTRAIT = 0x20 # Rotate 90° -REFLECT = 0x40 # Swap pixels left-right -USD = 0x80 # Upside down: swap pixels top-bottom -``` -For non-standard modes these may be combined using the bitwise-or `|` operator. The following example `color_setup.py` is for Pi Pico and produces an upside down portrait display. ```python -from drivers.st7789.st7789_4bit import ST7789 as SSD, PORTRAIT, USD, REFLECT, LANDSCAPE +from drivers.st7789.st7789_4bit import * +SSD = ST7789 pdc = Pin(13, Pin.OUT, value=0) # Arbitrary pins pcs = Pin(14, Pin.OUT, value=1) diff --git a/color_setup/color_setup_ttgo.py b/color_setup/color_setup_ttgo.py index ca9fb5b..5a713a2 100644 --- a/color_setup/color_setup_ttgo.py +++ b/color_setup/color_setup_ttgo.py @@ -3,11 +3,6 @@ # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa -# Notes: Peter Hinch April 2021 -# UNDER DEVELOPMENT. This file and the ST7789 driver may change. -# These settings produce a landscape mode display with top left -# adjacent to pin 36. - # Supports: # TTGO T-Display 1.14" 135*240(Pixel) based on ST7789V # http://www.lilygo.cn/claprod_view.aspx?TypeId=62&Id=1274 @@ -46,7 +41,8 @@ BUTTON2 = 0 # left of the USB connector from machine import Pin, SPI, ADC import gc -from drivers.st7789.st7789_4bit import ST7789 as SSD, PORTRAIT, USD, REFLECT, LANDSCAPE +from drivers.st7789.st7789_4bit import * +SSD = ST7789 pdc = Pin(TFT_DC, Pin.OUT, value=0) # Arbitrary pins pcs = Pin(TFT_CS, Pin.OUT, value=1) @@ -57,12 +53,10 @@ gc.collect() # Precaution before instantiating framebuf # Conservative low baudrate. Can go to 62.5MHz. spi = SPI(1, 30_000_000, sck=Pin(TFT_SCLK), mosi=Pin(TFT_MOSI)) -# Display config. Tweak for TTGO. -OFFSET = (52, 40, 1) # Right way up landscape: defined as top left adjacent to pin 36 -ssd = SSD(spi, height=135, width=240, dc=pdc, cs=pcs, rst=prst, disp_mode=LANDSCAPE, offset=OFFSET) +ssd = SSD(spi, height=135, width=240, dc=pdc, cs=pcs, rst=prst, disp_mode=LANDSCAPE, display=TDISPLAY) # Normal portrait display: consistent with TTGO logo at top -# ssd = SSD(spi, height=240, width=135, dc=pdc, cs=pcs, rst=prst, disp_mode=PORTRAIT, offset=OFFSET) +# ssd = SSD(spi, height=240, width=135, dc=pdc, cs=pcs, rst=prst, disp_mode=PORTRAIT, display=TDISPLAY) # optional # b1 = Pin(BUTTON1, Pin.IN) diff --git a/color_setup/ssd7789.py b/color_setup/ssd7789.py index ddfc83d..292b772 100644 --- a/color_setup/ssd7789.py +++ b/color_setup/ssd7789.py @@ -26,7 +26,8 @@ from machine import Pin, SPI import gc -from drivers.st7789.st7789_4bit import ST7789 as SSD, PORTRAIT, USD, REFLECT, LANDSCAPE +from drivers.st7789.st7789_4bit import * +SSD = ST7789 pdc = Pin(13, Pin.OUT, value=0) # Arbitrary pins pcs = Pin(14, Pin.OUT, value=1) diff --git a/drivers/st7789/st7789_4bit.py b/drivers/st7789/st7789_4bit.py index 681cfb4..955f50e 100644 --- a/drivers/st7789/st7789_4bit.py +++ b/drivers/st7789/st7789_4bit.py @@ -7,6 +7,7 @@ # Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789 # https://www.adafruit.com/product/4313 # TTGO T-Display + # Based on # Adfruit https://github.com/adafruit/Adafruit_CircuitPython_ST7789/blob/master/adafruit_st7789.py # Also see st7735r_4bit.py for other source acknowledgements @@ -20,16 +21,14 @@ import gc import micropython import uasyncio as asyncio -# d7..d5 of MADCTL determine rotation/orientation datasheet P124, P231 -# d5 = MV row/col exchange -# d6 = MX col addr order -# d7 = MY page addr order -# These constants are also used as user specifiers for orientation. However -# mapping is required between user value and that presented to hardware. +# User orientation constants LANDSCAPE = 0 # Default -PORTRAIT = 0x20 -REFLECT = 0x40 -USD = 0x80 +REFLECT = 1 +USD = 2 +PORTRAIT = 4 +# Display types +GENERIC = (0, 0, 0) +TDISPLAY = (52, 40, 1) @micropython.viper def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int): @@ -61,24 +60,28 @@ class ST7789(framebuf.FrameBuffer): # rst and cs are active low, SPI is mode 0 def __init__(self, spi, cs, dc, rst, height=240, width=240, - disp_mode=0, init_spi=False, offset=(0, 0, 0)): + disp_mode=LANDSCAPE, init_spi=False, display=GENERIC): + if not 0 <= disp_mode <= 7: + raise ValueError('Invalid display mode:', disp_mode) + if not display in (GENERIC, TDISPLAY): + raise ValueError('Invalid display type.') self._spi = spi # Clock cycle time for write 16ns 62.5MHz max (read is 150ns) self._rst = rst # Pins self._dc = dc self._cs = cs self.height = height # Required by Writer class self.width = width - self._offset = offset + self._offset = display[:2] # display arg is (x, y, orientation) + orientation = display[2] # where x, y is the RAM offset self._spi_init = init_spi # Possible user callback self._lock = asyncio.Lock() mode = framebuf.GS4_HMSB # Use 4bit greyscale. gc.collect() - #buf = bytearray(height * width // 2) buf = bytearray(height * -(-width // 2)) # Ceiling division for odd widths self._mvb = memoryview(buf) super().__init__(buf, width, height, mode) self._linebuf = bytearray(self.width * 2) # 16 bit color out - self._init(disp_mode, offset[2]) + self._init(disp_mode, orientation) self.show() # Hardware reset @@ -112,7 +115,7 @@ class ST7789(framebuf.FrameBuffer): # Initialise the hardware. Blocks 163ms. Adafruit have various sleep delays # where I can find no requirement in the datasheet. I removed them with # other redundant code. - def _init(self, disp_mode, orientation): + def _init(self, user_mode, orientation): self._hwreset() # Hardware reset. Blocks 3ms if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared @@ -126,41 +129,48 @@ class ST7789(framebuf.FrameBuffer): cmd(b'\x20') # INVOFF Adafruit turn inversion on. This driver fixes .rgb cmd(b'\x13') # NORON Normal display mode - # Display modes correspond to values expected by hardware. Sometimes - # these have to be applied in combination to achieve what the user wants. - # Table entries map user request onto what is required. idx values: + # Table maps user request onto hardware values. index values: # 0 Normal # 1 Reflect # 2 USD # 3 USD reflect # Followed by same for LANDSCAPE - if orientation: # Display hardware is portrait mode - disp_mode ^= PORTRAIT - idx = disp_mode >> 6 if disp_mode & PORTRAIT else (disp_mode >> 6) + 4 - disp_mode = (0x60, 0xe0, 0xa0, 0x20, 0, 0x40, 0xc0, 0x80)[idx] + if not orientation: + user_mode ^= PORTRAIT + # Hardware mappings + # d7..d5 of MADCTL determine rotation/orientation datasheet P124, P231 + # d5 = MV row/col exchange + # d6 = MX col addr order + # d7 = MY page addr order + # LANDSCAPE = 0 + # PORTRAIT = 0x20 + # REFLECT = 0x40 + # USD = 0x80 + mode = (0x60, 0xe0, 0xa0, 0x20, 0, 0x40, 0xc0, 0x80)[user_mode] # Set display window depending on mode, .height and .width. - self.set_window(disp_mode) - wcd(b'\x36', int.to_bytes(disp_mode, 1, 'little')) + self.set_window(mode) + wcd(b'\x36', int.to_bytes(mode, 1, 'little')) cmd(b'\x29') # DISPON. Adafruit then delay 500ms. # Define the mapping between RAM and the display. # Datasheet section 8.12 p124. def set_window(self, mode): + portrait, reflect, usd = 0x20, 0x40, 0x80 rht = 320 rwd = 240 # RAM ht and width wht = self.height # Window (framebuf) dimensions. wwd = self.width # In portrait mode wht > wwd - if mode & PORTRAIT: + if mode & portrait: xoff = self._offset[1] # x and y transposed yoff = self._offset[0] xs = xoff xe = wwd + xoff - 1 ys = yoff # y start ye = wht + yoff - 1 # y end - if mode & REFLECT: + if mode & reflect: ys = rwd - wht - yoff ye = rwd - yoff - 1 - if mode & USD: + if mode & usd: xs = rht - wwd - xoff xe = rht - xoff - 1 else: # LANDSCAPE @@ -170,17 +180,17 @@ class ST7789(framebuf.FrameBuffer): xe = wwd + xoff - 1 ys = yoff # y start ye = wht + yoff - 1 # y end - if mode & USD: + if mode & usd: ys = rht - wht - yoff ye = rht - yoff - 1 - if mode & REFLECT: + if mode & reflect: xs = rwd - wwd - xoff xe = rwd - xoff - 1 # Col address set. - self._wcd(b'\x2a', int.to_bytes(xs, 2, 'big') + int.to_bytes(xe, 2, 'big')) + self._wcd(b'\x2a', int.to_bytes((xs << 16) + xe, 4, 'big')) # Row address set - self._wcd(b'\x2b', int.to_bytes(ys, 2, 'big') + int.to_bytes(ye, 2, 'big')) + self._wcd(b'\x2b', int.to_bytes((ys << 16) + ye, 4, 'big')) #@micropython.native # Made virtually no difference to timing. def show(self): # Blocks for 83ms @60MHz SPI