From b7e0ef153ec292805ecf85a87f7ad1be8648296c Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Thu, 5 Nov 2020 10:10:21 +0000 Subject: [PATCH] Complete refactor as Python package. --- README.md | 262 +++++++++++++++++++---------- ssd1351_setup.py => color_setup.py | 15 +- drivers/sharp/README.md | 14 +- drivers/sharp/clock_batt.py | 14 +- drivers/sharp/clocktest.py | 14 +- drivers/sharp/sharptest.py | 11 +- gui/core/colors.py | 9 +- gui/demos/aclock.py | 14 +- gui/demos/alevel.py | 26 +-- gui/demos/asnano.py | 4 +- gui/demos/asnano_sync.py | 7 +- gui/demos/color15.py | 6 +- gui/demos/color96.py | 7 +- gui/demos/fpt.py | 9 +- gui/demos/mono_test.py | 31 +++- gui/demos/scale.py | 24 ++- gui/widgets/scale.py | 5 +- 17 files changed, 293 insertions(+), 179 deletions(-) rename ssd1351_setup.py => color_setup.py (68%) diff --git a/README.md b/README.md index b204424..737c086 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,9 @@ wiring details, pin names and hardware issues. # Contents 1. [Introduction](./README.md#1-introduction) + 1.1 [Update](./README.md#11-update) + 1.2 [Description](./README.md#12-description) + 1.3 [Quick start](./README.md#13-quick-start) 2. [Files and Dependencies](./README.md#2-files-and-dependencies) 2.1 [Dependencies](./README.md#21-dependencies) 2.2.1 [Monochrome use](./README.md#211-monochrome-use) @@ -44,12 +47,40 @@ wiring details, pin names and hardware issues. # 1. Introduction +This library provides a limited set of GUI objects (widgets) for displays whose +display driver is subclassed from the `framebuf` class. The GUI is display-only +and lacks provision for user input. This is because no `framebuf` based display +drivers exist for screens with a touch overlay. This is probably because touch +overlays require too many pixels and are best suited to displays with internal +frame buffers. + +The GUI is cross-platform. By default it is configured for a Pyboard (1.x or D). +This doc explains how to configure for other platforms by adapting a single +small file. The GUI supports multiple displays attached to a single target, but +bear in mind the RAM requirements for multiple frame buffers. + +Authors of applications requiring touch should consider my touch GUI's for the +following displays. These have internal buffers: + * [Official lcd160cr](https://github.com/peterhinch/micropython-lcd160cr-gui) + * [RA8875 large displays](https://github.com/peterhinch/micropython_ra8875) + * [SSD1963 large displays](https://github.com/peterhinch/micropython-tft-gui) + +## 1.1 Update + This library has been refactored as a Python package. The aim is to reduce RAM usage: widgets are imported on demand rather than unconditionally. This enabled -the addition of new widgets with zero impact on existsing applications. +the addition of new widgets with zero impact on existsing applications. Another +aim was to simplify installation with dependencies such as `writer` included in +the tree. Finally hardware configuration is contained in a single file: details +only need to be edited in one place to run all demo scripts. -This library provides a limited set of GUI objects (widgets) for displays whose -display driver is subclassed from the `framebuf` class. Display drivers include: +Existing users should re-install from scratch. In existing applications, import +statements will need to be adapted as per the demos. The GUI API is otherwise +unchanged. + +## 1.2 Description + +Compatible and tested display drivers include: * The official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). * The [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git). @@ -63,60 +94,88 @@ display driver is subclassed from the `framebuf` class. Display drivers include: is [here](./drivers/sharp/README.md). Widgets are intended for the display of data from physical devices such as -sensors. The GUI is display-only: there is no provision for user input. This -is because there are no `frmebuf`- based display drivers for screens with a -touch overlay. Authors of applications requiring input should consider my touch -GUI's for the official lcd160cr, for RA8875 based displays or for SSD1963 based -displays. - -Widgets are drawn using graphics primitives rather than icons to minimise RAM -usage. It also enables them to be effciently rendered at arbitrary scale on +sensors. They are drawn using graphics primitives rather than icons to minimise +RAM usage. It also enables them to be effciently rendered at arbitrary scale on devices with restricted processing power. The approach also enables widgets to -provide information in ways that are difficult with icons, in particular using +maximise information in ways that are difficult with icons, in particular using dynamic color changes in conjunction with moving elements. Owing to RAM requirements and limitations on communication speed, `framebuf` based display drivers are intended for physically small displays with limited numbers of pixels. The widgets are designed for displays as small as 0.96 -inches: this involves some compromises. They aim to maximise the information -on screen by offering the option of dynamically changing colors. +inches: this involves some compromises. Copying the contents of the frame buffer to the display is relatively slow. The time depends on the size of the frame buffer and the interface speed, but the latency may be too high for applications such as games. For example the time to -update a 128*128*8 color ssd1351 display on a Pyboard 1.0 is 41ms. +update a 128x128x8 color ssd1351 display on a Pyboard 1.0 is 41ms. Drivers based on `framebuf` must allocate contiguous RAM for the buffer. To -avoid 'out of memory' errors it is best to instantiate the display early, -possibly before importing many other modules. The `aclock.py` and `alevel.py` -demos illustrate this. +avoid 'out of memory' errors it is best to instantiate the display before +importing other modules. The demos illustrate this. + +## 1.3 Quick start + +A GUI description can seem daunting because of the number of class config +options. Defaults can usually be accepted and meaningful applications can be +minimal. Installation can seem difficult. To counter this, this session using +[rshell](https://github.com/dhylands/rshell) installed and ran a demo showing +analog and digital clocks. + +Clone the repo to your PC, wire up a Pyboard (1.x or D) to an Adafruit 1.27" +OLED as per `color_setup.py`, move to the root directory of the repo and run +`rshell`. +```bash +> cp -r drivers /sd +> cp -r gui /sd +> cp color_setup.py /sd +> repl ~ import gui.demos.aclock +``` +Note also that the `gui.demos.aclock.py` demo comprises 38 lines of actual +code. This stuff is easier than you might think. # 2. Files and Dependencies -In general installation comprises copying the `ngui` and `drivers` directories, -with their contents, to the target hardware +Firmware should be V1.13 or later. + +Installation comprises copying the `gui` and `drivers` directories, with their +contents, plus a hardware configuration file, to the target. The directory +structure on the target must match that in the repo. + +Filesystem space may be conserved by copying only the required driver from +`drivers`, but the directory path to that file must be retained. For example, +for SSD1351 displays only the following is actually required: +`drivers/ssd1351/ssd1351.py` ## 2.1 Files ### 2.1.1 Core files -The `ngui/core` directory contains the GUI core and its principal dependencies: +The root directory contains setup files for monochrome and color displays. The +relevant file will need to be edited to match the display in use, the +MicroPython target and the electrical connections between display and target. + * `color_setup.py` Color displays. As written supports an SSD1351 display + connected to a Pyboard. + * `ssd1306_setup.py` Setup file for monochrome displays using the official + driver. Supports hard or soft SPI or I2C connections, as does the test script + `mono_test.py`. On non Pyboard targets this will require adaptation to match + the hardware connections. + +The `gui/core` directory contains the GUI core and its principal dependencies: * `nanogui.py` The library. * `writer.py` Module for rendering Python fonts. * `fplot.py` The graph plotting module. - * `ssd1306_setup.py` Applications using an SSD1306 monochrome OLED display - import this file to determine hardware initialisation. On non Pyboard targets - this will require adaptation to match the hardware connections. + * `colors.py` Color constants. * `framebuf_utils.mpy` Accelerator for the `CWriter` class. This optional file - is compiled for STM hardware and will be ignored on other ports. Instructions - and code for compiling for other architectures may be found + is compiled for STM hardware and will be ignored on other ports unless + recompiled. Instructions and code for compiling for other architectures may be + found [here](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost). ### 2.1.2 Demo scripts -The `ngui/demos` directory contains test/demo scripts. In general these will -need minor adaptation to match your display hardware. +The `gui/demos` directory contains test/demo scripts. * `mono_test.py` Tests/demos using the official SSD1306 library for a monochrome 128*64 OLED display. @@ -126,77 +185,95 @@ need minor adaptation to match your display hardware. Demos for Adafruit 1.27 inch and 1.5 inch color OLEDs. Edit the `height = 96` line as per the code comment for the larger display. - * `aclock.py` Analog clock demo. + * `aclock.py` Analog clock demo. Cross platform. * `alevel.py` Spirit level using Pyboard accelerometer. + * `fpt.py` Plot demo. Cross platform. + * `scale.py` A demo of the new `Scale` widget. Cross platform. + * `asnano_sync.py` Two Pyboard specific demos using the GUI with `uasyncio`. + * `asnano.py` Could readily be adapted for other targets. + +Compatibility with `uasyncio` and the last two demos are discussed +[here](./ASYNC.md). + +Demo scripts for Sharp displays are in `drivers/sharp`. Check source code for +wiring details. See [the README](./drivers/sharp/README.md). They may be run as +follows: +```python +import drivers.sharp.sharptest +# or +import drivers.sharp.clocktest +``` ### 2.1.3 Fonts -Python font files are in the root directory. This facilitates freezing them to -conserve RAM. Python fonts may be created using -[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git). -Supplied examples are: +Python font files are in the `gui/fonts` directory. The easiest way to conserve +RAM is to freeze them which is highly recommended. In doing so the directory +structure must be maintained. Python fonts may be created using +[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git). The +`-x` option for horizontal mapping must be specified. Supplied examples are: - * `arial10.py` - * `courier20.py` + * `arial10.py` Variable pitch Arial in various sizes. + * `arial35.py` + * `arial_50.py` + * `courier20.py` Fixed pitch font. * `font6.py` + * `font10.py` * `freesans20.py` -Demos showing the use of `nanogui` with `uasyncio` may be found [here](./ASYNC.md). - ## 2.2 Dependencies -All applicatons require a device driver for the display in use plus any Python -font files in use. The following is required by all applications: +The source tree now includes all dependencies. These are listed to enable users +to check for newer versions. * [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py) Provides text rendering. +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). + + ### 2.2.1 Monochrome use -OLED displays using the SSD1306 chip require: - * [ssd1306_setup.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/ssd1306_setup.py) - Contains wiring information. - * The official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). +The official driver for OLED displays using the SSD1306 chip is provided, but +the source is here: + * [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). -Displays based on the PCD8544 chip require: +Displays based on the Nokia 5110 (PCD8544 chip) require this driver. It is not +in this repo but may be found here: * [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git) ### 2.2.2 Color use -Supported displays amd their drivers are listed below: +Drivers for Adafruit 0.96", 1.27" and 1.5" OLEDS and the Sharp display are +included in the source tree. Each driver has its own small `README.md`. The +default driver for the larger OLEDs is Pyboard specific, but there are cross +platform alternatives in the directory. - * [Adafruit 0.96 inch color OLED](https://github.com/peterhinch/micropython-nano-gui/tree/master/drivers/ssd1331). - Driver for SSD1331 controller. - * [Adafruit 1.5 and 1.27 inch color OLEDs](./drivers/ssd1351/README.md) - Driver for SSD1351 controller. - -Test script for Adafruit 1.5 and 1.27 inch color OLED displays. It's a good -idea to paste this at the REPL to ensure the display is working before -progressing to the GUI. Remember to change `height` if using the 1.5 inch -display. +If using the Adafruit 1.5 or 1.27 inch color OLED displays it is suggested that +after installing the GUI the following script is pasted at the REPL. This will +verify the hardware. Please change `height` to 128 if using the 1.5 inch +display. Note the commented-out cross platform alternative. ```python import machine -from ssd1351 import SSD1351 as SSD - -pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) -pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) -prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) -spi = machine.SPI(1) +from drivers.ssd1351.ssd1351 import SSD1351 as SSD +# from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD +pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) +pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) +prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) +spi = machine.SPI(2) ssd = SSD(spi, pcs, pdc, prst, height=96) # Ensure height is correct (96/128) ssd.fill(0) ssd.line(0, 0, 127, 95, ssd.rgb(0, 255, 0)) # Green diagonal corner-to-corner ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) # Red square at top left ssd.show() ``` -Color applications which do a lot of text rendering may achieve a speed gain by -means of -[this optimisation](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost). ###### [Contents](./README.md#contents) # 3. The nanogui module -This supports widgets whose text components are drawn using the `Writer` +The GUI supports widgets whose text components are drawn using the `Writer` (monochrome) or `CWriter` (colour) classes. Upside down rendering is not supported: attempts to specify it will produce unexpected results. @@ -204,28 +281,29 @@ Widgets are drawn at specific locations on screen and are incompatible with the display of scrolling text: they are therefore not intended for use with the `Writer.printstring` method. The coordinates of a widget are those of its top left corner. If a border is specified, this is drawn outside of the limits of -the widgets with a margin of 2 pixels. If the widget is placed at [row, col] -the top left hand corner of the border is at [row-2, col-2]. +the widgets with a margin of 2 pixels. If the widget is placed at `[row, col]` +the top left hand corner of the border is at `[row-2, col-2]`. When a widget is drawn or updated (typically with its `value` method) it is not immediately displayed. To update the display `nanogui.refresh` is called: this -ensures that the `framebuf` contents are updated before copying the contents to -the display. This postponement is for performance reasons and to provide the -appearance of a rapid update. +enables multiple updates to the `framebuf` contents before once copying the +buffer to the display. Postponement is for performance and provides a visually +rapid update. ## 3.1 Initialisation The GUI is initialised in the following stages. The aim is to allocate the `framebuf` before importing other modules. This is intended to reduce the risk of memory failures when instantiating a large framebuf in an application which -imports multiple modules. +imports multiple modules. Note that the hardware dependent code is located in +`color_setup.py`: it is illustrated here to explain the process. Firstly set the display height and import the driver: ```python height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch import machine import gc -from ssd1351 import SSD1351 as SSD # Import the display driver +from drivers.ssd1351.ssd1351 import SSD1351 as SSD # Import the display driver ``` Then set up the bus (SPI or I2C) and instantiate the display. At this point the framebuffer is created: @@ -242,20 +320,20 @@ modules required by the application. For each font to be used import the Python font and create a `CWriter` instance (for monochrome displays a `Writer` is used): ```python -from nanogui import Label, Dial, Pointer, refresh # Whatever you need +from color_setup import ssd, height # Create a display instance +from gui.core.nanogui import refresh +from gui.widgets.label import Label # Import any widgets you plan to use +from gui.widgets.dial import Dial, Pointer + refresh(ssd) # Initialise and clear display. -from writer import CWriter # Import other modules -import arial10 # Font -GREEN = SSD.rgb(0, 255, 0) # Define colors -RED = SSD.rgb(255, 0, 0) -BLUE = SSD.rgb(0, 0, 255) -YELLOW = SSD.rgb(255, 255, 0) -BLACK = 0 +from gui.core.writer import CWriter # Import other modules +import gui.fonts.arial10 # Font +from gui.core.colors import * # Define colors CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it # Instantiate any CWriters to be used (one for each font) -wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) +wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) # Colors are defaults wri.set_clip(True, True, False) ``` @@ -317,17 +395,18 @@ The following is a complete "Hello world" script. height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch import machine import gc -from ssd1351 import SSD1351 as SSD # Import the display driver -pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) -pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) -prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) -spi = machine.SPI(1) +from drivers.ssd1351.ssd1351 import SSD1351 as SSD +pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) +pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) +prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) +spi = machine.SPI(2) gc.collect() # Precaution before instantiating framebuf ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance -from nanogui import Label, refresh +from gui.core.nanogui import refresh +from gui.widgets.label import Label refresh(ssd) # Initialise and clear display. -from writer import CWriter # Import other modules -import freesans20 # Font +from gui.core.writer import CWriter # Import other modules +import gui.fonts.freesans20 as freesans20 # Font GREEN = SSD.rgb(0, 255, 0) # Define colors BLACK = 0 CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it @@ -521,9 +600,8 @@ This should be amended if the hardware uses a different 8-bit format. The `Writer` (monochrome) or `CWriter` (color) classes and the `nanogui` module should then work automatically. -If a display uses I2C note that owing to -[this issue](https://github.com/micropython/micropython/pull/4020) soft I2C -may be required, depending on the detailed specification of the chip. - +Drivers for displays using I2C may need to use +[I2C.writevto](http://docs.micropython.org/en/latest/library/machine.I2C.html?highlight=writevto#machine.I2C.writevto) +depending on the chip requirements. ###### [Contents](./README.md#contents) diff --git a/ssd1351_setup.py b/color_setup.py similarity index 68% rename from ssd1351_setup.py rename to color_setup.py index 28dfbac..8b57b9b 100644 --- a/ssd1351_setup.py +++ b/color_setup.py @@ -1,8 +1,13 @@ -# ssd1351_setup.py Customise for your hardware config +# color_setup.py Customise for your hardware config # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2020 Peter Hinch +# As written, supports: +# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 +# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 +# 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. @@ -17,10 +22,14 @@ # Y6 CLK (2 CL SCK) # Y8 DATA (1 SI MOSI) -height = 96 # 1.27 inch 96*128 (rows*cols) display - import machine import gc + +# *** Choose your color display driver here *** +# Driver supporting non-STM platforms +# from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD + +# STM specific driver from drivers.ssd1351.ssd1351 import SSD1351 as SSD height = 96 # 1.27 inch 96*128 (rows*cols) display diff --git a/drivers/sharp/README.md b/drivers/sharp/README.md index bc499b4..1efc2d2 100644 --- a/drivers/sharp/README.md +++ b/drivers/sharp/README.md @@ -74,17 +74,17 @@ The datasheet specifies a minimum refresh rate of 1Hz. 3. `clock_batt.py` As above but designed for low power operation. Pyboard specific. -`sharptest` should not be run for long periods as it does not regularly refresh -the display. It tests `writer.py` and some `framebuffer` graphics primitives. -`clocktest` tests `nanogui.py`. +Tests assume that `nanogui` is installed as per the instructions. `sharptest` +should not be run for long periods as it does not regularly refresh the +display. It tests `writer.py` and some `framebuffer` graphics primitives. +`clocktest` demostrates use with `nanogui`. -To run the tests the fonts in the directory, `writer.py` and `nanogui.py` must -be copied to the device or frozen as bytecode. The `clack_batt.py` demo needs -`upower.py` from +The `clock_batt.py` demo needs `upower.py` from [micropython-micropower](https://github.com/peterhinch/micropython-micropower). Testing was done on a Pyboard D SF6W: frozen bytecode was not required. I -suspect a Pyboard 1.x would require it to prevent memory errors. +suspect a Pyboard 1.x would require it to prevent memory errors. Fonts in +particular benefit from freezing as their RAM usage is radically reduced. # 3. Device driver constructor diff --git a/drivers/sharp/clock_batt.py b/drivers/sharp/clock_batt.py index 42537b3..00a3d3e 100644 --- a/drivers/sharp/clock_batt.py +++ b/drivers/sharp/clock_batt.py @@ -20,7 +20,7 @@ import machine import gc -from sharp import SHARP as SSD +from drivers.sharp.sharp import SHARP as SSD # Initialise hardware pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high @@ -30,14 +30,18 @@ ssd = SSD(spi, pcs) # Now import other modules import upower -from nanogui import Dial, Pointer, refresh, Label +from gui.core.nanogui import refresh +from gui.widgets.label import Label +from gui.widgets.dial import Dial, Pointer + import pyb import cmath -from writer import Writer + +from gui.core.writer import Writer # Fonts for Writer -import freesans20 as font_small -import arial35 as font_large +import gui.fonts.freesans20 as font_small +import gui.fonts.arial35 as font_large refresh(ssd) # Initialise display. diff --git a/drivers/sharp/clocktest.py b/drivers/sharp/clocktest.py index c1f9d6c..ad50062 100644 --- a/drivers/sharp/clocktest.py +++ b/drivers/sharp/clocktest.py @@ -18,7 +18,7 @@ import machine import gc -from sharp import SHARP as SSD +from drivers.sharp.sharp import SHARP as SSD # Initialise hardware pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high @@ -27,14 +27,18 @@ gc.collect() # Precaution before instantiating framebuf ssd = SSD(spi, pcs) # Now import other modules -from nanogui import Dial, Pointer, refresh, Label +from gui.core.nanogui import refresh +from gui.widgets.label import Label +from gui.widgets.dial import Dial, Pointer + import cmath import utime -from writer import Writer + +from gui.core.writer import Writer # Fonts for Writer -import freesans20 as font_small -import arial35 as font_large +import gui.fonts.freesans20 as font_small +import gui.fonts.arial35 as font_large refresh(ssd) # Initialise display. diff --git a/drivers/sharp/sharptest.py b/drivers/sharp/sharptest.py index be9621b..a551079 100644 --- a/drivers/sharp/sharptest.py +++ b/drivers/sharp/sharptest.py @@ -14,15 +14,18 @@ # Y5 CS import machine -from sharp import SHARP -import freesans20, arial_50 -from writer import Writer +from drivers.sharp.sharp import SHARP as SSD +# Fonts for Writer +import gui.fonts.freesans20 as freesans20 +import gui.fonts.arial_50 as arial_50 + +from gui.core.writer import Writer import time def test(): pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high spi = machine.SPI(2) - ssd = SHARP(spi, pcs) + ssd = SSD(spi, pcs) rhs = ssd.width -1 ssd.line(rhs - 80, 0, rhs, 80, 1) square_side = 40 diff --git a/gui/core/colors.py b/gui/core/colors.py index 365e4e4..961cce6 100644 --- a/gui/core/colors.py +++ b/gui/core/colors.py @@ -3,13 +3,18 @@ # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2020 Peter Hinch -from drivers.ssd1351.ssd1351 import SSD1351 as SSD +from color_setup import SSD GREEN = SSD.rgb(0, 255, 0) RED = SSD.rgb(255, 0, 0) +LIGHTRED = SSD.rgb(140, 0, 0) BLUE = SSD.rgb(0, 0, 255) YELLOW = SSD.rgb(255, 255, 0) BLACK = 0 WHITE = SSD.rgb(255, 255, 255) +GREY = SSD.rgb(100, 100, 100) +MAGENTA = SSD.rgb(255, 0, 255) +CYAN = SSD.rgb(0, 255, 255) LIGHTGREEN = SSD.rgb(0, 100, 0) - +DARKGREEN = SSD.rgb(0, 80, 0) +DARKBLUE = SSD.rgb(0, 0, 90) diff --git a/gui/demos/aclock.py b/gui/demos/aclock.py index 7421463..ce9c631 100644 --- a/gui/demos/aclock.py +++ b/gui/demos/aclock.py @@ -5,18 +5,8 @@ # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2018-2020 Peter Hinch -# WIRING -# Pyb SSD -# 3v3 Vin -# Gnd Gnd -# X1 DC -# X2 CS -# X3 Rst -# X6 CLK -# X8 DATA - -# Initialise hardware -from ssd1351_setup import ssd, height # Create a display instance +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd, height # Create a display instance from gui.core.nanogui import refresh from gui.widgets.label import Label from gui.widgets.dial import Dial, Pointer diff --git a/gui/demos/alevel.py b/gui/demos/alevel.py index 4e9965d..a4a7614 100644 --- a/gui/demos/alevel.py +++ b/gui/demos/alevel.py @@ -1,28 +1,12 @@ -# alevel.py Test/demo program for Adafruit ssd1351-based OLED displays -# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 -# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 +# alevel.py Test/demo "spirit level" program. +# Requires Pyboard for accelerometer. +# Tested with Adafruit ssd1351 OLED display. # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2018-2020 Peter Hinch -# WIRING -# Pyb SSD -# 3v3 Vin -# Gnd Gnd -# X1 DC -# X2 CS -# X3 Rst -# X6 CLK -# X8 DATA - -# 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. - -import gc - -# Initialise hardware -from ssd1351_setup import ssd # Create a display instance +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd # Create a display instance from gui.core.nanogui import refresh from gui.widgets.dial import Dial, Pointer diff --git a/gui/demos/asnano.py b/gui/demos/asnano.py index cf55657..4f7ea33 100644 --- a/gui/demos/asnano.py +++ b/gui/demos/asnano.py @@ -6,8 +6,8 @@ # Copyright (c) 2020 Peter Hinch # Released under the MIT License (MIT) - see LICENSE file -# Initialise hardware and framebuf before importing modules -from ssd1351_setup import ssd # Create a display instance +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd # Create a display instance import uasyncio as asyncio import pyb diff --git a/gui/demos/asnano_sync.py b/gui/demos/asnano_sync.py index 419b6bb..8ebf92f 100644 --- a/gui/demos/asnano_sync.py +++ b/gui/demos/asnano_sync.py @@ -1,13 +1,12 @@ # asnano_sync.py Test/demo program for use of nanogui with uasyncio -# Uses Adafruit ssd1351-based OLED displays (change height to suit) -# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 -# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 +# Requires Pyboard for switch and LEDs. +# Tested with Adafruit ssd1351 OLED display. # Copyright (c) 2020 Peter Hinch # Released under the MIT License (MIT) - see LICENSE file # Initialise hardware and framebuf before importing modules -from ssd1351_setup import ssd # Create a display instance +from color_setup import ssd # Create a display instance import uasyncio as asyncio import pyb diff --git a/gui/demos/color15.py b/gui/demos/color15.py index 3dc7a7e..c8cf5f9 100644 --- a/gui/demos/color15.py +++ b/gui/demos/color15.py @@ -1,4 +1,5 @@ -# color15.py Test/demo program for Adafruit ssd1351-based OLED displays +# color15.py Test/demo program for larger displays. Cross-platform. +# Tested on Adafruit ssd1351-based OLED displays: # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 # For wiring details see drivers/ADAFRUIT.md in this repo. @@ -6,7 +7,8 @@ # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2018-2020 Peter Hinch -from ssd1351_setup import ssd # Create a display instance +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd # Create a display instance import cmath import utime diff --git a/gui/demos/color96.py b/gui/demos/color96.py index 3941bba..6a143b3 100644 --- a/gui/demos/color96.py +++ b/gui/demos/color96.py @@ -1,11 +1,14 @@ -# color96.py Test/demo program for ssd1331 Adafruit 0.96" OLED display +# color96.py Test/demo program for ssd1331 Adafruit 0.96" OLED display. +# Cross-platfom. +# Works on larger displays, but only occupies the top left region. # https://www.adafruit.com/product/684 # For wiring details see drivers/ADAFRUIT.md in this repo. # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2018-2020 Peter Hinch -from ssd1351_setup import ssd # Create a display instance +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd # Create a display instance from gui.core.nanogui import refresh from gui.widgets.led import LED diff --git a/gui/demos/fpt.py b/gui/demos/fpt.py index 7c08cd3..3901aac 100644 --- a/gui/demos/fpt.py +++ b/gui/demos/fpt.py @@ -1,12 +1,14 @@ -# fpt.py Test/demo program for framebuf plot -# Uses Adafruit ssd1351-based OLED displays (change height to suit) +# fpt.py Test/demo program for framebuf plot. Cross-patform, +# but requires a large enough display. +# Tested on Adafruit ssd1351-based OLED displays: # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2018-2020 Peter Hinch -from ssd1351_setup import ssd # Create a display instance +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd # Create a display instance import cmath import math @@ -17,7 +19,6 @@ from gui.core.fplot import PolarGraph, PolarCurve, CartesianGraph, Curve, TSeque from gui.core.nanogui import refresh from gui.widgets.label import Label - refresh(ssd) # Fonts diff --git a/gui/demos/mono_test.py b/gui/demos/mono_test.py index a72a1f4..e4e3652 100644 --- a/gui/demos/mono_test.py +++ b/gui/demos/mono_test.py @@ -6,19 +6,33 @@ # https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display # https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html -# V0.31 9th Sep 2018 +# V0.32 5th Nov 2020 Replace uos.urandom for minimal ports import utime -import uos +# import uos from ssd1306_setup import WIDTH, HEIGHT, setup from gui.core.writer import Writer, CWriter -from gui.core.nanogui import Label, Meter, refresh +from gui.core.nanogui import refresh +from gui.widgets.meter import Meter +from gui.widgets.label import Label # Fonts import gui.fonts.arial10 as arial10 -import gui.courier20 as fixed +import gui.fonts.courier20 as fixed import gui.fonts.font6 as small +# Some ports don't support uos.urandom. +# See https://github.com/peterhinch/micropython-samples/tree/master/random +def xorshift64star(modulo, seed = 0xf9ac6ba4): + x = seed + def func(): + nonlocal x + x ^= x >> 12 + x ^= ((x << 25) & 0xffffffffffffffff) # modulo 2**64 + x ^= x >> 27 + return (x * 0x2545F4914F6CDD1D) % modulo + return func + def fields(use_spi=False, soft=True): ssd = setup(use_spi, soft) # Create a display instance Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it @@ -28,9 +42,10 @@ def fields(use_spi=False, soft=True): numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bdcolor=None) countfield = Label(wri, 0, 90, wri.stringlen('1')) n = 1 + random = xorshift64star(65535) for s in ('short', 'longer', '1', ''): textfield.value(s) - numfield.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + numfield.value('{:5.2f}'.format(random() /1000)) countfield.value('{:1d}'.format(n)) n += 1 refresh(ssd) @@ -54,9 +69,10 @@ def multi_fields(use_spi=False, soft=True): nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border y += dy + random = xorshift64star(2**24 - 1) for _ in range(10): for field in nfields: - value = int.from_bytes(uos.urandom(3),'little')/167772 + value = random() / 167772 field.value('{:5.2f}'.format(value)) refresh(ssd) utime.sleep(1) @@ -72,8 +88,9 @@ def meter(use_spi=False, soft=True): m1 = Meter(wri, 5, 44, height = 50, divisions = 4, legends=('-1', '0', '+1')) m2 = Meter(wri, 5, 86, height = 50, divisions = 4, legends=('-1', '0', '+1')) steps = 10 + random = xorshift64star(2**24 - 1) for n in range(steps + 1): - m0.value(int.from_bytes(uos.urandom(3),'little')/16777216) + m0.value(random() / 16777216) m1.value(n/steps) m2.value(1 - n/steps) refresh(ssd) diff --git a/gui/demos/scale.py b/gui/demos/scale.py index 7a6bf58..32a51b4 100644 --- a/gui/demos/scale.py +++ b/gui/demos/scale.py @@ -5,8 +5,10 @@ # Usage: # import gui.demos.scale -# Initialise hardware -from ssd1351_setup import ssd # Create a display instance + +# Initialise hardware and framebuf before importing modules. +from color_setup import ssd # Create a display instance + from gui.core.nanogui import refresh from gui.core.writer import CWriter @@ -48,12 +50,26 @@ async def default(scale, lbl): 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) # 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) - lbl = Label(wri, ssd.height - wri.height - 2, 0, 50) - scale = Scale(wri, 5, 5) + scale1 = Scale(wri, 2, 2, width = 124, legendcb = legendcb, + pointercolor=RED, fontcolor=YELLOW) + asyncio.create_task(radio(scale1)) + + lbl = Label(wri, ssd.height - wri.height - 2, 2, 50, + bgcolor = DARKGREEN, bdcolor = RED, fgcolor=WHITE) + scale = Scale(wri, 45, 2, width = 124, tickcb = tickcb, + pointercolor=RED, fontcolor=YELLOW) asyncio.run(default(scale, lbl)) test() diff --git a/gui/widgets/scale.py b/gui/widgets/scale.py index d2b8421..c991722 100644 --- a/gui/widgets/scale.py +++ b/gui/widgets/scale.py @@ -6,8 +6,6 @@ # Usage: # from gui.widgets.scale import Scale -# NEED print_left, get_stringsize - from gui.core.nanogui import DObject from gui.core.writer import Writer from gui.core.colors import BLACK @@ -34,6 +32,7 @@ class Scale(DObject): ctrl_ht = height - min_ht # adjust ticks for greater height width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset super().__init__(writer, row, col, height, width, fgcolor, bgcolor, fgcolor) + self.fontcolor = fontcolor if fontcolor is not None else self.fgcolor self.x0 = col + border self.x1 = col + self.width - border self.y0 = row + border @@ -86,7 +85,7 @@ class Scale(DObject): txt = self.legendcb(self._fvalue(iv * 10)) tlen = wri.stringlen(txt) Writer.set_textpos(dev, y0, min(x, x1 - tlen)) - wri.setcolor(self.fgcolor, self.bgcolor) + wri.setcolor(self.fontcolor, self.bgcolor) wri.printstring(txt) wri.setcolor() ys = self.ldy0 # Large tick