README improvements. Fix writer to import framebuf_utils.mpy

ili9341
Peter Hinch 2020-11-06 14:38:48 +00:00
rodzic 4ef49c8758
commit 9c82154262
2 zmienionych plików z 106 dodań i 80 usunięć

163
README.md
Wyświetl plik

@ -36,7 +36,8 @@ wiring details, pin names and hardware issues.
2.2.1 [Monochrome use](./README.md#211-monochrome-use)
2.2.2 [Color use](./README.md#222-color-use)
3. [The nanogui module](./README.md#3-the-nanogui-module)
3.1 [Initialisation](./README.md#31-initialisation) Initial setup and refresh method.
3.1 [Application Initialisation](./README.md#31-application-initialisation) Initial setup and refresh method.
     3.1.1 [Setup file internals](./README.md#311-setup-file-internals)
3.2 [Label class](./README.md#32-label-class) Dynamic text at any screen location.
3.3 [Meter class](./README.md#33-meter-class) A vertical panel meter.
3.4 [LED class](./README.md#34-led-class) Virtual LED of any color.
@ -157,14 +158,15 @@ for SSD1351 displays only the following is actually required:
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.
* `color_setup.py` Setup for 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.
* `esp32_setup.py` After editing to match the display and wiring, this should
be copied to the target as `/pyboard/color_setup.py`.
* `esp32_setup.py` As written supports an ESP32 connected to a 128x128 SSD1351
display. After editing to match the display and wiring, it should be copied to
the target as `/pyboard/color_setup.py`.
The `gui/core` directory contains the GUI core and its principal dependencies:
@ -182,7 +184,7 @@ The `gui/core` directory contains the GUI core and its principal dependencies:
The `gui/demos` directory contains test/demo scripts.
* `mono_test.py` Tests/demos using the official SSD1306 library for a
* `mono_test.py` Tests/demos using the official SSD1306 driver for a
monochrome 128*64 OLED display.
* `color96.py` Tests/demos for the Adafruit 0.96 inch color OLED.
* `color15.py` Similar for Adafruit 1.27 inch and 1.5 inch color OLEDs. Edit
@ -197,7 +199,7 @@ display so long as `color_setup.py` has the correct `height` value.
* `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
Compatibility with `uasyncio` and the last two demos is discussed
[here](./ASYNC.md).
Demo scripts for Sharp displays are in `drivers/sharp`. Check source code for
@ -240,20 +242,23 @@ Optional feature:
### 2.2.1 Monochrome use
The official driver for OLED displays using the SSD1306 chip is provided, but
the source is here:
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).
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)
The Sharp display is supported in `gui/drivers/sharp`. See README.md and demos.
### 2.2.2 Color use
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.
Drivers for Adafruit 0.96", 1.27" and 1.5" OLEDS 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 slightly slower cross platform
alternatives in the directory - see the code below for usage on ESP32.
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
@ -293,40 +298,71 @@ ssd.show()
# 3. The nanogui module
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.
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 GUI supports a variety of widgets, some of which include text elements. 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]`.
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
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.
instant update.
## 3.1 Initialisation
Text components of widgets are rendered using the `Writer` (monochrome) or
`CWriter` (colour) classes.
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. Note that the hardware dependent code is located in
`color_setup.py`: it is illustrated here to explain the process.
## 3.1 Application Initialisation
Firstly set the display height and import the driver:
The GUI is initialised for color display by issuing:
```python
from color_setup import ssd, height
```
This works as described [below](./README.md#311-setup-file-internals).
A typical application then imports `nanogui` modules and clears the display:
```python
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.
```
This is followed by Python fonts. A `CWriter` instance is created for each
font (for monochrome displays a `Writer` is used). Note that upside down
rendering is not supported; current widgets do not support scrolling text.
```python
from gui.core.writer import CWriter # Renders color text
import gui.fonts.arial10 # A Python Font
from gui.core.colors import * # Standard color constants
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) # Colors are defaults
wri.set_clip(True, True, False)
```
The application calls `nanogui.refresh` on initialisation to clear the display,
then subsequently whenever a refresh is required. The method takes two args:
1. `device` The display instance (supports multiple displays).
2. `clear=False` If set `True` the display will be blanked; it is also
blanked when a device is refreshed for the first time.
### 3.1.1 Setup file internals
The file `color_setup.py` contains the hardware dependent code. It works as
described below, with the aim of allocating the `framebuf` before importing
other modules. This is intended to reduce the risk of memory failures.
Firstly the file sets 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 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:
It then ses up the bus (SPI or I2C) and instantiates the display. At this point
the framebuffer is created:
```python
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
@ -335,34 +371,6 @@ spi = machine.SPI(1)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
```
Finally import `nanogui` modules and initialise the display. Import any other
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 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 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) # Colors are defaults
wri.set_clip(True, True, False)
```
The `nanogui.refresh` method takes two args:
1. `device` The display instance (supports multiple displays).
2. `clear=False` If set `True` the display will be blanked; it is also
blanked when a device is refreshed for the first time.
It should be called after instantiating the display, and again whenever the
physical display is to be updated.
###### [Contents](./README.md#contents)
@ -518,12 +526,24 @@ Methods:
## 3.5 Dial and Pointer classes
A dial is a circular analogue clock style display showing a set of pointers. To
use, the `Dial` is instantiated then one or more `Pointer` objects are
A `Dial` is a circular display capable of displaying a number of vectors; each
vector is represented by a `Pointer` instance. The format of the display may be
chosen to resemble an analog clock or a compass. In the `CLOCK` case a pointer
resembles a clock's hand extending from the centre towards the periphery. In
the `COMPASS` case pointers are chevrons extending equally either side of the
circle centre.
In both cases the length, angle and color of each `Pointer` may be changed
dynamically. A `Dial` can include an optional `Label` at the bottom which may
be used to display any required text.
In use, a `Dial` is instantiated then one or more `Pointer` objects are
instantiated and assigned to it. The `Pointer.value` method enables the `Dial`
to be updated, with the length, angle and color being dynamically variable.
to be updated affecting the length, angle and color of the `Pointer`.
Pointer values are complex numbers.
### Dial class
Constructor positional args:
1. `writer` The `Writer` instance (font and screen) to use.
2. `row` Location on screen.
@ -548,18 +568,19 @@ Keyword only args:
When a `Pointer` is instantiated it is assigned to the `Dial` by the `Pointer`
constructor.
The `Pointer` class:
### Pointer class
Constructor arg:
1. `dial` The `Dial` instance on which it is to be dsplayed.
Methods:
1. `value` Args:
* `v=None` The value is a complex number. If its magnitude exceeds unity it
is reduced (preserving phase) to constrain it to the boundary of the unit
* `v=None` The value is a complex number. A magnitude exceeding unity is
reduced (preserving phase) to constrain the `Pointer` within the unit
circle.
* `color=None` By default the pointer is rendered in the foreground color
of the parent `Dial`. Otherwise the passed color is used.
of the parent `Dial`. Otherwise the passed color is used.
Returns the current value.
2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:
* `text=None` The text to display. If `None` displays last value.
* ` invert=False` If true, show inverse text.
@ -580,7 +601,7 @@ def clock(ssd, wri):
mins.value(0 + 0.9j, YELLOW)
dm = cmath.exp(-1j * cmath.pi / 30) # Rotate by 1 minute
dh = cmath.exp(-1j * cmath.pi / 1800) # Rotate hours by 1 minute
# Twiddle the hands: see clock.py for an actual clock
# Twiddle the hands: see aclock.py for an actual clock
for _ in range(80):
utime.sleep_ms(200)
mins.value(mins.value() * dm, RED)
@ -597,7 +618,7 @@ on old radios where a large scale scrolls past a small window having a fixed
pointer. This enables a scale with (say) 200 graduations (ticks) to readily be
visible on a small display, with sufficient resolution to enable the user to
interpolate between ticks. Default settings enable estimation of a value to
within +-0.1%.
within about +-0.1%.
Legends for the scale are created dynamically as it scrolls past the window.
The user may control this by means of a callback. The example `lscale.py`
@ -662,7 +683,7 @@ def tickcb(f, c):
return c
```
### increasing the ticks value
### Increasing the ticks value
This increases the precision of the display.

Wyświetl plik

@ -19,17 +19,22 @@
# Slow method 2700μs typical, up to 11ms on larger fonts
import framebuf
fast_mode = False
try:
from framebuf_utils import render
fast_mode = True
except ImportError:
pass
except ValueError:
print('Ignoring framebuf_utils.mpy: it was compiled for an incorrect architecture.')
from uctypes import bytearray_at, addressof
fast_mode = True
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
print('Ignoring framebuf_utils.mpy: compiled for incorrect architecture.')
class DisplayState():
def __init__(self):
self.text_row = 0