Optimize ili9488 driver

The following optimizations to the ili9488 driver:

1) Change __init__ to use bit 5 in madctl register
   to swap row/column for landscape mode.

2) Eliminate functions _lscopy and _lscopy_gs.
   They are not needed because the ili9488 chip
   is handling orientation.
   Results in simplification in show() and
   do_refresh().

3) Minor optimizations to _lcopy and _lcopy_gs.
   Reverse order processing bytes.
   Specific changes:
   - change while condition from 'x < length' to
     simply 'length'. This test is faster in viper.
   - use length as index into source instead of x.
     Variable x is removed.
     This requires the source and dest to be
     processed from end to beginning.

   Changes to _lcopy and _lscopy reduce time for show() by
   about 4 milliseconds on my ESP32 and Pico2.
   Saving on Pico is about 6 milliseconds.
   About 1% or 2% improvement.
   Saving for do_refresh() was not measured, but is
   probably similar.

The performance improvement is small.
The reduction in code size is probably more significant.

Similar changes can be made to the ili9486 driver.
I do not have hardware to test.
pull/99/head
Carl Pottle 2025-04-07 12:32:02 -07:00
rodzic e136541191
commit 7557d367b4
1 zmienionych plików z 78 dodań i 171 usunięć

Wyświetl plik

