forked from peterhinch/micropython-nano-gui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2fdabc5
commit 108f3d9
Showing
5 changed files
with
351 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
# gc9a01_8_bit.py nano-gui driver for gc9a01 displays using 8 bit pixels | ||
# Default args are for a 240*240 (typically circular) display. This will result | ||
# in a 57,600 byte frame buffer. | ||
|
||
# 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/ | ||
# 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 | ||
|
||
# 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): | ||
# 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) | ||
n += 1 | ||
length -= 1 | ||
|
||
|
||
class GC9A01(framebuf.FrameBuffer): | ||
# Convert r, g, b in range 0-255 to an 8 bit colour value | ||
# rrrgggbb. Converted to 16 bit on the fly. | ||
# GC9A01 expects RGB order. | ||
@staticmethod | ||
def rgb(r, g, b): | ||
return (r & 0xE0) | ((g >> 3) & 0x1C) | (b >> 6) | ||
|
||
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 | ||
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) | ||
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() # Prevent concurrent refreshes. | ||
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) | ||
|
||
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 | ||
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 | ||
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 | ||
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.") | ||
lb = self._linebuf | ||
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 | ||
self._spi.write(lb) | ||
line += lines | ||
self._cs(1) # Allow other tasks to use bus | ||
await asyncio.sleep_ms(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"urls": [ | ||
["drivers/gc9a01/gc9a01.py", "github:peterhinch/micropython-nano-gui/drivers/gc9a01/gc9a01.py"], | ||
["drivers/gc9a01/gc9a01_8_bit.py", "github:peterhinch/micropython-nano-gui/drivers/gc9a01/gc9a01_8_bit.py"], | ||
["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] | ||
], | ||
"version": "0.1" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# round.py Test/demo of scale widget for nano-gui on round gc9a01 screen | ||
|
||
# Released under the MIT License (MIT). See LICENSE. | ||
# Copyright (c) 2024 Peter Hinch | ||
|
||
# Usage: | ||
# import gui.demos.round | ||
|
||
# Initialise hardware and framebuf before importing modules. | ||
# Uses asyncio and also the asynchronous do_refresh method if the driver | ||
# supports it. | ||
|
||
from color_setup import ssd # Create a display instance | ||
|
||
from gui.core.nanogui import refresh | ||
from gui.core.writer import CWriter | ||
|
||
import asyncio | ||
from gui.core.colors import * | ||
import gui.fonts.arial10 as arial10 | ||
from gui.widgets.label import Label | ||
from gui.widgets.scale import Scale | ||
|
||
# COROUTINES | ||
async def radio(scale): | ||
cv = 88.0 # Current value | ||
val = 108.0 # Target value | ||
while True: | ||
v1, v2 = val, cv | ||
steps = 200 | ||
delta = (val - cv) / steps | ||
for _ in range(steps): | ||
cv += delta | ||
# Map user variable to -1.0..+1.0 | ||
scale.value(2 * (cv - 88) / (108 - 88) - 1) | ||
await asyncio.sleep_ms(200) | ||
val, cv = v2, v1 | ||
|
||
|
||
async def default(scale, lbl): | ||
cv = -1.0 # Current | ||
val = 1.0 | ||
while True: | ||
v1, v2 = val, cv | ||
steps = 400 | ||
delta = (val - cv) / steps | ||
for _ in range(steps): | ||
cv += delta | ||
scale.value(cv) | ||
lbl.value("{:4.3f}".format(cv)) | ||
if hasattr(ssd, "do_refresh"): | ||
# Option to reduce asyncio latency | ||
await ssd.do_refresh() | ||
else: | ||
# Normal synchronous call | ||
refresh(ssd) | ||
await asyncio.sleep_ms(250) | ||
val, cv = v2, v1 | ||
|
||
|
||
def test(): | ||
def tickcb(f, c): | ||
if f > 0.8: | ||
return RED | ||
if f < -0.8: | ||
return BLUE | ||
return c | ||
|
||
def legendcb(f): | ||
return "{:2.0f}".format(88 + ((f + 1) / 2) * (108 - 88)) | ||
|
||
refresh(ssd, True) # Initialise and clear display. | ||
CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it | ||
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) | ||
wri.set_clip(True, True, False) | ||
scale1 = Scale(wri, 64, 64, width=124, legendcb=legendcb, pointercolor=RED, fontcolor=YELLOW) | ||
asyncio.create_task(radio(scale1)) | ||
|
||
lbl = Label(wri, 180, 64, 50, bgcolor=DARKGREEN, bdcolor=RED, fgcolor=WHITE) | ||
# do_refresh is called with arg 4. In landscape mode this splits screen | ||
# into segments of 240/4=60 lines. Here we ensure a scale straddles | ||
# this boundary | ||
scale = Scale( | ||
wri, 140, 64, width=124, tickcb=tickcb, pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN | ||
) | ||
asyncio.run(default(scale, lbl)) | ||
|
||
|
||
test() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# color_setup.py Customise for your hardware config | ||
|
||
# Released under the MIT License (MIT). See LICENSE. | ||
# Copyright (c) 2024 Peter Hinch | ||
|
||
# As written, supports: | ||
# gc9a01 240x240 circular display on Pi Pico | ||
# Edit the driver import for other displays. | ||
|
||
# Demo of initialisation procedure designed to minimise risk of memory fail | ||
# when instantiating the frame buffer. The aim is to do this as early as | ||
# possible before importing other modules. | ||
|
||
# WIRING | ||
# Pico Display | ||
# GPIO Pin | ||
# 3v3 36 Vin | ||
# IO6 9 CLK Hardware SPI0 | ||
# IO7 10 DATA (AKA SI MOSI) | ||
# IO8 11 DC | ||
# IO9 12 Rst | ||
# Gnd 13 Gnd | ||
# IO10 14 CS | ||
|
||
from machine import Pin, SPI | ||
import gc | ||
from drivers.gc9a01.gc9a01 import GC9A01 as SSD | ||
|
||
# from drivers.gc9a01.gc9a01_8_bit import GC9A01 as SSD | ||
|
||
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins | ||
prst = Pin(9, Pin.OUT, value=1) | ||
pcs = Pin(10, Pin.OUT, value=1) | ||
|
||
gc.collect() # Precaution before instantiating framebuf | ||
# See DRIVERS.md | ||
spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=33_000_000) | ||
ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst, lscape=False, usd=False, mirror=False) |