kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
Add gc9a01 driver.
rodzic
ff77aea008
commit
b077a443f7
29
README.md
29
README.md
|
@ -72,7 +72,8 @@ display.
|
||||||
3.7 [Class Textbox](./README.md#37-class-textbox) Scrolling text display.
|
3.7 [Class Textbox](./README.md#37-class-textbox) Scrolling text display.
|
||||||
4. [ESP8266](./README.md#4-esp8266) This can work. Contains information on
|
4. [ESP8266](./README.md#4-esp8266) This can work. Contains information on
|
||||||
minimising the RAM and flash footprints of the GUI.
|
minimising the RAM and flash footprints of the GUI.
|
||||||
[Appendix 1 Freezing bytecode](./README.md#appendix-1-freezing-bytecode) Optional way to save RAM.
|
[Appendix 1 Freezing bytecode](./README.md#appendix-1-freezing-bytecode) Optional way to save RAM.
|
||||||
|
[Appendix 2 Round displays](./README.md#appendix-2-round-displays) Alternative hardware check script.
|
||||||
|
|
||||||
#### [Supported displays](./DISPLAYS.md)
|
#### [Supported displays](./DISPLAYS.md)
|
||||||
|
|
||||||
|
@ -295,6 +296,7 @@ Demos for larger displays.
|
||||||
* `asnano_sync.py` Two Pyboard specific demos using the GUI with `asyncio`.
|
* `asnano_sync.py` Two Pyboard specific demos using the GUI with `asyncio`.
|
||||||
* `asnano.py` Could readily be adapted for other targets.
|
* `asnano.py` Could readily be adapted for other targets.
|
||||||
* `tbox.py` Demo `Textbox` class. Cross-platform.
|
* `tbox.py` Demo `Textbox` class. Cross-platform.
|
||||||
|
* `round.py` Demo for 240*240 circular displays.
|
||||||
|
|
||||||
Demos for ePaper displays:
|
Demos for ePaper displays:
|
||||||
* `epd_async.py` Demo of asynchronous code on an eInk display. Needs a large display.
|
* `epd_async.py` Demo of asynchronous code on an eInk display. Needs a large display.
|
||||||
|
@ -405,6 +407,9 @@ ssd.rect(0, 0, 15, 15, RED) # Red square at top left
|
||||||
ssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE) # Blue square at bottom right
|
ssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE) # Blue square at bottom right
|
||||||
refresh(ssd)
|
refresh(ssd)
|
||||||
```
|
```
|
||||||
|
For round displays please see
|
||||||
|
[Appendix 2 Round displays](./README.md#appendix-2-round-displays) for a
|
||||||
|
suitable hardware check script.
|
||||||
|
|
||||||
###### [Contents](./README.md#contents)
|
###### [Contents](./README.md#contents)
|
||||||
|
|
||||||
|
@ -455,7 +460,7 @@ wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) # Colors are defaults
|
||||||
wri.set_clip(True, True, False)
|
wri.set_clip(True, True, False)
|
||||||
```
|
```
|
||||||
|
|
||||||
Initialisation of monochorome text display follows. For each font a `Writer` instance
|
Initialisation of monochrome text display follows. For each font a `Writer` instance
|
||||||
is created:
|
is created:
|
||||||
```python
|
```python
|
||||||
from gui.core.writer import Writer # Renders color text
|
from gui.core.writer import Writer # Renders color text
|
||||||
|
@ -1023,3 +1028,23 @@ as I have experienced problems freezing display drivers - but feel free to
|
||||||
experiment.
|
experiment.
|
||||||
|
|
||||||
###### [Contents](./README.md#contents)
|
###### [Contents](./README.md#contents)
|
||||||
|
|
||||||
|
## Appendix 2 Round displays
|
||||||
|
|
||||||
|
The normal test script is unsuitable as the rectangles are off-screen. Please
|
||||||
|
paste this at the REPL to verify hardware and display orientation:
|
||||||
|
```Python
|
||||||
|
from color_setup import ssd # Create a display instance
|
||||||
|
from gui.core.colors import RED, BLUE, GREEN
|
||||||
|
from gui.core.nanogui import refresh, circle
|
||||||
|
refresh(ssd, True) # Initialise and clear display.
|
||||||
|
ssd.fill(0)
|
||||||
|
w = ssd.width
|
||||||
|
ssd.line(0, 0, w - 1, w - 1, GREEN) # Green diagonal corner-to-corner
|
||||||
|
offs = round(0.29289 * w / 2)
|
||||||
|
ssd.rect(offs, offs, 15, 15, RED) # Red square at top left
|
||||||
|
ssd.rect(w - offs - 15, w - offs - 15, 15, 15, BLUE) # Blue square at bottom right
|
||||||
|
circle(ssd, 119, 119, 119, GREEN)
|
||||||
|
refresh(ssd)
|
||||||
|
```
|
||||||
|
###### [Contents](./README.md#contents)
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
# gc9a01 nano-gui driver for gc9a01 displays
|
||||||
|
# Default args are for a 240*240 (typically circular) display
|
||||||
|
|
||||||
|
# Copyright (c) Peter Hinch 2024
|
||||||
|
# Released under the MIT license see LICENSE
|
||||||
|
|
||||||
|
from time import sleep_ms
|
||||||
|
import gc
|
||||||
|
import framebuf
|
||||||
|
import asyncio
|
||||||
|
from drivers.boolpalette import BoolPalette
|
||||||
|
|
||||||
|
# Initialisation ported from Russ Hughes' C driver
|
||||||
|
# https://github.com/russhughes/gc9a01_mpy/blob/main/src/gc9a01.h
|
||||||
|
# https://github.com/russhughes/gc9a01_mpy/blob/main/src/gc9a01.c
|
||||||
|
# Based on a ST7789 C driver: https://github.com/devbis/st7789_mpy
|
||||||
|
# Many registers are undocumented. Lines initialising them are commented "?"
|
||||||
|
# in cases where initialising them seems to have no effect.
|
||||||
|
|
||||||
|
# Datasheet 7.3.4 allows scl <= 100MHz
|
||||||
|
# Waveshare touch board https://www.waveshare.com/wiki/1.28inch_Touch_LCD has CST816S touch controller
|
||||||
|
# Touch controller uses I2C
|
||||||
|
|
||||||
|
# Portrait mode
|
||||||
|
@micropython.viper
|
||||||
|
def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int):
|
||||||
|
# rgb565 - 16bit/pixel
|
||||||
|
n: int = 0
|
||||||
|
x: int = 0
|
||||||
|
while length:
|
||||||
|
c = source[x] # Source byte holds two 4-bit colors
|
||||||
|
dest[n] = lut[c >> 4] # current pixel
|
||||||
|
n += 1
|
||||||
|
dest[n] = lut[c & 0x0F] # next pixel
|
||||||
|
n += 1
|
||||||
|
x += 1
|
||||||
|
length -= 1
|
||||||
|
|
||||||
|
|
||||||
|
class GC9A01(framebuf.FrameBuffer):
|
||||||
|
|
||||||
|
lut = bytearray(32) # Color LUT holds all possible 16-bit colors
|
||||||
|
|
||||||
|
# Convert r, g, b in range 0-255 to a 16 bit colour value
|
||||||
|
# LS byte goes into LUT offset 0, MS byte into offset 1
|
||||||
|
# Same mapping in linebuf so LS byte is shifted out 1st
|
||||||
|
# GC9A01 expects RGB order. 8 bit register writes require padding
|
||||||
|
@classmethod
|
||||||
|
def rgb(cls, r, g, b):
|
||||||
|
return (r & 0xF8) | ((g & 0xE0) >> 5) | ((g & 0x1C) << 11) | ((b & 0xF8) << 5)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
spi,
|
||||||
|
cs,
|
||||||
|
dc,
|
||||||
|
rst,
|
||||||
|
height=240,
|
||||||
|
width=240,
|
||||||
|
lscape=False,
|
||||||
|
usd=False,
|
||||||
|
mirror=False,
|
||||||
|
init_spi=False,
|
||||||
|
):
|
||||||
|
self._spi = spi
|
||||||
|
self._cs = cs
|
||||||
|
self._dc = dc
|
||||||
|
self._rst = rst
|
||||||
|
self.height = height # Logical dimensions for GUIs
|
||||||
|
self.width = width
|
||||||
|
self._spi_init = init_spi
|
||||||
|
mode = framebuf.GS4_HMSB
|
||||||
|
self.palette = BoolPalette(mode)
|
||||||
|
gc.collect()
|
||||||
|
buf = bytearray(height * width // 2) # Frame buffer
|
||||||
|
self._mvb = memoryview(buf)
|
||||||
|
super().__init__(buf, width, height, mode)
|
||||||
|
self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors)
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
sleep_ms(100)
|
||||||
|
self._wcd(b"\x2a", int.to_bytes(width - 1, 4, "big"))
|
||||||
|
# Default page address start == 0 end == 0xEF (239)
|
||||||
|
self._wcd(b"\x2b", int.to_bytes(height - 1, 4, "big")) # SET_PAGE ht
|
||||||
|
# **** Start of opaque chip setup ****
|
||||||
|
self._wcmd(b"\xEF") # Inter register enable 2
|
||||||
|
self._wcd(b"\xEB", b"\x14") # ?
|
||||||
|
self._wcmd(b"\xFE") # Inter register enable 1
|
||||||
|
self._wcmd(b"\xEF") # Inter register enable 2
|
||||||
|
self._wcd(b"\xEB", b"\x14") # ?
|
||||||
|
self._wcd(b"\x84", b"\x40") # ?
|
||||||
|
self._wcd(b"\x85", b"\xFF") # ?
|
||||||
|
self._wcd(b"\x87", b"\xFF") # ?
|
||||||
|
self._wcd(b"\x86", b"\xFF") # ?
|
||||||
|
self._wcd(b"\x88", b"\x0A") # ?
|
||||||
|
self._wcd(b"\x89", b"\x21") # ?
|
||||||
|
self._wcd(b"\x8A", b"\x00") # ?
|
||||||
|
self._wcd(b"\x8B", b"\x80") # ?
|
||||||
|
self._wcd(b"\x8C", b"\x01") # ?
|
||||||
|
self._wcd(b"\x8D", b"\x01") # ?
|
||||||
|
self._wcd(b"\x8E", b"\xFF") # ?
|
||||||
|
self._wcd(b"\x8F", b"\xFF") # ?
|
||||||
|
self._wcd(b"\xB6", b"\x00\x00") # Display function control
|
||||||
|
self._wcd(b"\x3A", b"\x55") # COLMOD
|
||||||
|
self._wcd(b"\x90", b"\x08\x08\x08\x08") # ?
|
||||||
|
self._wcd(b"\xBD", b"\x06") # ?
|
||||||
|
self._wcd(b"\xBC", b"\x00") # ?
|
||||||
|
self._wcd(b"\xFF", b"\x60\x01\x04") # ?
|
||||||
|
self._wcd(b"\xC3", b"\x13") # Vreg1a voltage Control
|
||||||
|
self._wcd(b"\xC4", b"\x13") # Vreg1b voltage Control
|
||||||
|
self._wcd(b"\xC9", b"\x22") # Vreg2a voltage Control
|
||||||
|
self._wcd(b"\xBE", b"\x11") # ?
|
||||||
|
self._wcd(b"\xE1", b"\x10\x0E") # ?
|
||||||
|
self._wcd(b"\xDF", b"\x21\x0c\x02") # ?
|
||||||
|
self._wcd(b"\xF0", b"\x45\x09\x08\x08\x26\x2A") # Gamma
|
||||||
|
self._wcd(b"\xF1", b"\x43\x70\x72\x36\x37\x6F") # Gamma
|
||||||
|
self._wcd(b"\xF2", b"\x45\x09\x08\x08\x26\x2A") # Gamma
|
||||||
|
self._wcd(b"\xF3", b"\x43\x70\x72\x36\x37\x6F") # Gamma
|
||||||
|
self._wcd(b"\xED", b"\x1B\x0B") # ?
|
||||||
|
self._wcd(b"\xAE", b"\x77") # ?
|
||||||
|
self._wcd(b"\xCD", b"\x63") # ?
|
||||||
|
self._wcd(b"\x70", b"\x07\x07\x04\x0E\x0F\x09\x07\x08\x03") # ?
|
||||||
|
self._wcd(b"\xE8", b"\x34") # Frame rate / dot inversion
|
||||||
|
self._wcd(b"\x62", b"\x18\x0D\x71\xED\x70\x70\x18\x0F\x71\xEF\x70\x70") # ?
|
||||||
|
self._wcd(b"\x63", b"\x18\x11\x71\xF1\x70\x70\x18\x13\x71\xF3\x70\x70") # ?
|
||||||
|
self._wcd(b"\x64", b"\x28\x29\xF1\x01\xF1\x00\x07") # ?
|
||||||
|
self._wcd(b"\x66", b"\x3C\x00\xCD\x67\x45\x45\x10\x00\x00\x00") # Undoc but needed
|
||||||
|
self._wcd(b"\x67", b"\x00\x3C\x00\x00\x00\x01\x54\x10\x32\x98") # Undoc but needed
|
||||||
|
self._wcd(b"\x74", b"\x10\x85\x80\x00\x00\x4E\x00") # ?
|
||||||
|
self._wcd(b"\x98", b"\x3e\x07") # ?
|
||||||
|
self._wcmd(b"\x35") # Tearing effect line on
|
||||||
|
self._wcmd(b"\x21") # Display inversion on ???
|
||||||
|
self._wcmd(b"\x11")
|
||||||
|
sleep_ms(120)
|
||||||
|
# *************************
|
||||||
|
|
||||||
|
# madctl reg 0x36 p127 6.2.18. b0-2 == 0. b3: color output BGR RGB/
|
||||||
|
# b4 == 0
|
||||||
|
# d5 row/col exchange
|
||||||
|
# d6 col address order
|
||||||
|
# d7 row address order
|
||||||
|
if lscape:
|
||||||
|
madctl = 0x28 if usd else 0xE8 # RGB landscape mode
|
||||||
|
else:
|
||||||
|
madctl = 0x48 if usd else 0x88 # RGB portrait mode
|
||||||
|
if mirror:
|
||||||
|
madctl ^= 0x80
|
||||||
|
self._wcd(b"\x36", madctl.to_bytes(1, "big")) # MADCTL: RGB portrait mode
|
||||||
|
self._wcmd(b"\x29") # display on
|
||||||
|
|
||||||
|
# Write a command.
|
||||||
|
def _wcmd(self, command):
|
||||||
|
self._dc(0)
|
||||||
|
self._cs(0)
|
||||||
|
self._spi.write(command)
|
||||||
|
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 # Made almost no difference to timing
|
||||||
|
def show(self): # Physical display is in portrait mode
|
||||||
|
clut = GC9A01.lut
|
||||||
|
lb = self._linebuf
|
||||||
|
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
|
||||||
|
self._dc(1)
|
||||||
|
self._cs(0)
|
||||||
|
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
|
||||||
|
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.")
|
||||||
|
clut = GC9A01.lut
|
||||||
|
lb = self._linebuf
|
||||||
|
buf = self._mvb
|
||||||
|
self._wcmd(b"\x2c") # WRITE_RAM
|
||||||
|
self._dc(1)
|
||||||
|
wd = self.width // 2
|
||||||
|
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:], 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)
|
|
@ -87,12 +87,10 @@ class ILI9341(framebuf.FrameBuffer):
|
||||||
self._wcd(b"\xb6", b"\x08\x82\x27") # DFUNCTR
|
self._wcd(b"\xb6", b"\x08\x82\x27") # DFUNCTR
|
||||||
self._wcd(b"\xf2", b"\x00") # ENABLE3G Enable 3 gamma ctrl
|
self._wcd(b"\xf2", b"\x00") # ENABLE3G Enable 3 gamma ctrl
|
||||||
self._wcd(b"\x26", b"\x01") # GAMMASET Gamma curve selected
|
self._wcd(b"\x26", b"\x01") # GAMMASET Gamma curve selected
|
||||||
self._wcd(
|
# GMCTRP1
|
||||||
b"\xe0", b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00"
|
self._wcd(b"\xe0", b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00")
|
||||||
) # GMCTRP1
|
# GMCTRN1
|
||||||
self._wcd(
|
self._wcd(b"\xe1", b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F")
|
||||||
b"\xe1", b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F"
|
|
||||||
) # GMCTRN1
|
|
||||||
self._wcmd(b"\x11") # SLPOUT Exit sleep
|
self._wcmd(b"\x11") # SLPOUT Exit sleep
|
||||||
sleep_ms(100)
|
sleep_ms(100)
|
||||||
self._wcmd(b"\x29") # DISPLAY_ON
|
self._wcmd(b"\x29") # DISPLAY_ON
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# st7789.py Driver for ST7789 LCD displays for nano-gui
|
# st7789.py Driver for ST7789 LCD displays for nano-gui
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa
|
# Copyright (c) 2021-2024 Peter Hinch, Ihor Nehrutsa
|
||||||
|
|
||||||
# Tested displays:
|
# Tested displays:
|
||||||
# Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789
|
# Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789
|
||||||
|
@ -39,13 +39,16 @@ WAVESHARE_13 = (0, 0, 16) # Waveshare 1.3" 240x240 LCD contributed by Aaron Mit
|
||||||
@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: int = 0
|
||||||
for x in range(length):
|
x: int = 0
|
||||||
|
while 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
|
||||||
|
x += 1
|
||||||
|
length -= 1
|
||||||
|
|
||||||
|
|
||||||
class ST7789(framebuf.FrameBuffer):
|
class ST7789(framebuf.FrameBuffer):
|
||||||
|
|
Ładowanie…
Reference in New Issue