ST7789: Add support for Waveshare 1.3inch LCD

pull/61/head
Peter Hinch 2024-02-13 09:51:36 +00:00
rodzic 521c7b553d
commit 172c976df0
2 zmienionych plików z 55 dodań i 41 usunięć

Wyświetl plik

@ -315,11 +315,11 @@ cross-platform but assume `micropython.viper` capability. They use 8-bit or
4-bit color to minimise the RAM used by the frame buffer.
Drivers for [Adafruit 1.8" display](https://www.adafruit.com/product/358).
* `st7735r.py` 8-bit color.
* `st7735r.py` 8-bit color.
* `st7735r_4bit.py` 4-bit color for further RAM reduction.
For [Adafruit 1.44" display](https://www.adafruit.com/product/2088).
* `st7735r144.py` 8-bit color.
* `st7735r144.py` 8-bit color.
* `st7735r144_4bit` 4 bit color.
Users of other ST7735R based displays should beware: there are many variants
@ -360,8 +360,8 @@ soft SPI may be used but hard may be faster.
* `init_spi=False` This optional arg enables flexible options in configuring
the SPI bus. See below.
#### The init_spi constructor arg
#### The init_spi constructor arg
The `False` default assumes exclusive access to the bus. It is initialised by
`color_setup.py` and those settings are left in place. If a callback function
is passed, it will be called prior to each SPI bus write. This is for shared
@ -419,7 +419,7 @@ On an ESP32 without SPIRAM, `nano-gui` runs but
frozen bytecode. The RP2 Pico runs both GUI's.
See [Color handling](./DRIVERS.md#11-color-handling) for details of the
implications of 4-bit color.
implications of 4-bit color.
The driver uses the `micropython.viper` decorator. If your platform does not
support this, the Viper code will need to be rewritten with a substantial hit
@ -524,6 +524,7 @@ Display types (values for `display`):
`TDISPLAY` For the TTGO T-Display and Waveshare Pico LCD.
`PI_PICO_LCD_2` Waveshare Pico LCD 2 determined by Mike Wilson.
`DFR0995` DFR0995 Contributed by @EdgarKluge
`WAVESHARE_13` Waveshare 1.3" 240x240 LCD contributed by Aaron Mittelmeier
### init_spi
@ -948,7 +949,7 @@ contiguous RAM is available.
### 4.4.1 Micropower applications
These comments largely assume a Pyboard host. The application should import
`upower` from
`upower` from
[micropython-micropower](https://github.com/peterhinch/micropython-micropower).
This turns the USB interface off if not in use to conserve power. It also
provides an `lpdelay` function to implement a delay using `pyb.stop()` to
@ -990,9 +991,9 @@ the CPU enabling user code to continue to run.
The standard refresh method blocks (monopolises the CPU) until refresh is
complete, adding an additional 2s delay. This enables the demo scripts to run
unchanged, with the 2s delay allowing the results to be seen before the next
refresh begins. This is fine for simple applications. The drivers also support
refresh begins. This is fine for simple applications. The drivers also support
concurrency with `uasyncio`. Such applications can perform other tasks while a
refresh is in progress. See
refresh is in progress. See
[EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).
Finally the [Waveshare 400x300 Pi Pico display](./DRIVERS.md#53-waveshare-400x300-pi-pico-display)
@ -1010,7 +1011,7 @@ An alternative is the
[wiring details](./DRIVERS.md#514-featherwing-wiring) listed below.
In my testing there are differences between these alternatives. The FeatherWing
shows a black border around the display. The reason for this is
shows a black border around the display. The reason for this is
[unclear](https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/11#issuecomment-763704622).
In development I encountered instances where the image on the flexible display
gradually degraded after the system was powered down. The white background
@ -1398,8 +1399,8 @@ The driver supports the WeAct Studio SSD1680 2.9 inch 296*128 pixel
[display](https://github.com/WeActStudio/WeActStudio.EpaperModule) that uses the
[SSD1680 driver](https://github.com/WeActStudio/WeActStudio.EpaperModule/blob/master/Doc/SSD1680.pdf).
This display lacks many features when compared to the ones from Waveshare,
two important examples are fast refresh and partial refresh. The big pro however is the price,
This display lacks many features when compared to the ones from Waveshare,
two important examples are fast refresh and partial refresh. The big pro however is the price,
it costs half the money of the Waveshare 2.9in alternative.
The driver is cross platform and supports landscape or portrait mode. To keep
@ -1556,7 +1557,7 @@ hardware.
## 7.4 Mapped drivers
In the simplest case the `FrameBuffer` mode is chosen to match a mode used by
the hardware. The `rgb` static method converts colors to that format and
the hardware. The `rgb` static method converts colors to that format and
`.show` writes it out.
In some cases this can result in a need for a large `FrameBuffer`, either

Wyświetl plik

@ -8,7 +8,7 @@
# https://www.adafruit.com/product/4313
# TTGO T-Display
# http://www.lilygo.cn/prod_view.aspx?TypeId=50044&Id=1126
# Based on
# Adfruit https://github.com/adafruit/Adafruit_CircuitPython_ST7789/blob/master/adafruit_st7789.py
# Also see st7735r_4bit.py for other source acknowledgements
@ -16,7 +16,7 @@
# SPI bus: default mode. Driver performs no read cycles.
# Datasheet table 6 p44 scl write cycle 16ns == 62.5MHz
from time import sleep_ms #, ticks_us, ticks_diff
from time import sleep_ms # , ticks_us, ticks_diff
import framebuf
import gc
import micropython
@ -32,19 +32,22 @@ PORTRAIT = 4
GENERIC = (0, 0, 0)
TDISPLAY = (52, 40, 1)
PI_PICO_LCD_2 = (0, 0, 1) # Waveshare Pico LCD 2 determined by Mike Wilson.
DFR0995 = (34, 0, 0) # DFR0995 Contributed by @EdgarKluge
DFR0995 = (34, 0, 0) # DFR0995 Contributed by @EdgarKluge
WAVESHARE_13 = (0, 0, 16) # Waveshare 1.3" 240x240 LCD contributed by Aaron Mittelmeier
@micropython.viper
def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int):
def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int):
# rgb565 - 16bit/pixel
n = 0
for x in range(length):
c = source[x]
dest[n] = lut[c >> 4] # current pixel
n += 1
dest[n] = lut[c & 0x0f] # next pixel
dest[n] = lut[c & 0x0F] # next pixel
n += 1
class ST7789(framebuf.FrameBuffer):
lut = bytearray(0xFF for _ in range(32)) # set all colors to BLACK
@ -55,13 +58,23 @@ class ST7789(framebuf.FrameBuffer):
# For some reason color must be inverted on this controller.
@staticmethod
def rgb(r, g, b):
return ((b & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (r & 0xf8)) ^ 0xffff
return ((b & 0xF8) << 5 | (g & 0x1C) << 11 | (g & 0xE0) >> 5 | (r & 0xF8)) ^ 0xFFFF
# rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=240, width=240,
disp_mode=LANDSCAPE, init_spi=False, display=GENERIC):
def __init__(
self,
spi,
cs,
dc,
rst,
height=240,
width=240,
disp_mode=LANDSCAPE,
init_spi=False,
display=GENERIC,
):
if not 0 <= disp_mode <= 7:
raise ValueError('Invalid display mode:', disp_mode)
raise ValueError("Invalid display mode:", disp_mode)
if not display in (GENERIC, TDISPLAY, PI_PICO_LCD_2):
print("WARNING: unsupported display parameter value.")
self._spi = spi # Clock cycle time for write 16ns 62.5MHz max (read is 150ns)
@ -121,16 +134,16 @@ class ST7789(framebuf.FrameBuffer):
self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd
wcd = self._wcd
cmd(b'\x01') # SW reset datasheet specifies 120ms before SLPOUT
cmd(b"\x01") # SW reset datasheet specifies 120ms before SLPOUT
sleep_ms(150)
cmd(b'\x11') # SLPOUT: exit sleep mode
cmd(b"\x11") # SLPOUT: exit sleep mode
sleep_ms(10) # Adafruit delay 500ms (datsheet 5ms)
wcd(b'\x3a', b'\x55') # _COLMOD 16 bit/pixel, 65Kbit color space
cmd(b'\x20') # INVOFF Adafruit turn inversion on. This driver fixes .rgb
cmd(b'\x13') # NORON Normal display mode
wcd(b"\x3a", b"\x55") # _COLMOD 16 bit/pixel, 65Kbit color space
cmd(b"\x20") # INVOFF Adafruit turn inversion on. This driver fixes .rgb
cmd(b"\x13") # NORON Normal display mode
# Table maps user request onto hardware values. index values:
# 0 Normal
# 0 Normal
# 1 Reflect
# 2 USD
# 3 USD reflect
@ -146,11 +159,11 @@ class ST7789(framebuf.FrameBuffer):
# PORTRAIT = 0x20
# REFLECT = 0x40
# USD = 0x80
mode = (0x60, 0xe0, 0xa0, 0x20, 0, 0x40, 0xc0, 0x80)[user_mode]
mode = (0x60, 0xE0, 0xA0, 0x20, 0, 0x40, 0xC0, 0x80)[user_mode]
# Set display window depending on mode, .height and .width.
self.set_window(mode)
wcd(b'\x36', int.to_bytes(mode, 1, 'little'))
cmd(b'\x29') # DISPON. Adafruit then delay 500ms.
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.
@ -166,7 +179,7 @@ class ST7789(framebuf.FrameBuffer):
xs = xoff
xe = wwd + xoff - 1
ys = yoff # y start
ye = wht + yoff - 1 # y end
ye = wht + yoff - 1 # y end
if mode & reflect:
ys = rwd - wht - yoff
ye = rwd - yoff - 1
@ -179,7 +192,7 @@ class ST7789(framebuf.FrameBuffer):
xs = xoff
xe = wwd + xoff - 1
ys = yoff # y start
ye = wht + yoff - 1 # y end
ye = wht + yoff - 1 # y end
if mode & usd:
ys = rht - wht - yoff
ye = rht - yoff - 1
@ -188,15 +201,15 @@ class ST7789(framebuf.FrameBuffer):
xe = rwd - xoff - 1
# Col address set.
self._wcd(b'\x2a', int.to_bytes((xs << 16) + xe, 4, 'big'))
self._wcd(b"\x2a", int.to_bytes((xs << 16) + xe, 4, "big"))
# Row address set
self._wcd(b'\x2b', int.to_bytes((ys << 16) + ye, 4, 'big'))
self._wcd(b"\x2b", int.to_bytes((ys << 16) + ye, 4, "big"))
#@micropython.native # Made virtually no difference to timing.
# @micropython.native # Made virtually no difference to timing.
def show(self): # Blocks for 83ms @60MHz SPI
# Blocks for 60ms @30MHz SPI on TTGO in PORTRAIT mode
# Blocks for 46ms @30MHz SPI on TTGO in LANDSCAPE mode
#ts = ticks_us()
# ts = ticks_us()
clut = ST7789.lut
wd = -(-self.width // 2) # Ceiling division for odd number widths
end = self.height * wd
@ -206,20 +219,20 @@ class ST7789(framebuf.FrameBuffer):
self._spi_init(self._spi) # Bus may be shared
self._dc(0)
self._cs(0)
self._spi.write(b'\x2c') # RAMWR
self._spi.write(b"\x2c") # RAMWR
self._dc(1)
for start in range(0, end, wd):
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
self._cs(1)
#print(ticks_diff(ticks_us(), ts))
# print(ticks_diff(ticks_us(), ts))
# Asynchronous refresh with support for reducing blocking time.
async def do_refresh(self, split=5):
async with self._lock:
lines, mod = divmod(self.height, split) # Lines per segment
if mod:
raise ValueError('Invalid do_refresh arg.')
raise ValueError("Invalid do_refresh arg.")
clut = ST7789.lut
wd = -(-self.width // 2)
lb = memoryview(self._linebuf)
@ -230,10 +243,10 @@ class ST7789(framebuf.FrameBuffer):
self._spi_init(self._spi) # Bus may be shared
self._dc(0)
self._cs(0)
self._spi.write(b'\x3c' if n else b'\x2c') # RAMWR/Write memory continue
self._spi.write(b"\x3c" if n else b"\x2c") # RAMWR/Write memory continue
self._dc(1)
for start in range(wd * line, wd * (line + lines), wd):
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
line += lines
self._cs(1)