From dbc4f4689b08480a92b4ff2ec69fc7fd8541c067 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sun, 29 Aug 2021 11:50:03 +0100 Subject: [PATCH] PR7682 improvements: drivers, writer.py, docs. --- DRIVERS.md | 16 ++++++++ README.md | 25 ++++++------ drivers/boolpalette.py | 20 ++++++++++ drivers/ili93xx/ili9341.py | 2 + 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 +++++++++++++++-------------- 17 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 drivers/boolpalette.py delete mode 100644 gui/core/framebuf_utils.mpy diff --git a/DRIVERS.md b/DRIVERS.md index ee680cc..60a7cf9 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -1085,6 +1085,22 @@ long as `.rgb()` and the "on the fly" converter match, this is arbitrary. The `Writer` (monochrome) or `CWriter` (color) classes and the `nanogui` module should then work automatically. +For color displays the following provides a substantial performance boost when +rendering text. +```python +from drivers.boolpalette import BoolPalette +``` +and, in `__init__.py`: +```python + mode = framebuf.GS4_HMSB # The format to be used by the driver + self.palette = BoolPalette(mode) + gc.collect() + buf = bytearray(self.height * self.width // 2) # Computation must match the format + super().__init__(buf, self.width, self.height, mode) +``` +Assuming firmware dated after 26th Aug 2021, a fast C blit will be used to +render glyphs instead of Python code. + The following script is useful for testing color display drivers after configuring `color_setup.py`. It draws squares at the extreme corners of the display and a corner to corner diagonal. diff --git a/README.md b/README.md index fbd6407..281189d 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ wiring details, pin names and hardware issues. 1.1 [Change log](./README.md#11-change-log) 1.2 [Description](./README.md#12-description) 1.3 [Quick start](./README.md#13-quick-start) + 1.4 [A performance boost](./README.md#14-a-performance-boost) 2. [Files and Dependencies](./README.md#2-files-and-dependencies) 2.1 [Files](./README.md#21-files)      2.1.1 [Core files](./README.md#211-core-files) @@ -93,8 +94,7 @@ on ESP8266 is possible but frozen bytecode must be used owing to its restricted RAM. As of 14th March 2021 it runs on the Raspberry Pi Pico; on that target firmware -must be of that date or later. The `color15` demo fails because the firmware -lacks `uos.urandom()` but hopefully it will be fixed soon. +must be of that date or later. It uses synchronous code but is compatible with `uasyncio`. Some demo programs illustrate this. Code is standard MicroPython, but some device drivers use the @@ -108,10 +108,12 @@ displays: * [SSD1963 large displays](https://github.com/peterhinch/micropython-tft-gui) For historical reasons and to ensure consistency, code and documentation for -my GUI's employ the American spelling of `color`. +my GUI's employ the American spelling of `color`. ## 1.1 Change log +26 Aug 2021 Support [PR7682](https://github.com/micropython/micropython/pull/7682) +for fast text rendering. 25 Apr 2021 Support TTGO T-Display. 26 Mar 2021 Add ST7789. Alter uasyncio support on ili9341. 14 Mar 2021 Tested on Pi Pico. @@ -204,12 +206,19 @@ OLED as per `color_setup.py`, move to the root directory of the repo and run Note also that the `gui.demos.aclock.py` demo comprises 38 lines of actual code. This stuff is easier than you might think. +## 1.4 A performance boost + +As of Aug 2021 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#contents) # 2. Files and Dependencies Firmware should be V1.13 or later. On the Pi Pico firmware should be V1.15 or -later. +later. For fast text rendering use a daily build or V1.17 or later. Installation comprises copying the `gui` and `drivers` directories, with their contents, plus a hardware configuration file, to the target. The directory @@ -248,10 +257,6 @@ The `gui/core` directory contains the GUI core and its principal dependencies: * `writer.py` Module for rendering Python fonts. * `fplot.py` The graph plotting module. * `colors.py` Color constants. - * `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). ###### [Contents](./README.md#contents) @@ -348,10 +353,6 @@ to check for newer versions: * [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py) Provides text rendering of Python font files. -Optional feature: - * An STM32 implementation of - [this optimisation](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost). - A copy of the official driver for OLED displays using the SSD1306 chip is provided. The official file is here: * [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). 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 b1b9805..2ba8d4b 100644 --- a/drivers/ili93xx/ili9341.py +++ b/drivers/ili93xx/ili9341.py @@ -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) 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 3430924..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 or upside down rendering +# 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