Add ILI9341 8-bit driver.

pull/80/head
Peter Hinch 2024-09-18 16:54:14 +01:00
rodzic 0fad050301
commit c464dc844e
3 zmienionych plików z 203 dodań i 20 usunięć

Wyświetl plik

@ -410,31 +410,35 @@ soft SPI may be used but hard may be faster. See note on overclocking below.
`height` and `width` values.
* `width=320`
* `usd=False` Upside down: set `True` to invert display.
* `init_spi=False` This optional arg enables flexible options in configuring
the SPI bus. The default assumes exclusive access to the bus. In this normal
case, `color_setup.py` initialises it and the settings will be left in place.
If the bus is shared with devices which require different settings, a callback
function should be passed. It will be called prior to each SPI bus write. The
callback will receive a single arg being the SPI bus instance. It will
typically be a one-liner or lambda initialising the bus. A minimal example is
this function:
```python
def spi_init(spi):
spi.init(baudrate=10_000_000)
```
* `init_spi=False` Allow bus sharing. See note below.
The ILI9341 class uses 4-bit color to conserve RAM. Even with this adaptation
the buffer size is 37.5KiB which is too large for some platforms. On a Pyboard
1.1 the `scale.py` demo ran with 34.5K free with no modules frozen, and with
47K free with `gui` and contents frozen. An ESP32 with SPIRAM has been tested.
On an ESP32 without SPIRAM, `nano-gui` runs but
#### Method (4-bit driver only)
* `greyscale(gs=None)` Setting `gs=True` enables the screen to be used to show
a full screen monochrome image. By default the frame buffer contents are
interpreted as color values. In greyscale mode the contents are treated as
greyscale values. This mode persists until the method is called with
`gs=False`. The method returns the current greyscale state. It is possible to
superimpose widgets on an image, but the mapping of colors onto the greyscale
may yield unexpected shades of grey. `WHITE` and `BLACK` work well. In
[micro-gui](https://github.com/peterhinch/micropython-micro-gui) and
[micropython-touch](https://github.com/peterhinch/micropython-touch) the
`after_open` method should be used to render the image to the framebuf and to
overlay any widgets.
The 4-bit driver uses four bits per pixel to conserve RAM. Even with this
adaptation the buffer size is 37.5KiB which is too large for some platforms. On
a Pyboard 1.1 the `scale.py` demo ran with 34.5K free with no modules frozen,
and with 47K free with `gui` and contents frozen. An ESP32 with SPIRAM has been
tested. On an ESP32 without SPIRAM, `nano-gui` runs but
[micro-gui](https://github.com/peterhinch/micropython-micro-gui) requires
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 8-bit driver enables color image display on
platforms with sufficient RAM: see [IMAGE_DISPLAY.md](./IMAGE_DISPLAY.md).
The driver uses the `micropython.viper` decorator. If your platform does not
The drivers use the `micropython.viper` decorator. If your platform does not
support this, the Viper code will need to be rewritten with a substantial hit
to performance.
@ -462,6 +466,20 @@ training makes me baulk at exceeding datasheet limits but the choice is yours.
I raised [this isse](https://github.com/adafruit/Adafruit_CircuitPython_ILI9341/issues/24).
The response may be of interest.
### The init_spi constructor arg
This optional arg enables flexible options in configuring the SPI bus. The
default assumes exclusive access to the bus. In this normal case,
`color_setup.py` initialises it and the settings will be left in place. If the
bus is shared with devices which require different settings, a callback function
should be passed. It will be called prior to each SPI bus write. The callback
will receive a single arg being the SPI bus instance. It will typically be a
one-liner or lambda initialising the bus. A minimal example is this function:
```python
def spi_init(spi):
spi.init(baudrate=10_000_000)
```
#### Troubleshooting
Some Chinese modules produce garbled displays. Please try the
@ -1408,7 +1426,10 @@ visually unobtrusive. They have the drawback of "ghosting" where remnants of the
previous image are visible. At any time a full update may be performed which
removes all trace of ghosting. This model of display has low levels of ghosting
and thus is supported by micro-gui. The model supports hosts other than the Pico
via a supplied cable.
via a supplied cable. Ghosting shows no sign of increasing with time: in a test
one of these displays ran the micro-gui demo for over 1,000 hours. This performs
partial updates only. The level of ghosting showed no sign of increasing with
time.
Two versions of this display exist. They require different drivers. The type of
a board may be distinguished as below, with the V2 board being the second

Wyświetl plik

@ -0,0 +1,161 @@
# ILI9341_8bit.py 8-bit nano-gui driver for ili9341 displays
# Copyright (c) Peter Hinch 2020-2024
# Released under the MIT license see LICENSE
# This work is based on the following sources.
# https://github.com/rdagger/micropython-ili9341
# Also this forum thread with ideas from @minyiky:
# https://forum.micropython.org/viewtopic.php?f=18&t=9368
from time import sleep_ms
import gc
import framebuf
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, length: int):
# rgb565 - 16bit/pixel
n: int = 0
while length:
c = source[n]
# Source byte holds 8-bit rrrgggbb
# source rrrgggbb
# dest 000bb000rrr00ggg
dest[n] = (c & 0xE0) | ((c & 0x1C) >> 2) | ((c & 0x03) << 11)
n += 1
length -= 1
class ILI9341(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# rrrgggbb. Converted to 16 bit on the fly.
@staticmethod
def rgb(r, g, b):
return (r & 0xE0) | ((g >> 3) & 0x1C) | (b >> 6)
# Transpose width & height for landscape mode
def __init__(self, spi, cs, dc, rst, height=240, width=320, usd=False, init_spi=False):
self._spi = spi
self._cs = cs
self._dc = dc
self._rst = rst
self.height = height
self.width = width
self._spi_init = init_spi
self.mode = framebuf.GS8
self.palette = BoolPalette(self.mode)
gc.collect()
buf = bytearray(height * width)
self.mvb = memoryview(buf)
super().__init__(buf, width, height, self.mode)
self._linebuf = bytearray(width * 2)
# Hardware reset
self._rst(0)
sleep_ms(50)
self._rst(1)
sleep_ms(50)
if self._spi_init: # A callback was passed
self._spi_init(spi) # Bus may be shared
self._lock = asyncio.Lock()
# Send initialization commands
self._wcmd(b"\x01") # SWRESET Software reset
sleep_ms(100)
self._wcd(b"\xcf", b"\x00\xC1\x30") # PWCTRB Pwr ctrl B
self._wcd(b"\xed", b"\x64\x03\x12\x81") # POSC Pwr on seq. ctrl
self._wcd(b"\xe8", b"\x85\x00\x78") # DTCA Driver timing ctrl A
self._wcd(b"\xcb", b"\x39\x2C\x00\x34\x02") # PWCTRA Pwr ctrl A
self._wcd(b"\xf7", b"\x20") # PUMPRC Pump ratio control
self._wcd(b"\xea", b"\x00\x00") # DTCB Driver timing ctrl B
self._wcd(b"\xc0", b"\x23") # PWCTR1 Pwr ctrl 1
self._wcd(b"\xc1", b"\x10") # PWCTR2 Pwr ctrl 2
self._wcd(b"\xc5", b"\x3E\x28") # VMCTR1 VCOM ctrl 1
self._wcd(b"\xc7", b"\x86") # VMCTR2 VCOM ctrl 2
# (b'\x88', b'\xe8', b'\x48', b'\x28')[rotation // 90]
if height > width:
self._wcd(b"\x36", b"\x48" if usd else b"\x88") # MADCTL: RGB portrait mode
else:
self._wcd(b"\x36", b"\x28" if usd else b"\xe8") # MADCTL: RGB landscape mode
self._wcd(b"\x37", b"\x00") # VSCRSADD Vertical scrolling start address
self._wcd(b"\x3a", b"\x55") # PIXFMT COLMOD: Pixel format 16 bits (MCU & interface)
self._wcd(b"\xb1", b"\x00\x18") # FRMCTR1 Frame rate ctrl
self._wcd(b"\xb6", b"\x08\x82\x27") # DFUNCTR
self._wcd(b"\xf2", b"\x00") # ENABLE3G Enable 3 gamma ctrl
self._wcd(b"\x26", b"\x01") # GAMMASET Gamma curve selected
# GMCTRP1
self._wcd(b"\xe0", b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00")
# GMCTRN1
self._wcd(b"\xe1", b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F")
self._wcmd(b"\x11") # SLPOUT Exit sleep
sleep_ms(100)
self._wcmd(b"\x29") # DISPLAY_ON
sleep_ms(100)
# Write a command.
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, command, data):
self._dc(0)
self._cs(0)
self._spi.write(command)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(data)
self._cs(1)
@micropython.native
def show(self):
wd = self.width
ht = self.height
lb = self._linebuf
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
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._wcmd(b"\x2c") # WRITE_RAM
self._dc(1)
self._cs(0)
for start in range(0, wd * ht, wd): # For each line
_lcopy(lb, buf[start:], wd) # Copy and map colors
self._spi.write(lb)
self._cs(1)
async def do_refresh(self, split=4):
async with self._lock:
lines, mod = divmod(self.height, split) # Lines per segment
if mod:
raise ValueError("Invalid do_refresh arg.")
wd = self.width
ht = self.height
lb = self._linebuf
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
self._wcmd(b"\x2c") # WRITE_RAM
self._dc(1)
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) # Copy and map colors
self._spi.write(lb)
line += lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)

Wyświetl plik

@ -1,6 +1,7 @@
{
"urls": [
["drivers/ili93xx/ili9341.py", "github:peterhinch/micropython-nano-gui/drivers/ili93xx/ili9341.py"],
["drivers/ili93xx/ili9341_8bit.py", "github:peterhinch/micropython-nano-gui/drivers/ili93xx/ili9341_8bit.py"],
["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"]
],
"version": "0.1"