Complete refactor as Python package.

ili9341
Peter Hinch 2020-11-05 10:10:21 +00:00
rodzic dca99bf247
commit b7e0ef153e
17 zmienionych plików z 293 dodań i 179 usunięć

262
README.md
Wyświetl plik

@ -28,6 +28,9 @@ wiring details, pin names and hardware issues.
# Contents # Contents
1. [Introduction](./README.md#1-introduction) 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. [Files and Dependencies](./README.md#2-files-and-dependencies)
2.1 [Dependencies](./README.md#21-dependencies) 2.1 [Dependencies](./README.md#21-dependencies)
2.2.1 [Monochrome use](./README.md#211-monochrome-use) 2.2.1 [Monochrome use](./README.md#211-monochrome-use)
@ -44,12 +47,40 @@ wiring details, pin names and hardware issues.
# 1. Introduction # 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 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 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 Existing users should re-install from scratch. In existing applications, import
display driver is subclassed from the `framebuf` class. Display drivers include: 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 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). * 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). is [here](./drivers/sharp/README.md).
Widgets are intended for the display of data from physical devices such as 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 sensors. They are drawn using graphics primitives rather than icons to minimise
is because there are no `frmebuf`- based display drivers for screens with a RAM usage. It also enables them to be effciently rendered at arbitrary scale on
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
devices with restricted processing power. The approach also enables widgets to 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. dynamic color changes in conjunction with moving elements.
Owing to RAM requirements and limitations on communication speed, `framebuf` Owing to RAM requirements and limitations on communication speed, `framebuf`
based display drivers are intended for physically small displays with limited 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 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 inches: this involves some compromises.
on screen by offering the option of dynamically changing colors.
Copying the contents of the frame buffer to the display is relatively slow. The 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 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 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 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, avoid 'out of memory' errors it is best to instantiate the display before
possibly before importing many other modules. The `aclock.py` and `alevel.py` importing other modules. The demos illustrate this.
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 # 2. Files and Dependencies
In general installation comprises copying the `ngui` and `drivers` directories, Firmware should be V1.13 or later.
with their contents, to the target hardware
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 Files
### 2.1.1 Core 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. * `nanogui.py` The library.
* `writer.py` Module for rendering Python fonts. * `writer.py` Module for rendering Python fonts.
* `fplot.py` The graph plotting module. * `fplot.py` The graph plotting module.
* `ssd1306_setup.py` Applications using an SSD1306 monochrome OLED display * `colors.py` Color constants.
import this file to determine hardware initialisation. On non Pyboard targets
this will require adaptation to match the hardware connections.
* `framebuf_utils.mpy` Accelerator for the `CWriter` class. This optional file * `framebuf_utils.mpy` Accelerator for the `CWriter` class. This optional file
is compiled for STM hardware and will be ignored on other ports. Instructions is compiled for STM hardware and will be ignored on other ports unless
and code for compiling for other architectures may be found 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). [here](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost).
### 2.1.2 Demo scripts ### 2.1.2 Demo scripts
The `ngui/demos` directory contains test/demo scripts. In general these will The `gui/demos` directory contains test/demo scripts.
need minor adaptation to match your display hardware.
* `mono_test.py` Tests/demos using the official SSD1306 library for a * `mono_test.py` Tests/demos using the official SSD1306 library for a
monochrome 128*64 OLED display. 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` 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. 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. * `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 ### 2.1.3 Fonts
Python font files are in the root directory. This facilitates freezing them to Python font files are in the `gui/fonts` directory. The easiest way to conserve
conserve RAM. Python fonts may be created using RAM is to freeze them which is highly recommended. In doing so the directory
[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git). structure must be maintained. Python fonts may be created using
Supplied examples are: [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` * `arial10.py` Variable pitch Arial in various sizes.
* `courier20.py` * `arial35.py`
* `arial_50.py`
* `courier20.py` Fixed pitch font.
* `font6.py` * `font6.py`
* `font10.py`
* `freesans20.py` * `freesans20.py`
Demos showing the use of `nanogui` with `uasyncio` may be found [here](./ASYNC.md).
## 2.2 Dependencies ## 2.2 Dependencies
All applicatons require a device driver for the display in use plus any Python The source tree now includes all dependencies. These are listed to enable users
font files in use. The following is required by all applications: to check for newer versions.
* [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py) * [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py)
Provides text rendering. 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 ### 2.2.1 Monochrome use
OLED displays using the SSD1306 chip require: The official driver for OLED displays using the SSD1306 chip is provided, but
* [ssd1306_setup.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/ssd1306_setup.py) the source is here:
Contains wiring information. * [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
* The official [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) * [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git)
### 2.2.2 Color use ### 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). If using the Adafruit 1.5 or 1.27 inch color OLED displays it is suggested that
Driver for SSD1331 controller. after installing the GUI the following script is pasted at the REPL. This will
* [Adafruit 1.5 and 1.27 inch color OLEDs](./drivers/ssd1351/README.md) verify the hardware. Please change `height` to 128 if using the 1.5 inch
Driver for SSD1351 controller. display. Note the commented-out cross platform alternative.
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.
```python ```python
import machine import machine
from ssd1351 import SSD1351 as SSD from drivers.ssd1351.ssd1351 import SSD1351 as SSD
# from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(1) spi = machine.SPI(2)
ssd = SSD(spi, pcs, pdc, prst, height=96) # Ensure height is correct (96/128) ssd = SSD(spi, pcs, pdc, prst, height=96) # Ensure height is correct (96/128)
ssd.fill(0) ssd.fill(0)
ssd.line(0, 0, 127, 95, ssd.rgb(0, 255, 0)) # Green diagonal corner-to-corner 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.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) # Red square at top left
ssd.show() 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) ###### [Contents](./README.md#contents)
# 3. The nanogui module # 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 (monochrome) or `CWriter` (colour) classes. Upside down rendering is not
supported: attempts to specify it will produce unexpected results. 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 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 `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 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 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 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 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 immediately displayed. To update the display `nanogui.refresh` is called: this
ensures that the `framebuf` contents are updated before copying the contents to enables multiple updates to the `framebuf` contents before once copying the
the display. This postponement is for performance reasons and to provide the buffer to the display. Postponement is for performance and provides a visually
appearance of a rapid update. rapid update.
## 3.1 Initialisation ## 3.1 Initialisation
The GUI is initialised in the following stages. The aim is to allocate the 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 `framebuf` before importing other modules. This is intended to reduce the risk
of memory failures when instantiating a large framebuf in an application which 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: Firstly set the display height and import the driver:
```python ```python
height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch
import machine import machine
import gc 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 Then set up the bus (SPI or I2C) and instantiate the display. At this point the
framebuffer is created: 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` Python font and create a `CWriter` instance (for monochrome displays a `Writer`
is used): is used):
```python ```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. refresh(ssd) # Initialise and clear display.
from writer import CWriter # Import other modules from gui.core.writer import CWriter # Import other modules
import arial10 # Font import gui.fonts.arial10 # Font
GREEN = SSD.rgb(0, 255, 0) # Define colors from gui.core.colors import * # Define colors
RED = SSD.rgb(255, 0, 0)
BLUE = SSD.rgb(0, 0, 255)
YELLOW = SSD.rgb(255, 255, 0)
BLACK = 0
CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it
# Instantiate any CWriters to be used (one for each font) # 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) 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 height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch
import machine import machine
import gc import gc
from ssd1351 import SSD1351 as SSD # Import the display driver from drivers.ssd1351.ssd1351 import SSD1351 as SSD
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(1) spi = machine.SPI(2)
gc.collect() # Precaution before instantiating framebuf gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance 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. refresh(ssd) # Initialise and clear display.
from writer import CWriter # Import other modules from gui.core.writer import CWriter # Import other modules
import freesans20 # Font import gui.fonts.freesans20 as freesans20 # Font
GREEN = SSD.rgb(0, 255, 0) # Define colors GREEN = SSD.rgb(0, 255, 0) # Define colors
BLACK = 0 BLACK = 0
CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 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 The `Writer` (monochrome) or `CWriter` (color) classes and the `nanogui` module
should then work automatically. should then work automatically.
If a display uses I2C note that owing to Drivers for displays using I2C may need to use
[this issue](https://github.com/micropython/micropython/pull/4020) soft I2C [I2C.writevto](http://docs.micropython.org/en/latest/library/machine.I2C.html?highlight=writevto#machine.I2C.writevto)
may be required, depending on the detailed specification of the chip. depending on the chip requirements.
###### [Contents](./README.md#contents) ###### [Contents](./README.md#contents)

Wyświetl plik

@ -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. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch # 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 # 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 # when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules. # possible before importing other modules.
@ -17,10 +22,14 @@
# Y6 CLK (2 CL SCK) # Y6 CLK (2 CL SCK)
# Y8 DATA (1 SI MOSI) # Y8 DATA (1 SI MOSI)
height = 96 # 1.27 inch 96*128 (rows*cols) display
import machine import machine
import gc 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 from drivers.ssd1351.ssd1351 import SSD1351 as SSD
height = 96 # 1.27 inch 96*128 (rows*cols) display height = 96 # 1.27 inch 96*128 (rows*cols) display

Wyświetl plik

@ -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 3. `clock_batt.py` As above but designed for low power operation. Pyboard
specific. specific.
`sharptest` should not be run for long periods as it does not regularly refresh Tests assume that `nanogui` is installed as per the instructions. `sharptest`
the display. It tests `writer.py` and some `framebuffer` graphics primitives. should not be run for long periods as it does not regularly refresh the
`clocktest` tests `nanogui.py`. 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 The `clock_batt.py` demo needs `upower.py` from
be copied to the device or frozen as bytecode. The `clack_batt.py` demo needs
`upower.py` from
[micropython-micropower](https://github.com/peterhinch/micropython-micropower). [micropython-micropower](https://github.com/peterhinch/micropython-micropower).
Testing was done on a Pyboard D SF6W: frozen bytecode was not required. I 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 # 3. Device driver constructor

Wyświetl plik

@ -20,7 +20,7 @@
import machine import machine
import gc import gc
from sharp import SHARP as SSD from drivers.sharp.sharp import SHARP as SSD
# Initialise hardware # Initialise hardware
pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high
@ -30,14 +30,18 @@ ssd = SSD(spi, pcs)
# Now import other modules # Now import other modules
import upower 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 pyb
import cmath import cmath
from writer import Writer
from gui.core.writer import Writer
# Fonts for Writer # Fonts for Writer
import freesans20 as font_small import gui.fonts.freesans20 as font_small
import arial35 as font_large import gui.fonts.arial35 as font_large
refresh(ssd) # Initialise display. refresh(ssd) # Initialise display.

Wyświetl plik

@ -18,7 +18,7 @@
import machine import machine
import gc import gc
from sharp import SHARP as SSD from drivers.sharp.sharp import SHARP as SSD
# Initialise hardware # Initialise hardware
pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high 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) ssd = SSD(spi, pcs)
# Now import other modules # 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 cmath
import utime import utime
from writer import Writer
from gui.core.writer import Writer
# Fonts for Writer # Fonts for Writer
import freesans20 as font_small import gui.fonts.freesans20 as font_small
import arial35 as font_large import gui.fonts.arial35 as font_large
refresh(ssd) # Initialise display. refresh(ssd) # Initialise display.

Wyświetl plik

@ -14,15 +14,18 @@
# Y5 CS # Y5 CS
import machine import machine
from sharp import SHARP from drivers.sharp.sharp import SHARP as SSD
import freesans20, arial_50 # Fonts for Writer
from writer import Writer import gui.fonts.freesans20 as freesans20
import gui.fonts.arial_50 as arial_50
from gui.core.writer import Writer
import time import time
def test(): def test():
pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high
spi = machine.SPI(2) spi = machine.SPI(2)
ssd = SHARP(spi, pcs) ssd = SSD(spi, pcs)
rhs = ssd.width -1 rhs = ssd.width -1
ssd.line(rhs - 80, 0, rhs, 80, 1) ssd.line(rhs - 80, 0, rhs, 80, 1)
square_side = 40 square_side = 40

Wyświetl plik

@ -3,13 +3,18 @@
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch # Copyright (c) 2020 Peter Hinch
from drivers.ssd1351.ssd1351 import SSD1351 as SSD from color_setup import SSD
GREEN = SSD.rgb(0, 255, 0) GREEN = SSD.rgb(0, 255, 0)
RED = SSD.rgb(255, 0, 0) RED = SSD.rgb(255, 0, 0)
LIGHTRED = SSD.rgb(140, 0, 0)
BLUE = SSD.rgb(0, 0, 255) BLUE = SSD.rgb(0, 0, 255)
YELLOW = SSD.rgb(255, 255, 0) YELLOW = SSD.rgb(255, 255, 0)
BLACK = 0 BLACK = 0
WHITE = SSD.rgb(255, 255, 255) 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) LIGHTGREEN = SSD.rgb(0, 100, 0)
DARKGREEN = SSD.rgb(0, 80, 0)
DARKBLUE = SSD.rgb(0, 0, 90)

Wyświetl plik

@ -5,18 +5,8 @@
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch # Copyright (c) 2018-2020 Peter Hinch
# WIRING # Initialise hardware and framebuf before importing modules.
# Pyb SSD from color_setup import ssd, height # Create a display instance
# 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
from gui.core.nanogui import refresh from gui.core.nanogui import refresh
from gui.widgets.label import Label from gui.widgets.label import Label
from gui.widgets.dial import Dial, Pointer from gui.widgets.dial import Dial, Pointer

Wyświetl plik

@ -1,28 +1,12 @@
# alevel.py Test/demo program for Adafruit ssd1351-based OLED displays # alevel.py Test/demo "spirit level" program.
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 # Requires Pyboard for accelerometer.
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 # Tested with Adafruit ssd1351 OLED display.
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch # Copyright (c) 2018-2020 Peter Hinch
# WIRING # Initialise hardware and framebuf before importing modules.
# Pyb SSD from color_setup import ssd # Create a display instance
# 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
from gui.core.nanogui import refresh from gui.core.nanogui import refresh
from gui.widgets.dial import Dial, Pointer from gui.widgets.dial import Dial, Pointer

Wyświetl plik

@ -6,8 +6,8 @@
# Copyright (c) 2020 Peter Hinch # Copyright (c) 2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file # Released under the MIT License (MIT) - see LICENSE file
# Initialise hardware and framebuf before importing modules # 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 uasyncio as asyncio
import pyb import pyb

Wyświetl plik

@ -1,13 +1,12 @@
# asnano_sync.py Test/demo program for use of nanogui with uasyncio # asnano_sync.py Test/demo program for use of nanogui with uasyncio
# Uses Adafruit ssd1351-based OLED displays (change height to suit) # Requires Pyboard for switch and LEDs.
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 # Tested with Adafruit ssd1351 OLED display.
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# Copyright (c) 2020 Peter Hinch # Copyright (c) 2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file # Released under the MIT License (MIT) - see LICENSE file
# Initialise hardware and framebuf before importing modules # 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 uasyncio as asyncio
import pyb import pyb

Wyświetl plik

@ -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.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# For wiring details see drivers/ADAFRUIT.md in this repo. # For wiring details see drivers/ADAFRUIT.md in this repo.
@ -6,7 +7,8 @@
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch # 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 cmath
import utime import utime

Wyświetl plik

@ -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 # https://www.adafruit.com/product/684
# For wiring details see drivers/ADAFRUIT.md in this repo. # For wiring details see drivers/ADAFRUIT.md in this repo.
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch # 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.core.nanogui import refresh
from gui.widgets.led import LED from gui.widgets.led import LED

Wyświetl plik

@ -1,12 +1,14 @@
# fpt.py Test/demo program for framebuf plot # fpt.py Test/demo program for framebuf plot. Cross-patform,
# Uses Adafruit ssd1351-based OLED displays (change height to suit) # 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.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch # 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 cmath
import math import math
@ -17,7 +19,6 @@ from gui.core.fplot import PolarGraph, PolarCurve, CartesianGraph, Curve, TSeque
from gui.core.nanogui import refresh from gui.core.nanogui import refresh
from gui.widgets.label import Label from gui.widgets.label import Label
refresh(ssd) refresh(ssd)
# Fonts # Fonts

Wyświetl plik

@ -6,19 +6,33 @@
# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display # https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display
# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html # 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 utime
import uos # import uos
from ssd1306_setup import WIDTH, HEIGHT, setup from ssd1306_setup import WIDTH, HEIGHT, setup
from gui.core.writer import Writer, CWriter 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 # Fonts
import gui.fonts.arial10 as arial10 import gui.fonts.arial10 as arial10
import gui.courier20 as fixed import gui.fonts.courier20 as fixed
import gui.fonts.font6 as small 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): def fields(use_spi=False, soft=True):
ssd = setup(use_spi, soft) # Create a display instance ssd = setup(use_spi, soft) # Create a display instance
Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it 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) numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bdcolor=None)
countfield = Label(wri, 0, 90, wri.stringlen('1')) countfield = Label(wri, 0, 90, wri.stringlen('1'))
n = 1 n = 1
random = xorshift64star(65535)
for s in ('short', 'longer', '1', ''): for s in ('short', 'longer', '1', ''):
textfield.value(s) 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)) countfield.value('{:1d}'.format(n))
n += 1 n += 1
refresh(ssd) 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 nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border
y += dy y += dy
random = xorshift64star(2**24 - 1)
for _ in range(10): for _ in range(10):
for field in nfields: for field in nfields:
value = int.from_bytes(uos.urandom(3),'little')/167772 value = random() / 167772
field.value('{:5.2f}'.format(value)) field.value('{:5.2f}'.format(value))
refresh(ssd) refresh(ssd)
utime.sleep(1) 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')) 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')) m2 = Meter(wri, 5, 86, height = 50, divisions = 4, legends=('-1', '0', '+1'))
steps = 10 steps = 10
random = xorshift64star(2**24 - 1)
for n in range(steps + 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) m1.value(n/steps)
m2.value(1 - n/steps) m2.value(1 - n/steps)
refresh(ssd) refresh(ssd)

Wyświetl plik

@ -5,8 +5,10 @@
# Usage: # Usage:
# import gui.demos.scale # 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.nanogui import refresh
from gui.core.writer import CWriter from gui.core.writer import CWriter
@ -48,12 +50,26 @@ async def default(scale, lbl):
def test(): 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. refresh(ssd) # Initialise and clear display.
CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
wri.set_clip(True, True, False) wri.set_clip(True, True, False)
lbl = Label(wri, ssd.height - wri.height - 2, 0, 50) scale1 = Scale(wri, 2, 2, width = 124, legendcb = legendcb,
scale = Scale(wri, 5, 5) 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)) asyncio.run(default(scale, lbl))
test() test()

Wyświetl plik

@ -6,8 +6,6 @@
# Usage: # Usage:
# from gui.widgets.scale import Scale # from gui.widgets.scale import Scale
# NEED print_left, get_stringsize
from gui.core.nanogui import DObject from gui.core.nanogui import DObject
from gui.core.writer import Writer from gui.core.writer import Writer
from gui.core.colors import BLACK from gui.core.colors import BLACK
@ -34,6 +32,7 @@ class Scale(DObject):
ctrl_ht = height - min_ht # adjust ticks for greater height ctrl_ht = height - min_ht # adjust ticks for greater height
width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, fgcolor) 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.x0 = col + border
self.x1 = col + self.width - border self.x1 = col + self.width - border
self.y0 = row + border self.y0 = row + border
@ -86,7 +85,7 @@ class Scale(DObject):
txt = self.legendcb(self._fvalue(iv * 10)) txt = self.legendcb(self._fvalue(iv * 10))
tlen = wri.stringlen(txt) tlen = wri.stringlen(txt)
Writer.set_textpos(dev, y0, min(x, x1 - tlen)) 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.printstring(txt)
wri.setcolor() wri.setcolor()
ys = self.ldy0 # Large tick ys = self.ldy0 # Large tick