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. 4-bit color to minimise the RAM used by the frame buffer.
Drivers for [Adafruit 1.8" display](https://www.adafruit.com/product/358). 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. * `st7735r_4bit.py` 4-bit color for further RAM reduction.
For [Adafruit 1.44" display](https://www.adafruit.com/product/2088). 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. * `st7735r144_4bit` 4 bit color.
Users of other ST7735R based displays should beware: there are many variants 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 * `init_spi=False` This optional arg enables flexible options in configuring
the SPI bus. See below. 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 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 `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 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. frozen bytecode. The RP2 Pico runs both GUI's.
See [Color handling](./DRIVERS.md#11-color-handling) for details of the 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 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 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. `TDISPLAY` For the TTGO T-Display and Waveshare Pico LCD.
`PI_PICO_LCD_2` Waveshare Pico LCD 2 determined by Mike Wilson. `PI_PICO_LCD_2` Waveshare Pico LCD 2 determined by Mike Wilson.
`DFR0995` DFR0995 Contributed by @EdgarKluge `DFR0995` DFR0995 Contributed by @EdgarKluge
`WAVESHARE_13` Waveshare 1.3" 240x240 LCD contributed by Aaron Mittelmeier
### init_spi ### init_spi
@ -948,7 +949,7 @@ contiguous RAM is available.
### 4.4.1 Micropower applications ### 4.4.1 Micropower applications
These comments largely assume a Pyboard host. The application should import These comments largely assume a Pyboard host. The application should import
`upower` from `upower` from
[micropython-micropower](https://github.com/peterhinch/micropython-micropower). [micropython-micropower](https://github.com/peterhinch/micropython-micropower).
This turns the USB interface off if not in use to conserve power. It also 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 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 The standard refresh method blocks (monopolises the CPU) until refresh is
complete, adding an additional 2s delay. This enables the demo scripts to run 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 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 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). [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).
Finally the [Waveshare 400x300 Pi Pico display](./DRIVERS.md#53-waveshare-400x300-pi-pico-display) 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. [wiring details](./DRIVERS.md#514-featherwing-wiring) listed below.
In my testing there are differences between these alternatives. The FeatherWing 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). [unclear](https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/11#issuecomment-763704622).
In development I encountered instances where the image on the flexible display In development I encountered instances where the image on the flexible display
gradually degraded after the system was powered down. The white background 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 [display](https://github.com/WeActStudio/WeActStudio.EpaperModule) that uses the
[SSD1680 driver](https://github.com/WeActStudio/WeActStudio.EpaperModule/blob/master/Doc/SSD1680.pdf). [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, 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, 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. it costs half the money of the Waveshare 2.9in alternative.
The driver is cross platform and supports landscape or portrait mode. To keep The driver is cross platform and supports landscape or portrait mode. To keep
@ -1556,7 +1557,7 @@ hardware.
## 7.4 Mapped drivers ## 7.4 Mapped drivers
In the simplest case the `FrameBuffer` mode is chosen to match a mode used by 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. `.show` writes it out.
In some cases this can result in a need for a large `FrameBuffer`, either 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 # https://www.adafruit.com/product/4313
# TTGO T-Display # TTGO T-Display
# http://www.lilygo.cn/prod_view.aspx?TypeId=50044&Id=1126 # http://www.lilygo.cn/prod_view.aspx?TypeId=50044&Id=1126
# Based on # Based on
# Adfruit https://github.com/adafruit/Adafruit_CircuitPython_ST7789/blob/master/adafruit_st7789.py # Adfruit https://github.com/adafruit/Adafruit_CircuitPython_ST7789/blob/master/adafruit_st7789.py
# Also see st7735r_4bit.py for other source acknowledgements # Also see st7735r_4bit.py for other source acknowledgements
@ -16,7 +16,7 @@
# SPI bus: default mode. Driver performs no read cycles. # SPI bus: default mode. Driver performs no read cycles.
# Datasheet table 6 p44 scl write cycle 16ns == 62.5MHz # 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 framebuf
import gc import gc
import micropython import micropython
@ -32,19 +32,22 @@ PORTRAIT = 4
GENERIC = (0, 0, 0) GENERIC = (0, 0, 0)
TDISPLAY = (52, 40, 1) TDISPLAY = (52, 40, 1)
PI_PICO_LCD_2 = (0, 0, 1) # Waveshare Pico LCD 2 determined by Mike Wilson. 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 @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 # rgb565 - 16bit/pixel
n = 0 n = 0
for x in range(length): for x in range(length):
c = source[x] c = source[x]
dest[n] = lut[c >> 4] # current pixel dest[n] = lut[c >> 4] # current pixel
n += 1 n += 1
dest[n] = lut[c & 0x0f] # next pixel dest[n] = lut[c & 0x0F] # next pixel
n += 1 n += 1
class ST7789(framebuf.FrameBuffer): class ST7789(framebuf.FrameBuffer):
lut = bytearray(0xFF for _ in range(32)) # set all colors to BLACK 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. # For some reason color must be inverted on this controller.
@staticmethod @staticmethod
def rgb(r, g, b): 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 # rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=240, width=240, def __init__(
disp_mode=LANDSCAPE, init_spi=False, display=GENERIC): self,
spi,
cs,
dc,
rst,
height=240,
width=240,
disp_mode=LANDSCAPE,
init_spi=False,
display=GENERIC,
):
if not 0 <= disp_mode <= 7: 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): if not display in (GENERIC, TDISPLAY, PI_PICO_LCD_2):
print("WARNING: unsupported display parameter value.") print("WARNING: unsupported display parameter value.")
self._spi = spi # Clock cycle time for write 16ns 62.5MHz max (read is 150ns) 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 self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd cmd = self._wcmd
wcd = self._wcd 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) 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) sleep_ms(10) # Adafruit delay 500ms (datsheet 5ms)
wcd(b'\x3a', b'\x55') # _COLMOD 16 bit/pixel, 65Kbit color space 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"\x20") # INVOFF Adafruit turn inversion on. This driver fixes .rgb
cmd(b'\x13') # NORON Normal display mode cmd(b"\x13") # NORON Normal display mode
# Table maps user request onto hardware values. index values: # Table maps user request onto hardware values. index values:
# 0 Normal # 0 Normal
# 1 Reflect # 1 Reflect
# 2 USD # 2 USD
# 3 USD reflect # 3 USD reflect
@ -146,11 +159,11 @@ class ST7789(framebuf.FrameBuffer):
# PORTRAIT = 0x20 # PORTRAIT = 0x20
# REFLECT = 0x40 # REFLECT = 0x40
# USD = 0x80 # 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. # Set display window depending on mode, .height and .width.
self.set_window(mode) self.set_window(mode)
wcd(b'\x36', int.to_bytes(mode, 1, 'little')) wcd(b"\x36", int.to_bytes(mode, 1, "little"))
cmd(b'\x29') # DISPON. Adafruit then delay 500ms. cmd(b"\x29") # DISPON. Adafruit then delay 500ms.
# Define the mapping between RAM and the display. # Define the mapping between RAM and the display.
# Datasheet section 8.12 p124. # Datasheet section 8.12 p124.
@ -166,7 +179,7 @@ class ST7789(framebuf.FrameBuffer):
xs = xoff xs = xoff
xe = wwd + xoff - 1 xe = wwd + xoff - 1
ys = yoff # y start ys = yoff # y start
ye = wht + yoff - 1 # y end ye = wht + yoff - 1 # y end
if mode & reflect: if mode & reflect:
ys = rwd - wht - yoff ys = rwd - wht - yoff
ye = rwd - yoff - 1 ye = rwd - yoff - 1
@ -179,7 +192,7 @@ class ST7789(framebuf.FrameBuffer):
xs = xoff xs = xoff
xe = wwd + xoff - 1 xe = wwd + xoff - 1
ys = yoff # y start ys = yoff # y start
ye = wht + yoff - 1 # y end ye = wht + yoff - 1 # y end
if mode & usd: if mode & usd:
ys = rht - wht - yoff ys = rht - wht - yoff
ye = rht - yoff - 1 ye = rht - yoff - 1
@ -188,15 +201,15 @@ class ST7789(framebuf.FrameBuffer):
xe = rwd - xoff - 1 xe = rwd - xoff - 1
# Col address set. # 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 # 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 def show(self): # Blocks for 83ms @60MHz SPI
# Blocks for 60ms @30MHz SPI on TTGO in PORTRAIT mode # Blocks for 60ms @30MHz SPI on TTGO in PORTRAIT mode
# Blocks for 46ms @30MHz SPI on TTGO in LANDSCAPE mode # Blocks for 46ms @30MHz SPI on TTGO in LANDSCAPE mode
#ts = ticks_us() # ts = ticks_us()
clut = ST7789.lut clut = ST7789.lut
wd = -(-self.width // 2) # Ceiling division for odd number widths wd = -(-self.width // 2) # Ceiling division for odd number widths
end = self.height * wd end = self.height * wd
@ -206,20 +219,20 @@ class ST7789(framebuf.FrameBuffer):
self._spi_init(self._spi) # Bus may be shared self._spi_init(self._spi) # Bus may be shared
self._dc(0) self._dc(0)
self._cs(0) self._cs(0)
self._spi.write(b'\x2c') # RAMWR self._spi.write(b"\x2c") # RAMWR
self._dc(1) self._dc(1)
for start in range(0, end, wd): for start in range(0, end, 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) self._spi.write(lb)
self._cs(1) self._cs(1)
#print(ticks_diff(ticks_us(), ts)) # print(ticks_diff(ticks_us(), ts))
# Asynchronous refresh with support for reducing blocking time. # Asynchronous refresh with support for reducing blocking time.
async def do_refresh(self, split=5): async def do_refresh(self, split=5):
async with self._lock: async with self._lock:
lines, mod = divmod(self.height, split) # Lines per segment lines, mod = divmod(self.height, split) # Lines per segment
if mod: if mod:
raise ValueError('Invalid do_refresh arg.') raise ValueError("Invalid do_refresh arg.")
clut = ST7789.lut clut = ST7789.lut
wd = -(-self.width // 2) wd = -(-self.width // 2)
lb = memoryview(self._linebuf) lb = memoryview(self._linebuf)
@ -230,10 +243,10 @@ class ST7789(framebuf.FrameBuffer):
self._spi_init(self._spi) # Bus may be shared self._spi_init(self._spi) # Bus may be shared
self._dc(0) self._dc(0)
self._cs(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) self._dc(1)
for start in range(wd * line, wd * (line + lines), wd): 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) self._spi.write(lb)
line += lines line += lines
self._cs(1) self._cs(1)