From c282cfa6c94fa21c2efce312a9ad57c2f6addf58 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Fri, 7 Jun 2024 09:58:10 +0100 Subject: [PATCH] Update ili9x and gc9a01 drivers for image display. --- drivers/gc9a01/gc9a01.py | 6 ++--- drivers/gc9a01/gc9a01_8_bit.py | 29 +++++++--------------- drivers/ili93xx/ili9341.py | 35 ++++++++++++++++++++------- drivers/ili94xx/ili9486.py | 44 +++++++++++++++++++++++----------- 4 files changed, 68 insertions(+), 46 deletions(-) diff --git a/drivers/gc9a01/gc9a01.py b/drivers/gc9a01/gc9a01.py index 016f814..747e4fc 100644 --- a/drivers/gc9a01/gc9a01.py +++ b/drivers/gc9a01/gc9a01.py @@ -81,7 +81,7 @@ class GC9A01(framebuf.FrameBuffer): self.palette = BoolPalette(mode) gc.collect() buf = bytearray(height * width // 2) # Frame buffer - self._mvb = memoryview(buf) + self.mvb = memoryview(buf) super().__init__(buf, width, height, mode) self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors) @@ -188,7 +188,7 @@ class GC9A01(framebuf.FrameBuffer): def show(self): # Physical display is in portrait mode clut = GC9A01.lut lb = self._linebuf - buf = self._mvb + buf = self.mvb if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared self._wcmd(b"\x2c") # WRITE_RAM @@ -209,7 +209,7 @@ class GC9A01(framebuf.FrameBuffer): raise ValueError("Invalid do_refresh arg.") clut = GC9A01.lut lb = self._linebuf - buf = self._mvb + buf = self.mvb self._wcmd(b"\x2c") # WRITE_RAM self._dc(1) wd = self.width // 2 diff --git a/drivers/gc9a01/gc9a01_8_bit.py b/drivers/gc9a01/gc9a01_8_bit.py index 6539f8b..5c447d4 100644 --- a/drivers/gc9a01/gc9a01_8_bit.py +++ b/drivers/gc9a01/gc9a01_8_bit.py @@ -23,17 +23,14 @@ from drivers.boolpalette import BoolPalette # g4 g3 g2 b7 b6 b5 b4 b3 r7 r6 r5 r4 r3 g7 g6 g5 @micropython.viper -def _lcopy(dest: ptr16, source: ptr8, length: int, gscale: bool): +def _lcopy(dest: ptr16, source: ptr8, length: int): # rgb565 - 16bit/pixel n: int = 0 while length: c = source[n] - if gscale: # Source byte holds 8-bit greyscale - # dest rrrr rggg gggb bbbb - dest[n] = (c & 0xF1) | (c >> 5) | ((c & 0x1C) << 11) | ((c & 0xF1) << 5) - else: # Source byte holds 8-bit rrrgggbb - # dest 000b b000 rrr0 0ggg - dest[n] = (c & 0xE0) | ((c & 0x1C) >> 2) | ((c & 0x03) << 11) + # Source byte holds 8-bit rrrgggbb + # dest 000b b000 rrr0 0ggg + dest[n] = (c & 0xE0) | ((c & 0x1C) >> 2) | ((c & 0x03) << 11) n += 1 length -= 1 @@ -66,12 +63,11 @@ class GC9A01(framebuf.FrameBuffer): self.height = height # Logical dimensions for GUIs self.width = width self._spi_init = init_spi - self._gscale = False # Interpret buffer as rrrgggbb color mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. self.palette = BoolPalette(mode) gc.collect() buf = bytearray(height * width) # Frame buffer - self._mvb = memoryview(buf) + self.mvb = memoryview(buf) super().__init__(buf, width, height, mode) self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors) @@ -171,14 +167,9 @@ class GC9A01(framebuf.FrameBuffer): self._spi.write(data) self._cs(1) - def greyscale(self, gs=None): - if gs is not None: - self._gscale = gs - return self._gscale - def show(self): # Physical display is in portrait mode lb = self._linebuf - buf = self._mvb + buf = self.mvb if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared self._wcmd(b"\x2c") # WRITE_RAM @@ -186,9 +177,8 @@ class GC9A01(framebuf.FrameBuffer): self._cs(0) wd = self.width ht = self.height - cm = self._gscale # color False, greyscale True for start in range(0, wd * ht, wd): # For each line - _lcopy(lb, buf[start:], wd, cm) # Copy and map colors + _lcopy(lb, buf[start:], wd) # Copy and map colors self._spi.write(lb) self._cs(1) @@ -198,18 +188,17 @@ class GC9A01(framebuf.FrameBuffer): if mod: raise ValueError("Invalid do_refresh arg.") lb = self._linebuf - buf = self._mvb + buf = self.mvb self._wcmd(b"\x2c") # WRITE_RAM self._dc(1) wd = self.width - cm = self._gscale # color False, greyscale True line = 0 for _ in range(split): # For each segment if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared self._cs(0) for start in range(wd * line, wd * (line + lines), wd): # For each line - _lcopy(lb, buf[start:], wd, cm) # Copy and map colors + _lcopy(lb, buf[start:], wd) # Copy and map colors self._spi.write(lb) line += lines self._cs(1) # Allow other tasks to use bus diff --git a/drivers/ili93xx/ili9341.py b/drivers/ili93xx/ili9341.py index 99b3104..778c1ab 100644 --- a/drivers/ili93xx/ili9341.py +++ b/drivers/ili93xx/ili9341.py @@ -15,17 +15,26 @@ import asyncio from drivers.boolpalette import BoolPalette +# Output RGB565 format, 16 bit/pixel: +# g4 g3 g2 b7 b6 b5 b4 b3 r7 r6 r5 r4 r3 g7 g6 g5 # ~80μs on RP2 @ 250MHz. @micropython.viper -def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int): +def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int, gscale: bool): # rgb565 - 16bit/pixel n: int = 0 x: int = 0 while length: c = source[x] - dest[n] = lut[c >> 4] # current pixel - n += 1 - dest[n] = lut[c & 0x0F] # next pixel + p = c >> 4 # current pixel + q = c & 0x0F # next pixel + if gscale: + dest[n] = p >> 1 | p << 4 | p << 9 | ((p & 0x01) << 15) + n += 1 + dest[n] = q >> 1 | q << 4 | q << 9 | ((q & 0x01) << 15) + else: + dest[n] = lut[p] # current pixel + n += 1 + dest[n] = lut[q] # next pixel n += 1 x += 1 length -= 1 @@ -52,11 +61,12 @@ class ILI9341(framebuf.FrameBuffer): self.height = height self.width = width self._spi_init = init_spi + self._gscale = False # Interpret buffer as index into color LUT mode = framebuf.GS4_HMSB self.palette = BoolPalette(mode) gc.collect() buf = bytearray(self.height * self.width // 2) - self._mvb = memoryview(buf) + self.mvb = memoryview(buf) super().__init__(buf, self.width, self.height, mode) self._linebuf = bytearray(self.width * 2) # Hardware reset @@ -118,6 +128,11 @@ class ILI9341(framebuf.FrameBuffer): self._spi.write(data) self._cs(1) + def greyscale(self, gs=None): + if gs is not None: + self._gscale = gs + return self._gscale + # Time (ESP32 stock freq) 196ms portrait, 185ms landscape. # mem free on ESP32 43472 bytes (vs 110192) @micropython.native @@ -125,8 +140,9 @@ class ILI9341(framebuf.FrameBuffer): clut = ILI9341.lut wd = self.width // 2 ht = self.height + cm = self._gscale # color False, greyscale True lb = self._linebuf - buf = self._mvb + buf = self.mvb if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared # Commands needed to start data write @@ -136,7 +152,7 @@ class ILI9341(framebuf.FrameBuffer): self._dc(1) self._cs(0) for start in range(0, wd * ht, wd): # For each line - _lcopy(lb, buf[start:], clut, wd) # Copy and map colors + _lcopy(lb, buf[start:], clut, wd, cm) # Copy and map colors self._spi.write(lb) self._cs(1) @@ -148,8 +164,9 @@ class ILI9341(framebuf.FrameBuffer): clut = ILI9341.lut wd = self.width // 2 ht = self.height + cm = self._gscale # color False, greyscale True lb = self._linebuf - buf = self._mvb + buf = self.mvb # Commands needed to start data write self._wcd(b"\x2a", int.to_bytes(self.width, 4, "big")) # SET_COLUMN self._wcd(b"\x2b", int.to_bytes(ht, 4, "big")) # SET_PAGE @@ -161,7 +178,7 @@ class ILI9341(framebuf.FrameBuffer): self._spi_init(self._spi) # Bus may be shared self._cs(0) for start in range(wd * line, wd * (line + lines), wd): # For each line - _lcopy(lb, buf[start:], clut, wd) # Copy and map colors + _lcopy(lb, buf[start:], clut, wd, cm) # Copy and map colors self._spi.write(lb) line += lines self._cs(1) # Allow other tasks to use bus diff --git a/drivers/ili94xx/ili9486.py b/drivers/ili94xx/ili9486.py index d6955e9..8461306 100644 --- a/drivers/ili94xx/ili9486.py +++ b/drivers/ili94xx/ili9486.py @@ -20,15 +20,22 @@ from drivers.boolpalette import BoolPalette # Portrait mode @micropython.viper -def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int): +def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int, gscale: bool): # rgb565 - 16bit/pixel n: int = 0 x: int = 0 while length: c = source[x] - dest[n] = lut[c >> 4] # current pixel - n += 1 - dest[n] = lut[c & 0x0F] # next pixel + p = c >> 4 # current pixel + q = c & 0x0F # next pixel + if gscale: + dest[n] = p >> 1 | p << 4 | p << 9 | ((p & 0x01) << 15) + n += 1 + dest[n] = q >> 1 | q << 4 | q << 9 | ((q & 0x01) << 15) + else: + dest[n] = lut[p] # current pixel + n += 1 + dest[n] = lut[q] # next pixel n += 1 x += 1 length -= 1 @@ -36,8 +43,8 @@ def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int): # FB is in landscape mode, hence issue a column at a time to portrait mode hardware. @micropython.viper -def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int): - col = ch & 0x1FF # Unpack (viper 4 parameter limit) +def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int, gscale: bool): + col = ch & 0x1FF # Unpack (viper old 4 parameter limit) height = (ch >> 9) & 0x1FF wbytes = ch >> 19 # Width in bytes is width // 2 # rgb565 - 16bit/pixel @@ -49,7 +56,8 @@ def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int): c = source[idx] & 0x0F else: c = source[idx] >> 4 - dest[n] = lut[c] # 16 bit transfer of rightmost 4-bit pixel + dest[n] = c >> 1 | c << 4 | c << 9 | ((c & 0x01) << 15) if gscale else lut[c] + # dest[n] = lut[c] # 16 bit transfer of rightmost 4-bit pixel n += 1 # 16 bit idx += wbytes height -= 1 @@ -83,11 +91,12 @@ class ILI9486(framebuf.FrameBuffer): self._long = max(height, width) # Physical dimensions of screen and aspect ratio self._short = min(height, width) self._spi_init = init_spi + self._gscale = False # Interpret buffer as index into color LUT mode = framebuf.GS4_HMSB self.palette = BoolPalette(mode) gc.collect() buf = bytearray(height * width // 2) - self._mvb = memoryview(buf) + self.mvb = memoryview(buf) super().__init__(buf, width, height, mode) # Logical aspect ratio self._linebuf = bytearray(self._short * 2) @@ -140,11 +149,17 @@ class ILI9486(framebuf.FrameBuffer): self._spi.write(data) self._cs(1) + def greyscale(self, gs=None): + if gs is not None: + self._gscale = gs + return self._gscale + # @micropython.native # Made almost no difference to timing def show(self): # Physical display is in portrait mode clut = ILI9486.lut lb = self._linebuf - buf = self._mvb + buf = self.mvb + cm = self._gscale # color False, greyscale True if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared self._wcmd(b"\x2c") # WRITE_RAM @@ -154,14 +169,14 @@ class ILI9486(framebuf.FrameBuffer): wd = self.width // 2 ht = self.height for start in range(0, wd * ht, wd): # For each line - _lcopy(lb, buf[start:], clut, wd) # Copy and map colors + _lcopy(lb, buf[start:], clut, wd, cm) # Copy and map colors self._spi.write(lb) else: # Landscpe 264ms on RP2 120MHz, 30MHz SPI clock width = self.width wd = width - 1 cargs = (self.height << 9) + (width << 18) # Viper 4-arg limit for col in range(width): # For each column of landscape display - _lscopy(lb, buf, clut, wd - col + cargs) # Copy and map colors + _lscopy(lb, buf, clut, wd - col + cargs, cm) # Copy and map colors self._spi.write(lb) self._cs(1) @@ -172,7 +187,8 @@ class ILI9486(framebuf.FrameBuffer): raise ValueError("Invalid do_refresh arg.") clut = ILI9486.lut lb = self._linebuf - buf = self._mvb + buf = self.mvb + cm = self._gscale # color False, greyscale True self._wcmd(b"\x2c") # WRITE_RAM self._dc(1) if self.width < self.height: # Portrait: write sets of rows @@ -183,7 +199,7 @@ class ILI9486(framebuf.FrameBuffer): self._spi_init(self._spi) # Bus may be shared self._cs(0) for start in range(wd * line, wd * (line + lines), wd): # For each line - _lcopy(lb, buf[start:], clut, wd) # Copy and map colors + _lcopy(lb, buf[start:], clut, wd, cm) # Copy and map colors self._spi.write(lb) line += lines self._cs(1) # Allow other tasks to use bus @@ -197,7 +213,7 @@ class ILI9486(framebuf.FrameBuffer): self._spi_init(self._spi) # Bus may be shared self._cs(0) for col in range(sc, ec, -1): # For each column of landscape display - _lscopy(lb, buf, clut, col + cargs) # Copy and map colors + _lscopy(lb, buf, clut, col + cargs, cm) # Copy and map colors self._spi.write(lb) sc -= lines ec -= lines