@ -21,120 +21,66 @@ import framebuf
import asyncio
from drivers.boolpalette import BoolPalette
# Portrait mode greyscale
# Do processing from end to beginning for
# small performance improvement.
# greyscale
@micropython.viper
def _lcopy_gs(dest: ptr8, source: ptr8, length: int) :
# rgb666 - 18bit/pixel
n: int = 0
x: int = 0
while x < length:
c : uint = source[x]
n: int = length * 6 - 1
while length:
length -= 1
c : uint = source[length]
# Store the index in the 4 high order bits
p : uint = c & 0xF0 # current pixel
q : uint = c << 4 # next pixel
dest[n] = p
n += 1
dest[n] = p
n += 1
dest[n] = p
n += 1
dest[n] = q
n += 1
n -= 1
dest[n] = q
n += 1
n -= 1
dest[n] = q
n += 1
x += 1
n -= 1
# Portrait mode color
dest[n] = p
n -= 1
dest[n] = p
n -= 1
dest[n] = p
n -= 1
# Do processing from end to beginning for
# small performance improvement.
# color
@micropython.viper
def _lcopy(dest: ptr8, source: ptr8, lut: ptr16, length: int) :
# Convert lut rgb 565 to rgb666
n: int = 0
x: int = 0
while x < length:
c : uint = source[x]
p : uint = c >> 4 # current pixel
q = c & 0x0F # next pixel
n: int = length * 6 - 1
while length:
length -= 1
c : uint = source[length]
p : uint = c >> 4 # current pixel
q : uint = c & 0x0F # next pixel
v : uint16 = lut[p]
dest[n] = (v & 0xF800) >> 8 # R
n += 1
dest[n] = (v & 0x07E0) >> 3 # G
n += 1
dest[n] = (v & 0x001F) << 3 # B
n += 1
v = lut[q]
dest[n] = (v & 0xF800) >> 8 # R
n += 1
dest[n] = (v & 0x07E0) >> 3 # G
n += 1
dest[n] = (v & 0x001F) << 3 # B
n += 1
x += 1
# FB is in landscape mode greyscale
@micropython.viper
def _lscopy_gs(dest: ptr8, source: ptr8, ch: int) :
col = ch & 0x1FF # Unpack (viper old 4 parameter limit)
height = (ch >> 9) & 0x1FF
wbytes = ch >> 19 # Width in bytes is width // 2
# rgb666 - 18bit/pixel
n = 0
clsb = col & 1
idx = col >> 1 # 2 pixels per byte
while height:
if clsb :
c = source[idx] << 4
else :
c = source[idx] & 0xf0
dest[n] = c
n += 1
dest[n] = c
n += 1
dest[n] = c
n += 1
idx += wbytes
height -= 1
# FB is in landscape mode color, hence issue a column at a time to portrait mode hardware.
@micropython.viper
def _lscopy(dest: ptr8, source: ptr8, lut: ptr16, ch: int) :
# Convert lut rgb 565 to rgb666
col = ch & 0x1FF # Unpack (viper old 4 parameter limit)
height = (ch >> 9) & 0x1FF
wbytes = ch >> 19 # Width in bytes is width // 2
n = 0
clsb = col & 1
idx = col >> 1 # 2 pixels per byte
while height:
if clsb:
c = source[idx] & 0x0F
else:
c = source[idx] >> 4
v : uint16 = lut[c]
dest[n] = (v & 0xF800) >> 8 # R
n += 1
n -= 1
dest[n] = (v & 0x07E0) >> 3 # G
n += 1
n -= 1
dest[n] = (v & 0xF800) >> 8 # R
n -= 1
v : uint = lut[p]
dest[n] = (v & 0x001F) << 3 # B
n += 1
idx += wbytes
height -= 1
n -= 1
dest[n] = (v & 0x07E0) >> 3 # G
n -= 1
dest[n] = (v & 0xF800) >> 8 # R
n -= 1
class ILI9488(framebuf.FrameBuffer):
lut = bytearray(32)
COLOR_INVERT = 0
# Convert r, g, b in range 0-255 to a 16 bit colour value
@ -157,8 +103,6 @@ class ILI9488(framebuf.FrameBuffer):
self.lock_mode = False # If set, user lock is passed to .do_refresh
self.height = height # Logical dimensions for GUIs
self.width = width
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
self.mode = framebuf.GS4_HMSB
@ -167,7 +111,7 @@ class ILI9488(framebuf.FrameBuffer):
buf = bytearray(height * width // 2)
self.mvb = memoryview(buf)
super().__init__(buf, width, height, self.mode) # Logical aspect ratio
self._linebuf = bytearray(self._short * 3)
self._linebuf = bytearray(self.width * 3)
# Hardware reset
self._rst(0)
@ -184,18 +128,18 @@ class ILI9488(framebuf.FrameBuffer):
self._wcmd(b"\x11") # sleep out
sleep_ms(20)
self._wcd(b"\x3a", b"\x66") # interface pixel format 18 bits per pixel
# Normally use defaults. This allows it to work on the Waveshare board with a
# shift register. If size is not 320x480 assume no shift register.
# Default column address start == 0, end == 0x13F (319)
if self._short != 320: # Not the Waveshare board: no shift register
self._wcd(b"\x2a", int.to_bytes(self._short - 1, 4, "big"))
# Default page address start == 0 end == 0x1DF (479)
if self._long != 480:
self._wcd(b"\x2b", int.to_bytes(self._long - 1, 4, "big")) # SET_PAGE ht
# self._wcd(b"\x36", b"\x48" if usd else b"\x88") # MADCTL: RGB portrait mode
madctl = 0x48 if usd else 0x88
self._wcd(b"\x2a", int.to_bytes(self.width - 1, 4, "big"))
self._wcd(b"\x2b", int.to_bytes(self.height - 1, 4, "big")) # SET_PAGE ht
if self.width > self.height :
# landscape
madctl = 0xe8 if usd else 0x28
else :
#portrait
madctl = 0x48 if usd else 0x88
if mirror:
madctl ^= 0x80
madctl ^= 0x80 # toggle MY
self._wcd(b"\x36", madctl.to_bytes(1, "big")) # MADCTL: RGB portrait mode
self._wcmd(b"\x11") # sleep out
self._wcmd(b"\x29") # display on
@ -234,30 +178,16 @@ class ILI9488(framebuf.FrameBuffer):
self._wcmd(b"\x2c") # WRITE_RAM
self._dc(1)
self._cs(0)
if self.width < self.height: # Portrait 350 ms on ESP32 160 MHz, 26.6 MHz SPI clock
wd = self.width // 2
ht = self.height
if cm :
for start in range(0, wd * ht, wd): # For each line
_lcopy_gs(lb, buf[start:], wd) # Copy greyscale
self._spi.write(lb)
else :
for start in range(0, wd * ht, wd): # For each line
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
else: # Landscape 370 ms on ESP32 160 MHz, 26.6 MHz SPI clock
width = self.width
wd = width - 1
cargs = (self.height << 9) + (width << 18) # Viper 4-arg limit
if cm :
for col in range(width): # For each column of landscape display
_lscopy_gs(lb, buf, wd - col + cargs) # Copy greyscale
self._spi.write(lb)
else :
for col in range(width): # For each column of landscape display
_lscopy(lb, buf, clut, wd - col + cargs) # Copy and map colors
self._spi.write(lb)
wd = self.width // 2
ht = self.height
if cm :
for start in range(0, wd * ht, wd): # For each line
_lcopy_gs(lb, buf[start:], wd) # Copy greyscale
self._spi.write(lb)
else :
for start in range(0, wd * ht, wd): # For each line
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
self._cs(1)
def short_lock(self, v=None):
@ -271,7 +201,7 @@ class ILI9488(framebuf.FrameBuffer):
if elock is None:
elock = asyncio.Lock()
async with self._lock:
lines, mod = divmod(self._long, split) # Lines per segment
lines, mod = divmod(self.height, split) # Lines per segment
if mod:
raise ValueError("Invalid do_refresh arg.")
clut = ILI9488.lut
@ -280,45 +210,22 @@ class ILI9488(framebuf.FrameBuffer):
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
wd = self.width // 2
line = 0
for _ in range(split): # For each segment
async with elock:
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._cs(0)
if cm:
for start in range(wd * line, wd * (line + lines), wd): # For each line
_lcopy_gs(lb, buf[start:], wd) # Copy and greyscale
self._spi.write(lb)
else :
for start in range(wd * line, wd * (line + lines), wd): # For each line
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
wd = self.width // 2
line = 0
for _ in range(split): # For each segment
async with elock:
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._cs(0)
if cm:
for start in range(wd * line, wd * (line + lines), wd): # For each line
_lcopy_gs(lb, buf[start:], wd) # Copy and greyscale
self._spi.write(lb)
else :
for start in range(wd * line, wd * (line + lines), wd): # For each line
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
line += lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)
else: # Landscape: write sets of cols. lines is no. of cols per segment.
cargs = (self.height << 9) + (self.width << 18) # Viper 4-arg limit
sc = self.width - 1 # Start and end columns
ec = sc - lines # End column
for _ in range(split): # For each segment
async with elock:
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._cs(0)
if cm :
for col in range(sc, ec, -1): # For each column of landscape display
_lscopy_gs(lb, buf, col + cargs) # Copy and map colors
self._spi.write(lb)
else :
for col in range(sc, ec, -1): # For each column of landscape display
_lscopy(lb, buf, clut, col + cargs) # Copy and map colors
self._spi.write(lb)
sc -= lines
ec -= lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)
line += lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)