From 355cd97981fa372bf81f1eea5201e355a273e448 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sun, 29 Aug 2021 11:50:49 +0100 Subject: [PATCH] PR7682 improvements: drivers, writer.py, docs. --- README.md | 32 +++++++++------ drivers/boolpalette.py | 20 ++++++++++ drivers/ili93xx/ili9341.py | 9 ++--- drivers/ssd1331/ssd1331.py | 3 ++ drivers/ssd1331/ssd1331_16bit.py | 2 + drivers/ssd1351/ssd1351.py | 2 + drivers/ssd1351/ssd1351_16bit.py | 2 + drivers/ssd1351/ssd1351_4bit.py | 2 + drivers/ssd1351/ssd1351_generic.py | 2 + drivers/st7735r/st7735r.py | 2 + drivers/st7735r/st7735r144.py | 2 + drivers/st7735r/st7735r144_4bit.py | 2 + drivers/st7735r/st7735r_4bit.py | 2 + drivers/st7789/st7789_4bit.py | 2 + gui/core/framebuf_utils.mpy | Bin 739 -> 0 bytes gui/core/writer.py | 61 +++++++++++++++-------------- gui/demos/aclock.py | 2 +- gui/demos/simple.py | 4 +- gui/demos/tbox.py | 2 +- gui/primitives/encoder.py | 1 + 20 files changed, 103 insertions(+), 51 deletions(-) create mode 100644 drivers/boolpalette.py delete mode 100644 gui/core/framebuf_utils.mpy diff --git a/README.md b/README.md index ca6bbf4..5d50421 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,6 @@ Raspberry Pico with an ILI9341 from eBay. TTGO T-Display. A joystick switch and an SIL resistor make a simple inexpensive and WiFi-capable system. -Please look at the GitHub Wiki for this project where I am requesting feedback -on an alternative user interface scheme. - # Rationale Touch GUI's have many advantages, however they have drawbacks, principally cost @@ -61,8 +58,11 @@ target and a C device driver (unless you can acquire a suitable binary). Code has been tested on ESP32, Pi Pico and Pyboard. The API shuld be stable. Code is new and issues are likely: please report any found. The project is -under development so check for updates. I also plan to upgrade the performance -of some display drivers. +under development so check for updates. + +A recent firmware update [PR7682](https://github.com/micropython/micropython/pull/7682) +has provided a major boost to text rendering speed on color displays. See +[section 1.8](./README.md#18-performance-and-hardware-notes) for details. # 0. Contents @@ -412,12 +412,23 @@ bytecode: in this configuration running the `various.py` demo there was 29K of free RAM. Note that, at 37.5KiB, this display is the worst-case in terms of RAM usage. A smaller display or a Pyboard D would offer more headroom. +#### A performance boost + +As of Aug 2021 a firmware update +[PR7682](https://github.com/micropython/micropython/pull/7682) means that color +displays can benefit from a substantial performance boost in rendering text. To +take advantage of this, firmware should be dated after 26 Aug 21. The display +driver and GUI core files should be updated. Ensure that the new file +`drivers/boolpalette.py` exists on the target hardware. + ###### [Contents](./README.md#0-contents) ## 1.9 Firmware and dependencies -Firmware should be V1.15 or later. The source tree includes all dependencies. -These are listed to enable users to check for newer versions or to read docs: +Firmware should be V1.15 or later. For fast text rendering a daily build or +a release build >= 1.17 should be used. The source tree includes all +dependencies. These are listed to enable users to check for newer versions or +to read docs: * [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py) Provides text rendering of Python font files. @@ -453,17 +464,14 @@ Display drivers are documented [here](https://github.com/peterhinch/micropython- ## 1.11 Files Display drivers may be found in the `drivers` directory. These are copies of -those in `nano-gui`, included for convenience. +those in `nano-gui`, included for convenience. Note the file +`drivers/boolpalette.py`, required by all color drivers. The system is organised as a Python package with the root being `gui`. Core files in `gui/core` are: * `colors.py` Constants including colors and shapes. * `ugui.py` The main GUI code. * `writer.py` Supports the `Writer` and `CWriter` classes. - * `framebuf_utils.mpy` Accelerator for the `CWriter` class. This optional file - is compiled for STM hardware. It is specific to Pyboards (1.x and D) and will - be ignored on other ports. Details may be found - [here](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost). The `gui/primitives` directory contains the following files: * `switch.py` Interface to physical pushbuttons. diff --git a/drivers/boolpalette.py b/drivers/boolpalette.py new file mode 100644 index 0000000..de44acb --- /dev/null +++ b/drivers/boolpalette.py @@ -0,0 +1,20 @@ +# boolpalette.py Implement BoolPalette class +# This is a 2-value color palette for rendering monochrome glyphs to color +# FrameBuffer instances. Supports destinations with up to 16 bit color. + +# Copyright (c) Peter Hinch 2021 +# Released under the MIT license see LICENSE + +import framebuf + +class BoolPalette(framebuf.FrameBuffer): + + def __init__(self, mode): + buf = bytearray(4) # OK for <= 16 bit color + super().__init__(buf, 2, 1, mode) + + def fg(self, color): # Set foreground color + self.pixel(1, 0, color) + + def bg(self, color): + self.pixel(0, 0, color) diff --git a/drivers/ili93xx/ili9341.py b/drivers/ili93xx/ili9341.py index 3908521..2ba8d4b 100644 --- a/drivers/ili93xx/ili9341.py +++ b/drivers/ili93xx/ili9341.py @@ -1,7 +1,7 @@ # ILI9341 nano-gui driver for ili9341 displays # As with all nano-gui displays, touch is not supported. -# Copyright (c) Peter Hinch 2020-2021 +# Copyright (c) Peter Hinch 2020 # Released under the MIT license see LICENSE # This work is based on the following sources. @@ -13,6 +13,7 @@ from time import sleep_ms import gc import framebuf import uasyncio as asyncio +from drivers.boolpalette import BoolPalette @micropython.viper def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int): @@ -49,6 +50,7 @@ class ILI9341(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.GS4_HMSB + self.palette = BoolPalette(mode) gc.collect() buf = bytearray(self.height * self.width // 2) self._mvb = memoryview(buf) @@ -133,12 +135,7 @@ class ILI9341(framebuf.FrameBuffer): self._spi.write(lb) self._cs(1) - def busy(self): - return self._lock.locked() - async def do_refresh(self, split=4): - if self.busy(): - print('Warning: refresh paused until prior refresh completed.') async with self._lock: lines, mod = divmod(self.height, split) # Lines per segment if mod: diff --git a/drivers/ssd1331/ssd1331.py b/drivers/ssd1331/ssd1331.py index 088a661..da995a6 100644 --- a/drivers/ssd1331/ssd1331.py +++ b/drivers/ssd1331/ssd1331.py @@ -33,6 +33,8 @@ import framebuf import utime import gc import sys +from drivers.boolpalette import BoolPalette + # https://github.com/peterhinch/micropython-nano-gui/issues/2 # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. # Mode 0, 0 works on ESP and STM @@ -54,6 +56,7 @@ class SSD1331(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. + self.palette = BoolPalette(mode) gc.collect() self.buffer = bytearray(self.height * self.width) super().__init__(self.buffer, self.width, self.height, mode) diff --git a/drivers/ssd1331/ssd1331_16bit.py b/drivers/ssd1331/ssd1331_16bit.py index 02109a8..d790092 100644 --- a/drivers/ssd1331/ssd1331_16bit.py +++ b/drivers/ssd1331/ssd1331_16bit.py @@ -30,6 +30,7 @@ import framebuf import utime import gc +from drivers.boolpalette import BoolPalette # https://github.com/peterhinch/micropython-nano-gui/issues/2 # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. @@ -52,6 +53,7 @@ class SSD1331(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.RGB565 + self.palette = BoolPalette(mode) gc.collect() self.buffer = bytearray(self.height * self.width * 2) super().__init__(self.buffer, self.width, self.height, mode) diff --git a/drivers/ssd1351/ssd1351.py b/drivers/ssd1351/ssd1351.py index baae425..70927a7 100644 --- a/drivers/ssd1351/ssd1351.py +++ b/drivers/ssd1351/ssd1351.py @@ -16,6 +16,7 @@ import utime import gc import micropython from uctypes import addressof +from drivers.boolpalette import BoolPalette # Timings with standard emitter # 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms @@ -90,6 +91,7 @@ class SSD1351(framebuf.FrameBuffer): self.height = height # Required by Writer class self.width = width mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. + self.palette = BoolPalette(mode) gc.collect() self.buffer = bytearray(self.height * self.width) super().__init__(self.buffer, self.width, self.height, mode) diff --git a/drivers/ssd1351/ssd1351_16bit.py b/drivers/ssd1351/ssd1351_16bit.py index 68609eb..4d30605 100644 --- a/drivers/ssd1351/ssd1351_16bit.py +++ b/drivers/ssd1351/ssd1351_16bit.py @@ -15,6 +15,7 @@ import utime import gc import micropython from uctypes import addressof +from drivers.boolpalette import BoolPalette # https://github.com/peterhinch/micropython-nano-gui/issues/2 # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. @@ -59,6 +60,7 @@ class SSD1351(framebuf.FrameBuffer): self.height = height # Required by Writer class self.width = width mode = framebuf.RGB565 + self.palette = BoolPalette(mode) gc.collect() self.buffer = bytearray(self.height * self.width * 2) super().__init__(self.buffer, self.width, self.height, mode) diff --git a/drivers/ssd1351/ssd1351_4bit.py b/drivers/ssd1351/ssd1351_4bit.py index ba67b18..e43f79d 100644 --- a/drivers/ssd1351/ssd1351_4bit.py +++ b/drivers/ssd1351/ssd1351_4bit.py @@ -16,6 +16,7 @@ import utime import gc import micropython from uctypes import addressof +from drivers.boolpalette import BoolPalette # https://github.com/peterhinch/micropython-nano-gui/issues/2 # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. @@ -87,6 +88,7 @@ class SSD1351(framebuf.FrameBuffer): self.width = width self.spi_init = init_spi mode = framebuf.GS4_HMSB # Use 4bit greyscale. + self.palette = BoolPalette(mode) gc.collect() self.buffer = bytearray(self.height * self.width // 2) super().__init__(self.buffer, self.width, self.height, mode) diff --git a/drivers/ssd1351/ssd1351_generic.py b/drivers/ssd1351/ssd1351_generic.py index 0570fa5..589c2a8 100644 --- a/drivers/ssd1351/ssd1351_generic.py +++ b/drivers/ssd1351/ssd1351_generic.py @@ -17,6 +17,7 @@ import utime import gc import micropython from uctypes import addressof +from drivers.boolpalette import BoolPalette import sys # https://github.com/peterhinch/micropython-nano-gui/issues/2 @@ -77,6 +78,7 @@ class SSD1351(framebuf.FrameBuffer): self.height = height # Required by Writer class self.width = width mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. + self.palette = BoolPalette(mode) gc.collect() self.buffer = bytearray(self.height * self.width) super().__init__(self.buffer, self.width, self.height, mode) diff --git a/drivers/st7735r/st7735r.py b/drivers/st7735r/st7735r.py index 4394e6a..0c4eee9 100644 --- a/drivers/st7735r/st7735r.py +++ b/drivers/st7735r/st7735r.py @@ -20,6 +20,7 @@ from time import sleep_ms import framebuf import gc import micropython +from drivers.boolpalette import BoolPalette # Datasheet para 8.4 scl write cycle 66ns == 15MHz @@ -60,6 +61,7 @@ class ST7735R(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. + self.palette = BoolPalette(mode) gc.collect() buf = bytearray(height * width) self._mvb = memoryview(buf) diff --git a/drivers/st7735r/st7735r144.py b/drivers/st7735r/st7735r144.py index cc98797..b2b95de 100644 --- a/drivers/st7735r/st7735r144.py +++ b/drivers/st7735r/st7735r144.py @@ -20,6 +20,7 @@ from time import sleep_ms import framebuf import gc import micropython +from drivers.boolpalette import BoolPalette # Datasheet para 8.4 scl write cycle 66ns == 15MHz @@ -58,6 +59,7 @@ class ST7735R(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. + self.palette = BoolPalette(mode) gc.collect() buf = bytearray(self.height * self.width) self._mvb = memoryview(buf) diff --git a/drivers/st7735r/st7735r144_4bit.py b/drivers/st7735r/st7735r144_4bit.py index fa359f7..05c6965 100644 --- a/drivers/st7735r/st7735r144_4bit.py +++ b/drivers/st7735r/st7735r144_4bit.py @@ -20,6 +20,7 @@ from time import sleep_ms import framebuf import gc import micropython +from drivers.boolpalette import BoolPalette # Datasheet para 8.4 scl write cycle 66ns == 15MHz @@ -61,6 +62,7 @@ class ST7735R(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.GS4_HMSB # Use 4bit greyscale. + self.palette = BoolPalette(mode) gc.collect() buf = bytearray(self.height * self.width // 2) self._mvb = memoryview(buf) diff --git a/drivers/st7735r/st7735r_4bit.py b/drivers/st7735r/st7735r_4bit.py index 1a0f355..6cd407d 100644 --- a/drivers/st7735r/st7735r_4bit.py +++ b/drivers/st7735r/st7735r_4bit.py @@ -20,6 +20,7 @@ from time import sleep_ms import framebuf import gc import micropython +from drivers.boolpalette import BoolPalette # Datasheet para 8.4 scl write cycle 66ns == 15MHz @@ -61,6 +62,7 @@ class ST7735R(framebuf.FrameBuffer): self.width = width self._spi_init = init_spi mode = framebuf.GS4_HMSB # Use 4bit greyscale. + self.palette = BoolPalette(mode) gc.collect() buf = bytearray(height * width // 2) self._mvb = memoryview(buf) diff --git a/drivers/st7789/st7789_4bit.py b/drivers/st7789/st7789_4bit.py index 1aaacf1..51d1562 100644 --- a/drivers/st7789/st7789_4bit.py +++ b/drivers/st7789/st7789_4bit.py @@ -21,6 +21,7 @@ import framebuf import gc import micropython import uasyncio as asyncio +from drivers.boolpalette import BoolPalette # User orientation constants LANDSCAPE = 0 # Default @@ -73,6 +74,7 @@ class ST7789(framebuf.FrameBuffer): self._spi_init = init_spi # Possible user callback self._lock = asyncio.Lock() mode = framebuf.GS4_HMSB # Use 4bit greyscale. + self.palette = BoolPalette(mode) gc.collect() buf = bytearray(height * -(-width // 2)) # Ceiling division for odd widths self._mvb = memoryview(buf) diff --git a/gui/core/framebuf_utils.mpy b/gui/core/framebuf_utils.mpy deleted file mode 100644 index 6bb1398d8799551b4b5fd61aadf0c7a71f7eb5a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 739 zcmYjOO-vI(6rSB~e`!k%0tHRzDgmM)u8BVwIc(PLjIa``r@{%8Hd|2Alpqq+Kt0I8 z#M-XO+BK%cn4nxZsIfuV9z0+Si3UsrB=%%!YfKvtZj4EtN)NuneD8PO_ulK#*EPAM z(4RB_9WTv-xqFgVyf(O|r5Os!XpW;koEe_y_Nt({IpBgFOF+Fwf`8JUEu6Q*ubMRL z6CNxOk>7lurehhA7E8Z2N&~_)#I}b?<^v~OpR_NmjJjX~(P9wC-<57dCYh^5BB&P@ zGoU#y+9D*qnzi$***$!Xw5qX@1rfw#MAAT+f(21VKrup+W&$!I6cXwW@82MBO)vly z?1?8eYwE4rzMa(U>1DTNdr`BpD@8!ea@6LcW@m13k2(ETn^?QBv{Sg}g5NT)i=cW% z1$0|(XA&Gq@K|qY&1ELi?k1jxbLt=*cb~>M3NHd3X z@4@%AbQNYM_yjr&Q>X)yWxY-3S*g9nNv%}X9X5;yrN5(cRA;Op(IZo3=LfM yabteJFC=*#{<3vx%rEs@f`RM4E0Smp`9=nPArJoD7UqaBZwy27X}FUJv!%b-qyifN diff --git a/gui/core/writer.py b/gui/core/writer.py index a4c2a1c..bf14068 100644 --- a/gui/core/writer.py +++ b/gui/core/writer.py @@ -1,9 +1,10 @@ # writer.py Implements the Writer class. # Handles colour, word wrap and tab stops -# V0.40 Jan 2021 Improved handling of word wrap and line clip. Upside-down +# V0.4.3 Aug 2021 Support for fast blit to color displays (PR7682). +# V0.4.0 Jan 2021 Improved handling of word wrap and line clip. Upside-down # rendering no longer supported: delegate to device driver. -# V0.35 Sept 2020 Fast rendering option for color displays +# V0.3.5 Sept 2020 Fast rendering option for color displays # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2019-2021 Peter Hinch @@ -14,35 +15,34 @@ # Timings based on a 20 pixel high proportional font, run on a pyboard V1.0. # Using CWriter's slow rendering: _printchar 9.5ms typ, 13.5ms max. -# Using Writer's fast rendering: _printchar 115μs min 480μs typ 950μs max. - -# CWriter on Pyboard D SF2W at standard clock rate -# Fast method 500-600μs typical, up to 1.07ms on larger fonts -# Revised fast method 691μs avg, up to 2.14ms on larger fonts -# Slow method 2700μs typical, up to 11ms on larger fonts import framebuf from uctypes import bytearray_at, addressof -from sys import platform +from sys import implementation +import os -__version__ = (0, 4, 2) +__version__ = (0, 4, 3) -fast_mode = platform == 'pyboard' -if fast_mode: +def buildcheck(device): + if not hasattr(device, 'palette'): + return False + i0, i1, _ = implementation[1] + if i0 > 1 or i1 > 16: + return True + # After release of V1.17 require that build. Until then check for date. + # TODO simplify this once V1.17 is released. try: - try: - from framebuf_utils import render - except ImportError: # May be running in GUI. Try relative import. - try: - from .framebuf_utils import render - except ImportError: - fast_mode = False - except ValueError: - fast_mode = False - if not fast_mode: - print('Ignoring missing or invalid framebuf_utils.mpy.') + datestring = os.uname()[3] + date = datestring.split(' on')[1] + date = date.lstrip()[:10] + idate = tuple([int(x) for x in date.split('-')]) + return idate >= (2021, 8, 25) + except AttributeError: + return False +fast_mode = False # False for mono displays although actually these render fast + class DisplayState(): def __init__(self): self.text_row = 0 @@ -264,12 +264,14 @@ class Writer(): def setcolor(self, *_): return self.fgcolor, self.bgcolor -# Writer for colour displays +# Writer for colour displays. class CWriter(Writer): def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True): super().__init__(device, font, verbose) + global fast_mode + fast_mode = buildcheck(device) if bgcolor is not None: # Assume monochrome. self.bgcolor = bgcolor if fgcolor is not None: @@ -285,11 +287,12 @@ class CWriter(Writer): if self.glyph is None: return # All done buf = bytearray_at(addressof(self.glyph), len(self.glyph)) - fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map) - fgcolor = self.bgcolor if invert else self.fgcolor - bgcolor = self.fgcolor if invert else self.bgcolor - # render clips a glyph if outside bounds of destination - render(self.device, fbc, s.text_col, s.text_row, fgcolor, bgcolor) + fbc = framebuf.FrameBuffer(buf, self.clip_width, self.char_height, self.map) + palette = self.device.palette + palette.bg(self.fgcolor if invert else self.bgcolor) + palette.fg(self.bgcolor if invert else self.fgcolor) + + self.device.blit(fbc, s.text_col, s.text_row, -1, palette) s.text_col += self.char_width self.cpos += 1 diff --git a/gui/demos/aclock.py b/gui/demos/aclock.py index ed4390f..9db004f 100644 --- a/gui/demos/aclock.py +++ b/gui/demos/aclock.py @@ -69,7 +69,7 @@ class BaseScreen(Screen): 'bgcolor' : DARKGREEN, } - wri = CWriter(ssd, font, GREEN, BLACK, verbose=False) + wri = CWriter(ssd, font, GREEN, BLACK) # verbose = True dial = Dial(wri, 2, 2, height = 70, ticks = 12, fgcolor = GREEN, pip = GREEN) # Set up clock display: instantiate labels diff --git a/gui/demos/simple.py b/gui/demos/simple.py index ca69222..7de4094 100644 --- a/gui/demos/simple.py +++ b/gui/demos/simple.py @@ -24,8 +24,8 @@ class BaseScreen(Screen): print('Button pressed', arg) super().__init__() - wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) - + # verbose default indicates if fast rendering is enabled + wri = CWriter(ssd, arial10, GREEN, BLACK) col = 2 row = 2 Label(wri, row, col, 'Simple Demo') diff --git a/gui/demos/tbox.py b/gui/demos/tbox.py index 7440548..a085fee 100644 --- a/gui/demos/tbox.py +++ b/gui/demos/tbox.py @@ -19,7 +19,7 @@ from gui.widgets.label import Label from gui.widgets.textbox import Textbox from gui.widgets.buttons import Button, CloseButton -wri = CWriter(ssd, arial10, verbose=False) +wri = CWriter(ssd, arial10) # verbose = True def fwdbutton(wri, row, col, cls_screen, text='Next'): def fwd(button): diff --git a/gui/primitives/encoder.py b/gui/primitives/encoder.py index 8c206be..b83abf6 100644 --- a/gui/primitives/encoder.py +++ b/gui/primitives/encoder.py @@ -3,6 +3,7 @@ # Copyright (c) 2021 Peter Hinch # Released under the MIT License (MIT) - see LICENSE file +# https://github.com/peterhinch/micropython-async/blob/master/v3/primitives/encoder.py # This driver is intended for encoder-based control knobs. It is # unsuitable for NC machine applications. Please see the docs.