kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
Update ili9x and gc9a01 drivers for image display.
rodzic
b972e8a3aa
commit
1bd163d72d
|
@ -0,0 +1,81 @@
|
||||||
|
# 1. Displaying photo images
|
||||||
|
|
||||||
|
The display drivers in this repo were primarily designed for displaying geometric shapes
|
||||||
|
and fonts. With a minor update they may also be used for image display. The method used is
|
||||||
|
ideal for full screen images however with suitable user code smaller images may be
|
||||||
|
rendered. It is also possible to overlay an image with GUI controls, although transparency
|
||||||
|
is not supported.
|
||||||
|
|
||||||
|
The following notes apply
|
||||||
|
[nanogui](https://github.com/peterhinch/micropython-nano-gui)
|
||||||
|
[micro-gui](https://github.com/peterhinch/micropython-micro-gui) and
|
||||||
|
[micropython-touch](https://github.com/peterhinch/micropython-touch).
|
||||||
|
|
||||||
|
Images for display should be converted to a [netpbm format](https://en.wikipedia.org/wiki/Netpbm),
|
||||||
|
namely a `.pgm` file for a monochrome image or `.ppm` for color. This may be
|
||||||
|
done using a utility such as Gimp. Netpbm files use 8-bit values. These are then
|
||||||
|
converted to RGB565 for 16-bit drivers, RRRGGGBB for 8-bit color, or 4-bit
|
||||||
|
greyscale to enable a monochrome image to display on a 4-bit driver. This is
|
||||||
|
done using a CPython utility `img_cvt.py` documented below.
|
||||||
|
|
||||||
|
An updated driver has a `greyscale` method enabling the frame buffer contents to
|
||||||
|
be interpreted at show time as either color or greyscale. This
|
||||||
|
|
||||||
|
## 1.2 Supported drivers
|
||||||
|
|
||||||
|
Currently only gc9a01 drivers are supported.
|
||||||
|
|
||||||
|
## 1.3 Monochrome images
|
||||||
|
|
||||||
|
These may be displayed using 8-bit or 16-bit drivers by treating it as if it
|
||||||
|
were color: exporting the image from the graphics program as a `.ppm` color
|
||||||
|
image and using `img_cvt.py` to convert it to the correct color mode.
|
||||||
|
|
||||||
|
On 4-bit drivers the image should be exported as a `.pgm` greyscale;
|
||||||
|
`img_cvt.py` will convert it to 4-bit format. In testing this produced good
|
||||||
|
results.
|
||||||
|
|
||||||
|
## 1.4 Color images
|
||||||
|
|
||||||
|
These cannot be displayed on 4-bit drivers. On 8 or 16 bit drivers these should
|
||||||
|
be exported from the graphics program as a `.ppm` color image. Then `img_cvt.py`
|
||||||
|
is used to convert the file to the correct color mode.
|
||||||
|
|
||||||
|
## 1.5 Code samples
|
||||||
|
|
||||||
|
Files produced by `img_cvt.py` are binary files. The first four bytes comprise
|
||||||
|
two 16-bit integers defining the numbers of rows and columns in the image. The
|
||||||
|
following is an example of a full-screen image display in microgui or
|
||||||
|
micropython-touch:
|
||||||
|
```py
|
||||||
|
class MoonScreen(Screen):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def after_open(self):
|
||||||
|
fn = "test.bin" # Image created by`img_cvt.py`
|
||||||
|
# The following line is required if a 4-bit driver is in use
|
||||||
|
# ssd.greyscale(True)
|
||||||
|
with open(fn, "rb") as f:
|
||||||
|
_ = f.read(4) # Read and discard rows and cols
|
||||||
|
f.readinto(ssd.mvb) # Read the image into the frame buffer
|
||||||
|
```
|
||||||
|
On nano-gui:
|
||||||
|
```py
|
||||||
|
from color_setup import ssd # Create a display instance
|
||||||
|
from gui.core.nanogui import refresh
|
||||||
|
|
||||||
|
refresh(ssd) # Initialise display.
|
||||||
|
fn = "test.bin" # Image created by`img_cvt.py`
|
||||||
|
# The following line is required if a 4-bit driver is in use
|
||||||
|
# ssd.greyscale(True)
|
||||||
|
with open(fn, "rb") as f:
|
||||||
|
_ = f.read(4) # Read and discard rows and cols
|
||||||
|
f.readinto(ssd.mvb) # Read the image into the frame buffer
|
||||||
|
refresh(ssd)
|
||||||
|
```
|
||||||
|
These examples rely on the images being configured to precisely match the screen
|
||||||
|
size. In other cases the rows and cols values must be used to populate a subset
|
||||||
|
of the frame buffer pixels or to display a subset of the image pixels. Secondly
|
||||||
|
the built-in flash of some platforms can be slow. If there is a visible pause in
|
||||||
|
displaying the image this is likely to be the cause.
|
|
@ -81,7 +81,7 @@ class GC9A01(framebuf.FrameBuffer):
|
||||||
self.palette = BoolPalette(mode)
|
self.palette = BoolPalette(mode)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
buf = bytearray(height * width // 2) # Frame buffer
|
buf = bytearray(height * width // 2) # Frame buffer
|
||||||
self._mvb = memoryview(buf)
|
self.mvb = memoryview(buf)
|
||||||
super().__init__(buf, width, height, mode)
|
super().__init__(buf, width, height, mode)
|
||||||
self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors)
|
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
|
def show(self): # Physical display is in portrait mode
|
||||||
clut = GC9A01.lut
|
clut = GC9A01.lut
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
if self._spi_init: # A callback was passed
|
if self._spi_init: # A callback was passed
|
||||||
self._spi_init(self._spi) # Bus may be shared
|
self._spi_init(self._spi) # Bus may be shared
|
||||||
self._wcmd(b"\x2c") # WRITE_RAM
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
|
@ -209,7 +209,7 @@ class GC9A01(framebuf.FrameBuffer):
|
||||||
raise ValueError("Invalid do_refresh arg.")
|
raise ValueError("Invalid do_refresh arg.")
|
||||||
clut = GC9A01.lut
|
clut = GC9A01.lut
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
self._wcmd(b"\x2c") # WRITE_RAM
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
self._dc(1)
|
self._dc(1)
|
||||||
wd = self.width // 2
|
wd = self.width // 2
|
||||||
|
|
|
@ -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
|
# g4 g3 g2 b7 b6 b5 b4 b3 r7 r6 r5 r4 r3 g7 g6 g5
|
||||||
@micropython.viper
|
@micropython.viper
|
||||||
def _lcopy(dest: ptr16, source: ptr8, length: int, gscale: bool):
|
def _lcopy(dest: ptr16, source: ptr8, length: int):
|
||||||
# rgb565 - 16bit/pixel
|
# rgb565 - 16bit/pixel
|
||||||
n: int = 0
|
n: int = 0
|
||||||
while length:
|
while length:
|
||||||
c = source[n]
|
c = source[n]
|
||||||
if gscale: # Source byte holds 8-bit greyscale
|
# Source byte holds 8-bit rrrgggbb
|
||||||
# dest rrrr rggg gggb bbbb
|
# dest 000b b000 rrr0 0ggg
|
||||||
dest[n] = (c & 0xF1) | (c >> 5) | ((c & 0x1C) << 11) | ((c & 0xF1) << 5)
|
dest[n] = (c & 0xE0) | ((c & 0x1C) >> 2) | ((c & 0x03) << 11)
|
||||||
else: # Source byte holds 8-bit rrrgggbb
|
|
||||||
# dest 000b b000 rrr0 0ggg
|
|
||||||
dest[n] = (c & 0xE0) | ((c & 0x1C) >> 2) | ((c & 0x03) << 11)
|
|
||||||
n += 1
|
n += 1
|
||||||
length -= 1
|
length -= 1
|
||||||
|
|
||||||
|
@ -66,12 +63,11 @@ class GC9A01(framebuf.FrameBuffer):
|
||||||
self.height = height # Logical dimensions for GUIs
|
self.height = height # Logical dimensions for GUIs
|
||||||
self.width = width
|
self.width = width
|
||||||
self._spi_init = init_spi
|
self._spi_init = init_spi
|
||||||
self._gscale = False # Interpret buffer as rrrgggbb color
|
|
||||||
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||||
self.palette = BoolPalette(mode)
|
self.palette = BoolPalette(mode)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
buf = bytearray(height * width) # Frame buffer
|
buf = bytearray(height * width) # Frame buffer
|
||||||
self._mvb = memoryview(buf)
|
self.mvb = memoryview(buf)
|
||||||
super().__init__(buf, width, height, mode)
|
super().__init__(buf, width, height, mode)
|
||||||
self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors)
|
self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors)
|
||||||
|
|
||||||
|
@ -171,14 +167,9 @@ class GC9A01(framebuf.FrameBuffer):
|
||||||
self._spi.write(data)
|
self._spi.write(data)
|
||||||
self._cs(1)
|
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
|
def show(self): # Physical display is in portrait mode
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
if self._spi_init: # A callback was passed
|
if self._spi_init: # A callback was passed
|
||||||
self._spi_init(self._spi) # Bus may be shared
|
self._spi_init(self._spi) # Bus may be shared
|
||||||
self._wcmd(b"\x2c") # WRITE_RAM
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
|
@ -186,9 +177,8 @@ class GC9A01(framebuf.FrameBuffer):
|
||||||
self._cs(0)
|
self._cs(0)
|
||||||
wd = self.width
|
wd = self.width
|
||||||
ht = self.height
|
ht = self.height
|
||||||
cm = self._gscale # color False, greyscale True
|
|
||||||
for start in range(0, wd * ht, wd): # For each line
|
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._spi.write(lb)
|
||||||
self._cs(1)
|
self._cs(1)
|
||||||
|
|
||||||
|
@ -198,18 +188,17 @@ class GC9A01(framebuf.FrameBuffer):
|
||||||
if mod:
|
if mod:
|
||||||
raise ValueError("Invalid do_refresh arg.")
|
raise ValueError("Invalid do_refresh arg.")
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
self._wcmd(b"\x2c") # WRITE_RAM
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
self._dc(1)
|
self._dc(1)
|
||||||
wd = self.width
|
wd = self.width
|
||||||
cm = self._gscale # color False, greyscale True
|
|
||||||
line = 0
|
line = 0
|
||||||
for _ in range(split): # For each segment
|
for _ in range(split): # For each segment
|
||||||
if self._spi_init: # A callback was passed
|
if self._spi_init: # A callback was passed
|
||||||
self._spi_init(self._spi) # Bus may be shared
|
self._spi_init(self._spi) # Bus may be shared
|
||||||
self._cs(0)
|
self._cs(0)
|
||||||
for start in range(wd * line, wd * (line + lines), wd): # For each line
|
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)
|
self._spi.write(lb)
|
||||||
line += lines
|
line += lines
|
||||||
self._cs(1) # Allow other tasks to use bus
|
self._cs(1) # Allow other tasks to use bus
|
||||||
|
|
|
@ -15,17 +15,26 @@ import asyncio
|
||||||
from drivers.boolpalette import BoolPalette
|
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.
|
# ~80μs on RP2 @ 250MHz.
|
||||||
@micropython.viper
|
@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
|
# rgb565 - 16bit/pixel
|
||||||
n: int = 0
|
n: int = 0
|
||||||
x: int = 0
|
x: int = 0
|
||||||
while length:
|
while length:
|
||||||
c = source[x]
|
c = source[x]
|
||||||
dest[n] = lut[c >> 4] # current pixel
|
p = c >> 4 # current pixel
|
||||||
n += 1
|
q = c & 0x0F # next pixel
|
||||||
dest[n] = lut[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
|
n += 1
|
||||||
x += 1
|
x += 1
|
||||||
length -= 1
|
length -= 1
|
||||||
|
@ -52,11 +61,12 @@ class ILI9341(framebuf.FrameBuffer):
|
||||||
self.height = height
|
self.height = height
|
||||||
self.width = width
|
self.width = width
|
||||||
self._spi_init = init_spi
|
self._spi_init = init_spi
|
||||||
|
self._gscale = False # Interpret buffer as index into color LUT
|
||||||
mode = framebuf.GS4_HMSB
|
mode = framebuf.GS4_HMSB
|
||||||
self.palette = BoolPalette(mode)
|
self.palette = BoolPalette(mode)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
buf = bytearray(self.height * self.width // 2)
|
buf = bytearray(self.height * self.width // 2)
|
||||||
self._mvb = memoryview(buf)
|
self.mvb = memoryview(buf)
|
||||||
super().__init__(buf, self.width, self.height, mode)
|
super().__init__(buf, self.width, self.height, mode)
|
||||||
self._linebuf = bytearray(self.width * 2)
|
self._linebuf = bytearray(self.width * 2)
|
||||||
# Hardware reset
|
# Hardware reset
|
||||||
|
@ -118,6 +128,11 @@ class ILI9341(framebuf.FrameBuffer):
|
||||||
self._spi.write(data)
|
self._spi.write(data)
|
||||||
self._cs(1)
|
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.
|
# Time (ESP32 stock freq) 196ms portrait, 185ms landscape.
|
||||||
# mem free on ESP32 43472 bytes (vs 110192)
|
# mem free on ESP32 43472 bytes (vs 110192)
|
||||||
@micropython.native
|
@micropython.native
|
||||||
|
@ -125,8 +140,9 @@ class ILI9341(framebuf.FrameBuffer):
|
||||||
clut = ILI9341.lut
|
clut = ILI9341.lut
|
||||||
wd = self.width // 2
|
wd = self.width // 2
|
||||||
ht = self.height
|
ht = self.height
|
||||||
|
cm = self._gscale # color False, greyscale True
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
if self._spi_init: # A callback was passed
|
if self._spi_init: # A callback was passed
|
||||||
self._spi_init(self._spi) # Bus may be shared
|
self._spi_init(self._spi) # Bus may be shared
|
||||||
# Commands needed to start data write
|
# Commands needed to start data write
|
||||||
|
@ -136,7 +152,7 @@ class ILI9341(framebuf.FrameBuffer):
|
||||||
self._dc(1)
|
self._dc(1)
|
||||||
self._cs(0)
|
self._cs(0)
|
||||||
for start in range(0, wd * ht, wd): # For each line
|
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._spi.write(lb)
|
||||||
self._cs(1)
|
self._cs(1)
|
||||||
|
|
||||||
|
@ -148,8 +164,9 @@ class ILI9341(framebuf.FrameBuffer):
|
||||||
clut = ILI9341.lut
|
clut = ILI9341.lut
|
||||||
wd = self.width // 2
|
wd = self.width // 2
|
||||||
ht = self.height
|
ht = self.height
|
||||||
|
cm = self._gscale # color False, greyscale True
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
# Commands needed to start data write
|
# Commands needed to start data write
|
||||||
self._wcd(b"\x2a", int.to_bytes(self.width, 4, "big")) # SET_COLUMN
|
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
|
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._spi_init(self._spi) # Bus may be shared
|
||||||
self._cs(0)
|
self._cs(0)
|
||||||
for start in range(wd * line, wd * (line + lines), wd): # For each line
|
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)
|
self._spi.write(lb)
|
||||||
line += lines
|
line += lines
|
||||||
self._cs(1) # Allow other tasks to use bus
|
self._cs(1) # Allow other tasks to use bus
|
||||||
|
|
|
@ -20,15 +20,22 @@ from drivers.boolpalette import BoolPalette
|
||||||
|
|
||||||
# Portrait mode
|
# Portrait mode
|
||||||
@micropython.viper
|
@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
|
# rgb565 - 16bit/pixel
|
||||||
n: int = 0
|
n: int = 0
|
||||||
x: int = 0
|
x: int = 0
|
||||||
while length:
|
while length:
|
||||||
c = source[x]
|
c = source[x]
|
||||||
dest[n] = lut[c >> 4] # current pixel
|
p = c >> 4 # current pixel
|
||||||
n += 1
|
q = c & 0x0F # next pixel
|
||||||
dest[n] = lut[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
|
n += 1
|
||||||
x += 1
|
x += 1
|
||||||
length -= 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.
|
# FB is in landscape mode, hence issue a column at a time to portrait mode hardware.
|
||||||
@micropython.viper
|
@micropython.viper
|
||||||
def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int):
|
def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int, gscale: bool):
|
||||||
col = ch & 0x1FF # Unpack (viper 4 parameter limit)
|
col = ch & 0x1FF # Unpack (viper old 4 parameter limit)
|
||||||
height = (ch >> 9) & 0x1FF
|
height = (ch >> 9) & 0x1FF
|
||||||
wbytes = ch >> 19 # Width in bytes is width // 2
|
wbytes = ch >> 19 # Width in bytes is width // 2
|
||||||
# rgb565 - 16bit/pixel
|
# rgb565 - 16bit/pixel
|
||||||
|
@ -49,7 +56,8 @@ def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int):
|
||||||
c = source[idx] & 0x0F
|
c = source[idx] & 0x0F
|
||||||
else:
|
else:
|
||||||
c = source[idx] >> 4
|
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
|
n += 1 # 16 bit
|
||||||
idx += wbytes
|
idx += wbytes
|
||||||
height -= 1
|
height -= 1
|
||||||
|
@ -83,11 +91,12 @@ class ILI9486(framebuf.FrameBuffer):
|
||||||
self._long = max(height, width) # Physical dimensions of screen and aspect ratio
|
self._long = max(height, width) # Physical dimensions of screen and aspect ratio
|
||||||
self._short = min(height, width)
|
self._short = min(height, width)
|
||||||
self._spi_init = init_spi
|
self._spi_init = init_spi
|
||||||
|
self._gscale = False # Interpret buffer as index into color LUT
|
||||||
mode = framebuf.GS4_HMSB
|
mode = framebuf.GS4_HMSB
|
||||||
self.palette = BoolPalette(mode)
|
self.palette = BoolPalette(mode)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
buf = bytearray(height * width // 2)
|
buf = bytearray(height * width // 2)
|
||||||
self._mvb = memoryview(buf)
|
self.mvb = memoryview(buf)
|
||||||
super().__init__(buf, width, height, mode) # Logical aspect ratio
|
super().__init__(buf, width, height, mode) # Logical aspect ratio
|
||||||
self._linebuf = bytearray(self._short * 2)
|
self._linebuf = bytearray(self._short * 2)
|
||||||
|
|
||||||
|
@ -140,11 +149,17 @@ class ILI9486(framebuf.FrameBuffer):
|
||||||
self._spi.write(data)
|
self._spi.write(data)
|
||||||
self._cs(1)
|
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
|
# @micropython.native # Made almost no difference to timing
|
||||||
def show(self): # Physical display is in portrait mode
|
def show(self): # Physical display is in portrait mode
|
||||||
clut = ILI9486.lut
|
clut = ILI9486.lut
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
|
cm = self._gscale # color False, greyscale True
|
||||||
if self._spi_init: # A callback was passed
|
if self._spi_init: # A callback was passed
|
||||||
self._spi_init(self._spi) # Bus may be shared
|
self._spi_init(self._spi) # Bus may be shared
|
||||||
self._wcmd(b"\x2c") # WRITE_RAM
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
|
@ -154,14 +169,14 @@ class ILI9486(framebuf.FrameBuffer):
|
||||||
wd = self.width // 2
|
wd = self.width // 2
|
||||||
ht = self.height
|
ht = self.height
|
||||||
for start in range(0, wd * ht, wd): # For each line
|
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._spi.write(lb)
|
||||||
else: # Landscpe 264ms on RP2 120MHz, 30MHz SPI clock
|
else: # Landscpe 264ms on RP2 120MHz, 30MHz SPI clock
|
||||||
width = self.width
|
width = self.width
|
||||||
wd = width - 1
|
wd = width - 1
|
||||||
cargs = (self.height << 9) + (width << 18) # Viper 4-arg limit
|
cargs = (self.height << 9) + (width << 18) # Viper 4-arg limit
|
||||||
for col in range(width): # For each column of landscape display
|
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._spi.write(lb)
|
||||||
self._cs(1)
|
self._cs(1)
|
||||||
|
|
||||||
|
@ -172,7 +187,8 @@ class ILI9486(framebuf.FrameBuffer):
|
||||||
raise ValueError("Invalid do_refresh arg.")
|
raise ValueError("Invalid do_refresh arg.")
|
||||||
clut = ILI9486.lut
|
clut = ILI9486.lut
|
||||||
lb = self._linebuf
|
lb = self._linebuf
|
||||||
buf = self._mvb
|
buf = self.mvb
|
||||||
|
cm = self._gscale # color False, greyscale True
|
||||||
self._wcmd(b"\x2c") # WRITE_RAM
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
self._dc(1)
|
self._dc(1)
|
||||||
if self.width < self.height: # Portrait: write sets of rows
|
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._spi_init(self._spi) # Bus may be shared
|
||||||
self._cs(0)
|
self._cs(0)
|
||||||
for start in range(wd * line, wd * (line + lines), wd): # For each line
|
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)
|
self._spi.write(lb)
|
||||||
line += lines
|
line += lines
|
||||||
self._cs(1) # Allow other tasks to use bus
|
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._spi_init(self._spi) # Bus may be shared
|
||||||
self._cs(0)
|
self._cs(0)
|
||||||
for col in range(sc, ec, -1): # For each column of landscape display
|
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)
|
self._spi.write(lb)
|
||||||
sc -= lines
|
sc -= lines
|
||||||
ec -= lines
|
ec -= lines
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
# As written, supports:
|
# As written, supports:
|
||||||
# gc9a01 240x240 circular display on Pi Pico
|
# gc9a01 240x240 circular display on Pi Pico
|
||||||
|
# Pin mapping is for Waveshare RP2040-Touch-LCD-1.28
|
||||||
# Edit the driver import for other displays.
|
# Edit the driver import for other displays.
|
||||||
|
|
||||||
# Demo of initialisation procedure designed to minimise risk of memory fail
|
# Demo of initialisation procedure designed to minimise risk of memory fail
|
||||||
|
@ -26,13 +27,13 @@ from machine import Pin, SPI
|
||||||
import gc
|
import gc
|
||||||
from drivers.gc9a01.gc9a01 import GC9A01 as SSD
|
from drivers.gc9a01.gc9a01 import GC9A01 as SSD
|
||||||
|
|
||||||
# from drivers.gc9a01.gc9a01_8_bit import GC9A01 as SSD
|
from drivers.gc9a01.gc9a01_8_bit import GC9A01 as SSD
|
||||||
|
|
||||||
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins
|
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins
|
||||||
prst = Pin(9, Pin.OUT, value=1)
|
prst = Pin(13, Pin.OUT, value=1)
|
||||||
pcs = Pin(10, Pin.OUT, value=1)
|
pcs = Pin(9, Pin.OUT, value=1)
|
||||||
|
|
||||||
gc.collect() # Precaution before instantiating framebuf
|
gc.collect() # Precaution before instantiating framebuf
|
||||||
# See DRIVERS.md
|
# See DRIVERS.md
|
||||||
spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=33_000_000)
|
spi = SPI(1, sck=Pin(10), mosi=Pin(11), miso=Pin(12), baudrate=33_000_000)
|
||||||
ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst, lscape=False, usd=False, mirror=False)
|
ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst, lscape=False, usd=False, mirror=False)
|
||||||
|
|
Ładowanie…
Reference in New Issue