Plot module added. Release 0.1

ili9341
Peter Hinch 2018-08-29 18:16:13 +01:00
rodzic 82be06767f
commit ca99637fb1
23 zmienionych plików z 3708 dodań i 2 usunięć

477
README.md
Wyświetl plik

@ -1,2 +1,475 @@
# micropython-nano-gui
A lightweight MicroPython GUI library for display drivers based on framebuf class
A lightweight and minimal MicroPython GUI library for display drivers based on
the `framebuf` class. With the exception of the Nokia 5110, such drivers are
currently for color and monochrome OLED displays. This is coincidental.
These images don't do justice to the OLED displays which are visually
impressive with bright colors and extreme contrast. For some reason they are
quite hard to photograph.
![Image](images/IMG_2885.png)
One of the demos running on an Adafruit 1.27 inch OLED. The colors change
dynamically with low values showing green, intermediate yellow and high red. In
reality the colors are vivid: the right hand meter bar and LED are a brilliant
yellow and the centre one is vivid green.
![Image](images/IMG_2887.png)
The Dial object.
There is an optional [graph plotting module](./plot/FPLOT.md) for basic
Cartesian and polar plots, also realtime plotting including time series.
Notes on [Adafruit and other OLED displays](./drivers/ADAFRUIT.md) including
wiring details, pin names and hardware issues.
**NOTE** 20 Sep 2018 Under development. I am now reasonably happy with the API
but I can't yet promise no changes. There may, of course, be bugs...
# Contents
1. [Introduction](./README.md#1-introduction)
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)
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.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.
3.5 [Dial and Pointer classes](./README.md#35-dial-and-pointer-classes) Clock
or compass style display of one or more pointers.
4. [Device drivers](./README.md#4-device-drivers) Device driver compatibility
requirements (these are minimal).
# 1. Introduction
This library provides a limited set of GUI objects (widgets) for displays whose
display driver is subclassed from the `framebuf` class. Examples are:
* 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 [Adafruit 0.96 inch color OLED](https://www.adafruit.com/product/684)
with [this driver](https://github.com/peterhinch/micropython-nano-gui/tree/master/drivers/ssd1331).
* A driver for [Adafruit 1.5 inch OLED](https://www.adafruit.com/product/1431)
and [Adafruit 1.27 inch OLED](https://www.adafruit.com/product/1673) may be
found [here](./drivers/ssd1351/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 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.
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.
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.
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.
# 2. Files and Dependencies
## 2.1 Files
* `nanogui.py` The library.
* `mono_test.py` Tests/demos using the official SSD1306 library 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
the `height = 96` line as per the comment for the larger display.
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.
* `alevel.py` Spirit level using Pyboard accelerometer.
Sample fonts created by [font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git):
* `arial10.py`
* `courier20.py`
* `font6.py`
* `freesans20.py`
## 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:
* [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py)
Provides text rendering.
### 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).
Displays based on the PCD8544 chip require:
* [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git)
### 2.2.2 Color use
Supported displays amd their drivers are listed below:
* [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.
```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)
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()
```
###### [Contents](./README.md#contents)
# 3. The nanogui module
This 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].
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.
## 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.
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
```
Then set up the bus (SPI or I2C) and instantiate 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)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
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 nanogui import Label, Dial, Pointer, refresh # Whatever you need
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
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.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)
## 3.2 Label class
This supports applications where text is to be rendered at specific screen
locations.
Text can be static or dynamic. In the case of dynamic text the background is
cleared to ensure that short strings cleanly replace longer ones.
Labels can be displayed with an optional single pixel border.
Colors are handled flexibly. By default the colors used are those of the
`Writer` instance, however they can be changed dynamically; this might be used
to warn of overrange or underrange values.
Constructor args:
1. `writer` The `Writer` instance (font and screen) to use.
2. `row` Location on screen.
3. `col`
4. `text` If a string is passed it is displayed: typically used for static
text. If an integer is passed it is interpreted as the maximum text length
in pixels; typically obtained from `writer.stringlen('-99.99')`. Nothing is
dsplayed until `.value()` is called. Intended for dynamic text fields.
5. `invert=False` Display in inverted or normal style.
6. `fgcolor=None` Optionally override the `Writer` colors.
7. `bgcolor=None`
8. `bdcolor=False` If `False` no border is displayed. If `None` a border is
shown in the `Writer` forgeround color. If a color is passed, it is used.
The constructor displays the string at the required location.
Methods:
1. `value` Redraws the label. This takes the following args:
* `text=None` The text to display. If `None` displays last value.
* ` invert=False` If true, show inverse text.
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
* `bgcolor=None` Background color, as per foreground.
* `bdcolor=None` Border color. As per above except that if `False` is
passed, no border is displayed. This clears a previously drawn border.
Returns the current text string.
2. `show` No args. (Re)draws the label. Primarily for internal use by GUI.
If populating a label would cause it to extend beyond the screen boundary a
warning is printed at the console. The label may appear at an unexpected place.
The following is a complete "Hello world" script.
```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
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)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
from nanogui import Label, refresh
refresh(ssd) # Initialise and clear display.
from writer import CWriter # Import other modules
import 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
wri = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)
wri.set_clip(True, True, False)
# End of boilerplate code. This is our application:
Label(wri, 2, 2, 'Hello world!')
refresh(ssd)
```
###### [Contents](./README.md#contents)
## 3.3 Meter class
This provides a vertical linear meter display of values scaled between 0.0 and
1.0.
Constructor positional args:
1. `writer` The `Writer` instance (font and screen) to use.
2. `row` Location on screen.
3. `col`
Keyword only args:
4. `height=50` Height of meter.
5. `width=10` Width.
6. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
7. `bgcolor=None` Background color, as per foreground.
8. `ptcolor=None` Color of meter pointer or bar. Default is foreground color.
9. `bdcolor=False` If `False` no border is displayed. If `None` a border is
shown in the `Writer` forgeround color. If a color is passed, it is used.
10. `divisions=5` No. of gradutions to show.
11. `label=None` A text string will cause a `Label` to be drawn below the
meter. An integer will create a `Label` of that width for later use.
12. `style=Meter.LINE` The pointer is a horizontal line. `Meter.BAR` causes a
vertical bar to be displayed.
13. `legends=None` If a tuple of strings is passed, `Label` instances will be
displayed to the right hand side of the meter, starting at the bottom. E.G.
`('0.0', '0.5', '1.0')`
14. `value=None` Initial value. If `None` the meter will not be drawn until
its `value()` method is called.
Methods:
1. `value` Args: `n=None, color=None`.
* `n` should be a float in range 0 to 1.0. Causes the meter to be updated.
Out of range values are constrained. If `None` is passed the meter is not
updated.
* `color` Updates the color of the bar or line if a value is also passed.
`None` causes no change.
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.
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
* `bgcolor=None` Background color, as per foreground.
* `bdcolor=None` Border color. As per above except that if `False` is
passed, no border is displayed. This clears a previously drawn border.
3. `show` No args. (Re)draws the meter. Primarily for internal use by GUI.
###### [Contents](./README.md#contents)
## 3.4 LED class
This is a virtual LED whose color may be altered dynamically.
Constructor positional args:
1. `writer` The `Writer` instance (font and screen) to use.
2. `row` Location on screen.
3. `col`
Keyword only args:
4. `height=12` Height of LED.
5. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
6. `bgcolor=None` Background color, as per foreground.
7. `bdcolor=False` If `False` no border is displayed. If `None` a border is
shown in the `Writer` forgeround color. If a color is passed, it is used.
8. `label=None` A text string will cause a `Label` to be drawn below the
LED. An integer will create a `Label` of that width for later use.
Methods:
1. `color` arg `c=None` Change the LED color to `c`. If `c` is `None` the LED
is turned off (rendered in the background color).
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.
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
* `bgcolor=None` Background color, as per foreground.
* `bdcolor=None` Border color. As per above except that if `False` is
passed, no border is displayed. This clears a previously drawn border.
3. `show` No args. (Re)draws the LED. Primarily for internal use by GUI.
###### [Contents](./README.md#contents)
## 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
instantiated and assigned to it. The `Pointer.value` method enables the `Dial`
to be updated, with the length, angle and color being dynamically variable.
Pointer values are complex numbers.
Constructor positional args:
1. `writer` The `Writer` instance (font and screen) to use.
2. `row` Location on screen.
3. `col`
Keyword only args:
4. `height=50` Height and width of dial.
5. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
6. `bgcolor=None` Background color, as per foreground.
7. `bdcolor=False` If `False` no border is displayed. If `None` a border is
shown in the `Writer` forgeround color. If a color is passed, it is used.
8. `ticks=4` No. of gradutions to show.
9. `label=None` A text string will cause a `Label` to be drawn below the
meter. An integer will create a `Label` of that width for later use.
10. `style=Dial.CLOCK` Pointers are drawn from the centre of the circle as per
the hands of a clock. `Dial.COMPASS` causes pointers to be drawn as arrows
centred on the control's centre. Arrow tail chevrons are suppressed for very
short pointers.
11. `pip=None` Draws a central dot. A color may be passed, otherwise the
foreground color will be used. If `False` is passed, no pip will be drawn. The
pip is suppressed if the shortest pointer would be hard to see.
When a `Pointer` is instantiated it is assigned to the `Dial` by the `Pointer`
constructor.
The `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
circle.
* `color=None` By default the pointer is rendered in the foreground color
of the parent `Dial`. Otherwise the passed color is used.
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.
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
* `bgcolor=None` Background color, as per foreground.
* `bdcolor=None` Border color. As per above except that if `False` is
passed, no border is displayed. This clears a previously drawn border.
3. `show` No args. (Re)draws the control. Primarily for internal use by GUI.
Typical usage (`ssd` is the device and `wri` is the current `Writer`):
```python
def clock(ssd, wri):
# Border in Writer foreground color:
dial = Dial(wri, 5, 5, ticks = 12, bdcolor=None)
hrs = Pointer(dial)
mins = Pointer(dial)
hrs.value(0 + 0.7j, RED)
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
for _ in range(80):
utime.sleep_ms(200)
mins.value(mins.value() * dm, RED)
hrs.value(hrs.value() * dh, YELLOW)
refresh(ssd)
```
# 4. Device drivers
For a driver to support `nanogui` it must be subclassed from `framebuf` and
provide `height` and `width` bound variables defining the display size in
pixels. This is all that is required for monochrome drivers.
For color drivers, to conserve RAM it is suggested that 8-bit color is used
for the `framebuf`. If the hardware does not support this, conversion to the
supported color space needs to be done "on the fly" as per the SSD1351 driver.
Since this is likely to be slow, consider using native, viper or assembler.
Color drivers should have a static method converting rgb(255, 255, 255) to a
form acceptable to the driver. For 8-bit rrrgggbb this can be:
```python
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
```
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.
###### [Contents](./README.md#contents)

104
aclock.py 100644
Wyświetl plik

@ -0,0 +1,104 @@
# aclock.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
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# WIRING
# Pyb SSD
# 3v3 Vin
# Gnd Gnd
# X1 DC
# X2 CS
# X3 Rst
# X6 CLK
# X8 DATA
height = 96 # 1.27 inch 96*128 (rows*cols) display
# height = 128 # 1.5 inch 128*128 display
# 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 machine
import gc
from ssd1351 import SSD1351 as SSD
# Initialise hardware
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)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
from nanogui import Dial, Pointer, refresh, Label
refresh(ssd) # Initialise and clear display.
# Now import other modules
import cmath
import utime
from writer import CWriter
# Font for CWriter
import arial10
GREEN = SSD.rgb(0, 255, 0)
RED = SSD.rgb(255, 0, 0)
BLUE = SSD.rgb(0, 0, 255)
YELLOW = SSD.rgb(255, 255, 0)
BLACK = 0
def aclock():
uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi
pi = cmath.pi
days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday')
months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',
'Aug', 'Sept', 'Oct', 'Nov', 'Dec')
# Instantiate CWriter
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)
# Instantiate displayable objects
dial = Dial(wri, 2, 2, height = 75, ticks = 12, bdcolor=None, label=120, pip=False) # Border in fg color
lbltim = Label(wri, 5, 85, 35)
hrs = Pointer(dial)
mins = Pointer(dial)
secs = Pointer(dial)
hstart = 0 + 0.7j # Pointer lengths and position at top
mstart = 0 + 0.92j
sstart = 0 + 0.92j
while True:
t = utime.localtime()
hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360), YELLOW)
mins.value(mstart * uv(-t[4] * pi/30), YELLOW)
secs.value(sstart * uv(-t[5] * pi/30), RED)
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
dial.text('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0]))
refresh(ssd)
utime.sleep(1)
aclock()

91
alevel.py 100644
Wyświetl plik

@ -0,0 +1,91 @@
# 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
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# WIRING
# Pyb SSD
# 3v3 Vin
# Gnd Gnd
# X1 DC
# X2 CS
# X3 Rst
# X6 CLK
# X8 DATA
height = 96 # 1.27 inch 96*128 (rows*cols) display
# height = 128 # 1.5 inch 128*128 display
# 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 machine
import gc
from ssd1351 import SSD1351 as SSD
# Initialise hardware
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)
gc.collect() # Precaution befor instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
from nanogui import Dial, Pointer, refresh
refresh(ssd) # Initialise and clear display.
# Now import other modules
import utime
import pyb
from writer import CWriter
import arial10 # Font
GREEN = SSD.rgb(0, 255, 0)
RED = SSD.rgb(255, 0, 0)
BLUE = SSD.rgb(0, 0, 255)
YELLOW = SSD.rgb(255, 255, 0)
BLACK = 0
def main():
print('alevel test is running.')
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)
acc = pyb.Accel()
dial = Dial(wri, 5, 5, height = 75, ticks = 12, bdcolor=None,
label='Tilt Pyboard', style = Dial.COMPASS, pip=YELLOW) # Border in fg color
ptr = Pointer(dial)
scale = 1/40
while True:
x, y, z = acc.filtered_xyz()
# Depending on relative alignment of display and Pyboard this line may
# need changing: swap x and y or change signs so arrow points in direction
# board is tilted.
ptr.value(-y*scale + 1j*x*scale, YELLOW)
refresh(ssd)
utime.sleep_ms(200)
main()

139
arial10.py 100644
Wyświetl plik

@ -0,0 +1,139 @@
# Code generated by font-to-py.py.
# Font: Arial.ttf
version = '0.25'
def height():
return 10
def max_width():
return 11
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x06\x00\x70\x88\x08\x10\x20\x20\x00\x20\x00\x00\x03\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\x80\x80\x80\x80\x80'\
b'\x00\x80\x00\x00\x04\x00\xa0\xa0\xa0\x00\x00\x00\x00\x00\x00\x00'\
b'\x06\x00\x28\x28\xf8\x50\x50\xf8\xa0\xa0\x00\x00\x06\x00\x70\xa8'\
b'\xa0\x70\x28\x28\xa8\x70\x20\x00\x0a\x00\x62\x00\x94\x00\x94\x00'\
b'\x68\x00\x0b\x00\x14\x80\x14\x80\x23\x00\x00\x00\x00\x00\x07\x00'\
b'\x30\x48\x48\x30\x50\x8c\x88\x74\x00\x00\x02\x00\x80\x80\x80\x00'\
b'\x00\x00\x00\x00\x00\x00\x04\x00\x20\x40\x80\x80\x80\x80\x80\x80'\
b'\x40\x20\x04\x00\x80\x40\x20\x20\x20\x20\x20\x20\x40\x80\x04\x00'\
b'\x40\xe0\x40\xa0\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x20\x20'\
b'\xf8\x20\x20\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x80'\
b'\x80\x80\x04\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x03\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x03\x00\x20\x20\x40\x40'\
b'\x40\x40\x80\x80\x00\x00\x06\x00\x70\x88\x88\x88\x88\x88\x88\x70'\
b'\x00\x00\x06\x00\x20\x60\xa0\x20\x20\x20\x20\x20\x00\x00\x06\x00'\
b'\x70\x88\x08\x08\x10\x20\x40\xf8\x00\x00\x06\x00\x70\x88\x08\x30'\
b'\x08\x08\x88\x70\x00\x00\x06\x00\x10\x30\x50\x50\x90\xf8\x10\x10'\
b'\x00\x00\x06\x00\x78\x40\x80\xf0\x08\x08\x88\x70\x00\x00\x06\x00'\
b'\x70\x88\x80\xf0\x88\x88\x88\x70\x00\x00\x06\x00\xf8\x10\x10\x20'\
b'\x20\x40\x40\x40\x00\x00\x06\x00\x70\x88\x88\x70\x88\x88\x88\x70'\
b'\x00\x00\x06\x00\x70\x88\x88\x88\x78\x08\x88\x70\x00\x00\x03\x00'\
b'\x00\x00\x80\x00\x00\x00\x00\x80\x00\x00\x03\x00\x00\x00\x80\x00'\
b'\x00\x00\x00\x80\x80\x80\x06\x00\x00\x00\x08\x70\x80\x70\x08\x00'\
b'\x00\x00\x06\x00\x00\x00\x00\xf8\x00\xf8\x00\x00\x00\x00\x06\x00'\
b'\x00\x00\x80\x70\x08\x70\x80\x00\x00\x00\x06\x00\x70\x88\x08\x10'\
b'\x20\x20\x00\x20\x00\x00\x0b\x00\x1f\x00\x60\x80\x4d\x40\x93\x40'\
b'\xa2\x40\xa2\x40\xa6\x80\x9b\x00\x40\x40\x3f\x80\x08\x00\x10\x28'\
b'\x28\x28\x44\x7c\x82\x82\x00\x00\x07\x00\xf8\x84\x84\xfc\x84\x84'\
b'\x84\xf8\x00\x00\x07\x00\x38\x44\x80\x80\x80\x80\x44\x38\x00\x00'\
b'\x07\x00\xf0\x88\x84\x84\x84\x84\x88\xf0\x00\x00\x06\x00\xf8\x80'\
b'\x80\xf8\x80\x80\x80\xf8\x00\x00\x06\x00\xf8\x80\x80\xf0\x80\x80'\
b'\x80\x80\x00\x00\x08\x00\x38\x44\x82\x80\x8e\x82\x44\x38\x00\x00'\
b'\x07\x00\x84\x84\x84\xfc\x84\x84\x84\x84\x00\x00\x02\x00\x80\x80'\
b'\x80\x80\x80\x80\x80\x80\x00\x00\x05\x00\x10\x10\x10\x10\x10\x90'\
b'\x90\x60\x00\x00\x07\x00\x84\x88\x90\xb0\xd0\x88\x88\x84\x00\x00'\
b'\x06\x00\x80\x80\x80\x80\x80\x80\x80\xf8\x00\x00\x08\x00\x82\xc6'\
b'\xc6\xaa\xaa\xaa\x92\x92\x00\x00\x07\x00\x84\xc4\xa4\xa4\x94\x94'\
b'\x8c\x84\x00\x00\x08\x00\x38\x44\x82\x82\x82\x82\x44\x38\x00\x00'\
b'\x06\x00\xf0\x88\x88\x88\xf0\x80\x80\x80\x00\x00\x08\x00\x38\x44'\
b'\x82\x82\x82\x9a\x44\x3e\x00\x00\x07\x00\xf8\x84\x84\xf8\x90\x88'\
b'\x88\x84\x00\x00\x07\x00\x78\x84\x80\x60\x18\x04\x84\x78\x00\x00'\
b'\x06\x00\xf8\x20\x20\x20\x20\x20\x20\x20\x00\x00\x07\x00\x84\x84'\
b'\x84\x84\x84\x84\x84\x78\x00\x00\x08\x00\x82\x82\x44\x44\x28\x28'\
b'\x10\x10\x00\x00\x0b\x00\x84\x20\x8a\x20\x4a\x40\x4a\x40\x51\x40'\
b'\x51\x40\x20\x80\x20\x80\x00\x00\x00\x00\x07\x00\x84\x48\x48\x30'\
b'\x30\x48\x48\x84\x00\x00\x08\x00\x82\x44\x44\x28\x10\x10\x10\x10'\
b'\x00\x00\x07\x00\x7c\x08\x10\x10\x20\x20\x40\xfc\x00\x00\x03\x00'\
b'\xc0\x80\x80\x80\x80\x80\x80\x80\x80\xc0\x03\x00\x80\x80\x40\x40'\
b'\x40\x40\x20\x20\x00\x00\x03\x00\xc0\x40\x40\x40\x40\x40\x40\x40'\
b'\x40\xc0\x05\x00\x20\x50\x50\x88\x00\x00\x00\x00\x00\x00\x06\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x00\x04\x00\x80\x40\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x70\x88\x78\x88\x98\xe8'\
b'\x00\x00\x06\x00\x80\x80\xb0\xc8\x88\x88\xc8\xb0\x00\x00\x06\x00'\
b'\x00\x00\x70\x88\x80\x80\x88\x70\x00\x00\x06\x00\x08\x08\x68\x98'\
b'\x88\x88\x98\x68\x00\x00\x06\x00\x00\x00\x70\x88\xf8\x80\x88\x70'\
b'\x00\x00\x04\x00\x20\x40\xe0\x40\x40\x40\x40\x40\x00\x00\x06\x00'\
b'\x00\x00\x68\x98\x88\x88\x98\x68\x08\xf0\x06\x00\x80\x80\xb0\xc8'\
b'\x88\x88\x88\x88\x00\x00\x02\x00\x80\x00\x80\x80\x80\x80\x80\x80'\
b'\x00\x00\x02\x00\x40\x00\x40\x40\x40\x40\x40\x40\x40\x80\x05\x00'\
b'\x80\x80\x90\xa0\xc0\xe0\xa0\x90\x00\x00\x02\x00\x80\x80\x80\x80'\
b'\x80\x80\x80\x80\x00\x00\x08\x00\x00\x00\xbc\xd2\x92\x92\x92\x92'\
b'\x00\x00\x06\x00\x00\x00\xf0\x88\x88\x88\x88\x88\x00\x00\x06\x00'\
b'\x00\x00\x70\x88\x88\x88\x88\x70\x00\x00\x06\x00\x00\x00\xb0\xc8'\
b'\x88\x88\xc8\xb0\x80\x80\x06\x00\x00\x00\x68\x98\x88\x88\x98\x68'\
b'\x08\x08\x04\x00\x00\x00\xa0\xc0\x80\x80\x80\x80\x00\x00\x06\x00'\
b'\x00\x00\x70\x88\x60\x10\x88\x70\x00\x00\x03\x00\x40\x40\xe0\x40'\
b'\x40\x40\x40\x60\x00\x00\x06\x00\x00\x00\x88\x88\x88\x88\x98\x68'\
b'\x00\x00\x06\x00\x00\x00\x88\x88\x50\x50\x20\x20\x00\x00\x0a\x00'\
b'\x00\x00\x00\x00\x88\x80\x94\x80\x55\x00\x55\x00\x22\x00\x22\x00'\
b'\x00\x00\x00\x00\x06\x00\x00\x00\x88\x50\x20\x20\x50\x88\x00\x00'\
b'\x06\x00\x00\x00\x88\x88\x50\x50\x20\x20\x20\x40\x06\x00\x00\x00'\
b'\xf8\x10\x20\x20\x40\xf8\x00\x00\x04\x00\x20\x40\x40\x40\x80\x40'\
b'\x40\x40\x40\x20\x02\x00\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80'\
b'\x04\x00\x80\x40\x40\x40\x20\x40\x40\x40\x40\x80\x06\x00\x00\x00'\
b'\x00\xe8\xb0\x00\x00\x00\x00\x00'
_index =\
b'\x00\x00\x0c\x00\x0c\x00\x18\x00\x18\x00\x24\x00\x24\x00\x30\x00'\
b'\x30\x00\x3c\x00\x3c\x00\x48\x00\x48\x00\x5e\x00\x5e\x00\x6a\x00'\
b'\x6a\x00\x76\x00\x76\x00\x82\x00\x82\x00\x8e\x00\x8e\x00\x9a\x00'\
b'\x9a\x00\xa6\x00\xa6\x00\xb2\x00\xb2\x00\xbe\x00\xbe\x00\xca\x00'\
b'\xca\x00\xd6\x00\xd6\x00\xe2\x00\xe2\x00\xee\x00\xee\x00\xfa\x00'\
b'\xfa\x00\x06\x01\x06\x01\x12\x01\x12\x01\x1e\x01\x1e\x01\x2a\x01'\
b'\x2a\x01\x36\x01\x36\x01\x42\x01\x42\x01\x4e\x01\x4e\x01\x5a\x01'\
b'\x5a\x01\x66\x01\x66\x01\x72\x01\x72\x01\x7e\x01\x7e\x01\x8a\x01'\
b'\x8a\x01\x96\x01\x96\x01\xac\x01\xac\x01\xb8\x01\xb8\x01\xc4\x01'\
b'\xc4\x01\xd0\x01\xd0\x01\xdc\x01\xdc\x01\xe8\x01\xe8\x01\xf4\x01'\
b'\xf4\x01\x00\x02\x00\x02\x0c\x02\x0c\x02\x18\x02\x18\x02\x24\x02'\
b'\x24\x02\x30\x02\x30\x02\x3c\x02\x3c\x02\x48\x02\x48\x02\x54\x02'\
b'\x54\x02\x60\x02\x60\x02\x6c\x02\x6c\x02\x78\x02\x78\x02\x84\x02'\
b'\x84\x02\x90\x02\x90\x02\x9c\x02\x9c\x02\xa8\x02\xa8\x02\xb4\x02'\
b'\xb4\x02\xca\x02\xca\x02\xd6\x02\xd6\x02\xe2\x02\xe2\x02\xee\x02'\
b'\xee\x02\xfa\x02\xfa\x02\x06\x03\x06\x03\x12\x03\x12\x03\x1e\x03'\
b'\x1e\x03\x2a\x03\x2a\x03\x36\x03\x36\x03\x42\x03\x42\x03\x4e\x03'\
b'\x4e\x03\x5a\x03\x5a\x03\x66\x03\x66\x03\x72\x03\x72\x03\x7e\x03'\
b'\x7e\x03\x8a\x03\x8a\x03\x96\x03\x96\x03\xa2\x03\xa2\x03\xae\x03'\
b'\xae\x03\xba\x03\xba\x03\xc6\x03\xc6\x03\xd2\x03\xd2\x03\xde\x03'\
b'\xde\x03\xea\x03\xea\x03\xf6\x03\xf6\x03\x02\x04\x02\x04\x0e\x04'\
b'\x0e\x04\x1a\x04\x1a\x04\x26\x04\x26\x04\x32\x04\x32\x04\x3e\x04'\
b'\x3e\x04\x54\x04\x54\x04\x60\x04\x60\x04\x6c\x04\x6c\x04\x78\x04'\
b'\x78\x04\x84\x04\x84\x04\x90\x04\x90\x04\x9c\x04\x9c\x04\xa8\x04'\
_mvfont = memoryview(_font)
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
idx_offs = 4 * (ordch - 32)
offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little')
next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little')
width = int.from_bytes(_font[offset:offset + 2], 'little')
return _mvfont[offset + 2:next_offs], 10, width

191
color15.py 100644
Wyświetl plik

@ -0,0 +1,191 @@
# color15.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
# For wiring details see drivers/ADAFRUIT.md in this repo.
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
height = 96 # 1.27 inch 96*128 (rows*cols) display
# height = 128 # 1.5 inch 128*128 display
import machine
import gc
from ssd1351 import SSD1351 as SSD
# Initialise hardware and framebuf before importing modules
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)
gc.collect() # Precaution befor instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
import cmath
import utime
import uos
from writer import Writer, CWriter
from nanogui import Label, Meter, LED, Dial, Pointer, refresh
# Fonts
import arial10, freesans20
GREEN = SSD.rgb(0, 255, 0)
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
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
wri.set_clip(True, True, False)
def meter():
print('Meter test.')
refresh(ssd, True) # Clear any prior image
color = lambda v : RED if v > 0.7 else YELLOW if v > 0.5 else GREEN
txt = lambda v : 'ovr' if v > 0.7 else 'high' if v > 0.5 else 'ok'
m0 = Meter(wri, 5, 2, divisions = 4, ptcolor=YELLOW,
label='left', style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
l0 = LED(wri, ssd.height - 16 - wri.height, 2, bdcolor=YELLOW, label ='over')
m1 = Meter(wri, 5, 50, divisions = 4, ptcolor=YELLOW,
label='right', style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
l1 = LED(wri, ssd.height - 16 - wri.height, 50, bdcolor=YELLOW, label ='over')
m2 = Meter(wri, 5, 98, divisions = 4, ptcolor=YELLOW,
label='bass', style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
l2 = LED(wri, ssd.height - 16 - wri.height, 98, bdcolor=YELLOW, label ='over')
steps = 10
for n in range(steps):
v = int.from_bytes(uos.urandom(3),'little')/16777216
m0.value(v, color(v))
l0.color(color(v))
l0.text(txt(v), fgcolor=color(v))
v = n/steps
m1.value(v, color(v))
l1.color(color(v))
l1.text(txt(v), fgcolor=color(v))
v = 1 - n/steps
m2.value(v, color(v))
l2.color(color(v))
l2.text(txt(v), fgcolor=color(v))
refresh(ssd)
utime.sleep(1)
def multi_fields(t):
print('Dynamic labels.')
refresh(ssd, True) # Clear any prior image
nfields = []
dy = wri.height + 6
y = 2
col = 15
width = wri.stringlen('99.99')
for txt in ('X:', 'Y:', 'Z:'):
Label(wri, y, 0, txt) # Use wri default colors
nfields.append(Label(wri, y, col, width, bdcolor=None)) # Specify a border, color TBD
y += dy
end = utime.ticks_add(utime.ticks_ms(), t * 1000)
while utime.ticks_diff(end, utime.ticks_ms()) > 0:
for field in nfields:
value = int.from_bytes(uos.urandom(3),'little')/167772
overrange = None if value < 70 else YELLOW if value < 90 else RED
field.value('{:5.2f}'.format(value), fgcolor = overrange, bdcolor = overrange)
refresh(ssd)
utime.sleep(1)
Label(wri, 0, 64, ' OK ', True, fgcolor = RED)
refresh(ssd)
utime.sleep(1)
def vari_fields():
print('Variable label styles.')
refresh(ssd, True) # Clear any prior image
wri_large = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)
wri_large.set_clip(True, True, False)
Label(wri_large, 0, 0, 'Text')
Label(wri_large, 20, 0, 'Border')
width = wri_large.stringlen('Yellow')
lbl_text = Label(wri_large, 0, 65, width)
lbl_bord = Label(wri_large, 20, 65, width)
lbl_text.value('Red')
lbl_bord.value('Red')
lbl_var = Label(wri_large, 50, 2, '25.46', fgcolor=RED, bdcolor=RED)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Red')
lbl_bord.value('Yellow')
lbl_var.value(bdcolor=YELLOW)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Red')
lbl_bord.value('None')
lbl_var.value(bdcolor=False)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Yellow')
lbl_bord.value('None')
lbl_var.value(fgcolor=YELLOW)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Blue')
lbl_bord.value('Green')
lbl_var.value('18.99', fgcolor=BLUE, bdcolor=GREEN)
Label(wri, ssd.height - wri.height - 2, 0, 'Done', fgcolor=RED)
refresh(ssd)
def clock(x):
print('Clock test.')
refresh(ssd, True) # Clear any prior image
lbl = Label(wri, 5, 85, 'Clock')
dial = Dial(wri, 5, 5, height = 75, ticks = 12, bdcolor=None, label=50) # Border in fg color
hrs = Pointer(dial)
mins = Pointer(dial)
hrs.value(0 + 0.7j, RED)
mins.value(0 + 0.9j, YELLOW)
dm = cmath.rect(1, -cmath.pi/30) # Rotate by 1 minute (CW)
dh = cmath.rect(1, -cmath.pi/1800) # Rotate hours by 1 minute
for n in range(x):
refresh(ssd)
utime.sleep_ms(200)
mins.value(mins.value() * dm, YELLOW)
hrs.value(hrs.value() * dh, RED)
dial.text('ticks: {}'.format(n))
lbl.value('Done')
def compass(x):
print('Compass test.')
refresh(ssd, True) # Clear any prior image
dial = Dial(wri, 5, 5, height = 75, bdcolor=None, label=50, style = Dial.COMPASS)
bearing = Pointer(dial)
bearing.value(0 + 1j, RED)
dh = cmath.rect(1, -cmath.pi/30) # Rotate by 6 degrees CW
for n in range(x):
utime.sleep_ms(200)
bearing.value(bearing.value() * dh, RED)
refresh(ssd)
print('Color display test is running.')
clock(70)
compass(70)
meter()
multi_fields(t = 10)
vari_fields()
print('Test complete.')

133
color96.py 100644
Wyświetl plik

@ -0,0 +1,133 @@
# color96.py Test/demo program for ssd1331 Adafruit 0.96" OLED display
# https://www.adafruit.com/product/684
# For wiring details see drivers/ADAFRUIT.md in this repo.
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import machine
import gc
from ssd1331 import SSD1331 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)
gc.collect()
ssd = SSD(spi, pcs, pdc, prst) # Create a display instance
from nanogui import Label, Meter, LED, refresh
refresh(ssd)
# Fonts
import arial10
from writer import Writer, CWriter
import utime
import uos
GREEN = SSD.rgb(0, 255, 0)
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
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
wri.set_clip(True, True, False)
def meter():
print('meter')
refresh(ssd, True) # Clear any prior image
m = Meter(wri, 5, 2, height = 45, divisions = 4, ptcolor=YELLOW,
label='level', style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
l = LED(wri, 5, 40, bdcolor=YELLOW, label ='over')
steps = 10
for _ in range(steps):
v = int.from_bytes(uos.urandom(3),'little')/16777216
m.value(v)
l.color(GREEN if v < 0.5 else RED)
refresh(ssd)
utime.sleep(1)
refresh(ssd)
def multi_fields(t):
print('multi_fields')
refresh(ssd, True) # Clear any prior image
nfields = []
dy = wri.height + 6
y = 2
col = 15
width = wri.stringlen('99.99')
for txt in ('X:', 'Y:', 'Z:'):
Label(wri, y, 0, txt) # Use wri default colors
nfields.append(Label(wri, y, col, width, bdcolor=None)) # Specify a border, color TBD
y += dy
end = utime.ticks_add(utime.ticks_ms(), t * 1000)
while utime.ticks_diff(end, utime.ticks_ms()) > 0:
for field in nfields:
value = int.from_bytes(uos.urandom(3),'little')/167772
overrange = None if value < 70 else YELLOW if value < 90 else RED
field.value('{:5.2f}'.format(value), fgcolor = overrange, bdcolor = overrange)
refresh(ssd)
utime.sleep(1)
Label(wri, 0, 64, ' OK ', True, fgcolor = RED)
refresh(ssd)
utime.sleep(1)
def vari_fields():
print('vari_fields')
refresh(ssd, True) # Clear any prior image
Label(wri, 0, 0, 'Text:')
Label(wri, 20, 0, 'Border:')
width = wri.stringlen('Yellow')
lbl_text = Label(wri, 0, 40, width)
lbl_bord = Label(wri, 20, 40, width)
lbl_text.value('Red')
lbl_bord.value('Red')
lbl_var = Label(wri, 40, 2, '25.46', fgcolor=RED, bdcolor=RED)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Red')
lbl_bord.value('Yellow')
lbl_var.value(bdcolor=YELLOW)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Red')
lbl_bord.value('None')
lbl_var.value(bdcolor=False)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Yellow')
lbl_bord.value('None')
lbl_var.value(fgcolor=YELLOW)
refresh(ssd)
utime.sleep(2)
lbl_text.value('Blue')
lbl_bord.value('Green')
lbl_var.value('18.99', fgcolor=BLUE, bdcolor=GREEN)
refresh(ssd)
print('Color display test is running.')
meter()
multi_fields(t = 10)
vari_fields()
print('Test complete.')

308
courier20.py 100644
Wyświetl plik

@ -0,0 +1,308 @@
# Code generated by font-to-py.py.
# Font: Courier Prime.ttf
version = '0.2'
def height():
return 20
def max_width():
return 14
def hmap():
return True
def reverse():
return False
def monospaced():
return True
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x0e\x00\x00\x00\x00\x00\x7c\x00\xfe\x00\xc7\x00\xc3\x00\x03\x00'\
b'\x07\x00\x1e\x00\x18\x00\x18\x00\x18\x00\x3c\x00\x3c\x00\x18\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x60\x00\x60\x00\x60\x00'\
b'\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x00\x00\x60\x00\xf0\x00'\
b'\xf0\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\xe6\x00\xe6\x00\x66\x00\x66\x00\x66\x00\x66\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x03\x30\x02\x20'\
b'\x02\x20\x06\x60\x3f\xf8\x3f\xf8\x0c\xc0\x08\x80\x7f\xf0\xff\xf0'\
b'\x19\x80\x11\x00\x33\x00\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x0c\x00\x0c\x00\x3d\x80\x7f\x80\xcd\x80\xcc\x80'\
b'\xec\x00\x7f\x00\x0f\x80\x0c\xc0\xcc\xc0\xcd\xc0\xff\x80\xcf\x00'\
b'\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x38\x00\xfe\x18\xc6\x30\xc6\x60\xfe\xc0\x39\x80\x03\x00'\
b'\x06\x70\x1d\xfc\x39\x8c\x71\x8c\x61\xfc\x00\x70\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x1e\x00\x3f\x00'\
b'\x63\x00\x63\x00\x60\x00\x30\x00\x31\xc0\x49\xc0\xc7\x00\xc3\x00'\
b'\xe3\x00\x7f\xc0\x39\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00'\
b'\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x04\x00\x0e\x00'\
b'\x18\x00\x30\x00\x30\x00\x60\x00\x60\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\x60\x00\x60\x00\x30\x00\x38\x00\x1c\x00'\
b'\x0e\x00\x04\x00\x0e\x00\x40\x00\xe0\x00\x30\x00\x18\x00\x18\x00'\
b'\x0c\x00\x0c\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00'\
b'\x0c\x00\x0c\x00\x18\x00\x38\x00\x70\x00\xe0\x00\x80\x00\x0e\x00'\
b'\x00\x00\x00\x00\x0c\x00\x0c\x00\x0c\x00\xed\xc0\x7f\x80\x0c\x00'\
b'\x1e\x00\x33\x00\x23\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\xff\xc0\xff\xc0\x0c\x00\x0c\x00'\
b'\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78\x00\x70\x00'\
b'\x60\x00\x60\x00\xc0\x00\xc0\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\xff\xc0'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00'\
b'\xf0\x00\xf0\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\xc0\x01\x80\x01\x80\x03\x00\x03\x00\x02\x00\x06\x00'\
b'\x04\x00\x0c\x00\x0c\x00\x18\x00\x18\x00\x30\x00\x30\x00\x20\x00'\
b'\x60\x00\x40\x00\xc0\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x1e\x00\x3f\x00\x61\x80\xe1\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\
b'\xc0\xc0\xe1\xc0\x61\x80\x3f\x00\x1e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x0c\x00\x7c\x00\xec\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\x3e\x00\xff\x00\xc3\x80\xc1\x80\x01\x80\x01\x00'\
b'\x02\x00\x04\x00\x08\x00\x11\x80\x21\x80\xff\x80\xff\x80\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3e\x00'\
b'\x7f\x00\x61\x80\x61\x80\x01\x80\x1f\x00\x1f\x00\x03\x80\x01\x80'\
b'\x01\x80\xc3\x80\xff\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\x03\x00\x07\x00\x0b\x00\x1b\x00'\
b'\x13\x00\x23\x00\x63\x00\xff\xc0\xff\xe0\x03\x00\x03\x00\x0f\xc0'\
b'\x0f\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x7f\x80\x7f\x80\x60\x00\x60\x00\x7e\x00\x7f\x00\x63\x80'\
b'\x01\x80\x01\x80\x01\x80\xc3\x80\xff\x00\x3c\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x07\x80\x1f\x80'\
b'\x3c\x00\x70\x00\x60\x00\xcf\x00\xff\x80\xe1\xc0\xc0\xc0\xc0\xc0'\
b'\x61\xc0\x7f\x80\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\xff\x80\xff\x80\xc1\x80\xc1\x00\x03\x00'\
b'\x02\x00\x06\x00\x06\x00\x0c\x00\x0c\x00\x08\x00\x18\x00\x10\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x1e\x00\x3f\x00\x61\x80\x61\x80\x73\x80\x3f\x00\x7f\x00\xe3\x80'\
b'\xc1\x80\xc1\x80\xe3\x80\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3e\x00\x7f\x80\xe1\x80'\
b'\xc0\xc0\xc0\xc0\xe1\xc0\x7f\xc0\x3c\xc0\x01\x80\x03\x80\x0f\x00'\
b'\x7e\x00\x78\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\xf0\x00\xf0\x00'\
b'\x60\x00\x00\x00\x00\x00\x60\x00\xf0\x00\xf0\x00\x60\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x30\x00\x78\x00\x78\x00\x30\x00\x00\x00\x00\x00'\
b'\x00\x00\x78\x00\x70\x00\x60\x00\x60\x00\xc0\x00\xc0\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x60\x01\xe0\x07\x80'\
b'\x1e\x00\x78\x00\xe0\x00\x78\x00\x0e\x00\x03\x80\x00\xe0\x00\x40'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\xff\xc0\x00\x00\x00\x00'\
b'\x00\x00\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x80\x00'\
b'\xe0\x00\x38\x00\x0e\x00\x03\xc0\x00\xc0\x03\x80\x0e\x00\x38\x00'\
b'\xe0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x7c\x00\xfe\x00\xc7\x00\xc3\x00\x03\x00'\
b'\x07\x00\x1e\x00\x18\x00\x18\x00\x18\x00\x3c\x00\x3c\x00\x18\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x00\x00\x0f\x80\x1f\xc0\x38\xe0\x66\xf0\x6f\xb0\xcd\x30\xd9\x30'\
b'\xd9\x30\xdb\x70\xdf\xe0\x6c\xc0\x70\x00\x3f\xc0\x0f\x80\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3f\x00\x3f\x00\x07\x80'\
b'\x0c\x80\x0c\x80\x18\xc0\x18\x40\x3f\xe0\x3f\xe0\x30\x60\x60\x30'\
b'\xf8\xf8\xf8\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\xff\x00\xff\xc0\x30\xc0\x30\xc0\x30\xc0\x3f\x80'\
b'\x3f\xc0\x30\xe0\x30\x60\x30\x60\x30\xe0\xff\xc0\xff\x80\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x1f\x60'\
b'\x3f\xe0\x70\xe0\x60\x60\xc0\x60\xc0\x40\xc0\x00\xc0\x00\xc0\x00'\
b'\x60\x00\x70\x60\x3f\xe0\x1f\x80\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\xff\x00\xff\x80\x31\xc0\x30\xe0'\
b'\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\xe0\x31\xc0\xff\x80'\
b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\xff\xe0\xff\xe0\x30\x60\x33\x60\x33\x00\x3f\x00\x3f\x00'\
b'\x33\x00\x33\x00\x30\x60\x30\x60\xff\xe0\xff\xe0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xff\xe0\xff\xe0'\
b'\x30\x60\x33\x60\x33\x00\x3f\x00\x3f\x00\x33\x00\x33\x00\x30\x00'\
b'\x30\x00\xfe\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x1e\xc0\x3f\xc0\x71\xc0\x60\xc0\xc0\xc0'\
b'\xc0\x00\xc0\x00\xc7\xf0\xc7\xf0\x60\xc0\x70\xc0\x3f\xc0\x1f\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\xfd\xf8\xfd\xf8\x30\x60\x30\x60\x30\x60\x3f\xe0\x3f\xe0\x30\x60'\
b'\x30\x60\x30\x60\x30\x60\xfd\xf8\xfd\xf8\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xff\xc0\xff\xc0\x0c\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\x3f\xf8\x3f\xf8\x01\x80\x01\x80\x01\x80\x01\x80'\
b'\x81\x80\xc1\x80\xc1\x80\xc1\x80\xc3\x80\x7f\x00\x3e\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xfc\xf0'\
b'\xfc\xf0\x30\x40\x31\x80\x33\x00\x34\x00\x3f\x00\x31\x80\x30\x80'\
b'\x30\xc0\x30\x40\xfc\x70\xfc\x30\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\x7f\x00\xff\x00\x18\x00\x18\x00'\
b'\x18\x00\x18\x00\x18\x00\x18\x30\x18\x30\x18\x30\x18\x30\x7f\xf0'\
b'\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\xf0\xf0\xf0\xf0\x70\xe0\x79\xe0\x69\x60\x69\x60\x6f\x60'\
b'\x66\x60\x66\x60\x66\x60\x60\x60\xf9\xf0\xf9\xf0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xf0\xf8\xf8\xf8'\
b'\x3c\x30\x34\x30\x32\x30\x32\x30\x31\x30\x31\x30\x30\xb0\x30\x70'\
b'\x30\x70\x7c\x30\x7c\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x1f\x80\x3f\xc0\x70\xe0\x60\x60\xc0\x30'\
b'\xc0\x30\xc0\x30\xc0\x30\xc0\x30\x60\x60\x70\xe0\x3f\xc0\x1f\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x7f\xc0\xff\xe0\x18\x70\x18\x30\x18\x30\x18\x70\x1f\xe0\x1f\xc0'\
b'\x18\x00\x18\x00\x18\x00\x7f\x00\xff\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x1f\x80\x3f\xc0\x70\xe0'\
b'\x60\x60\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xc0\x30\x60\x60\x70\xe0'\
b'\x3f\xc0\x0f\x80\x18\x20\x3f\xe0\x3f\xc0\x20\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\xff\x80\xff\xc0\x30\xe0\x30\x60\x30\xe0\x3f\xc0'\
b'\x3f\x00\x31\x80\x30\xc0\x30\xc0\x30\x60\xfc\x78\xfc\x38\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3c\xc0'\
b'\x7f\xc0\xe1\xc0\xc0\xc0\xc0\x00\x70\x00\x1f\x00\x01\x80\x00\xc0'\
b'\xc0\xc0\xe1\xc0\xff\x80\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\xff\xc0\xff\xc0\xcc\xc0\xcc\xc0'\
b'\xcc\xc0\xcc\xc0\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x7f\x80'\
b'\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\xfc\xfc\xfc\xfc\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x38\x70\x1f\xe0\x0f\xc0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xfc\x7c\xfc\x7c'\
b'\x30\x30\x30\x30\x10\x20\x18\x60\x18\x40\x0c\xc0\x0c\xc0\x04\x80'\
b'\x07\x80\x07\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\xf8\x7c\xf8\x7c\x60\x18\x63\x18\x23\x10'\
b'\x23\x90\x37\x90\x37\xb0\x34\xb0\x3c\xf0\x1c\xe0\x18\x60\x18\x60'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\xf9\xf0\xf9\xf0\x30\xc0\x19\x80\x1f\x80\x0f\x00\x06\x00\x0f\x00'\
b'\x19\x80\x30\xc0\x60\x60\xf9\xf0\xf9\xf0\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xf9\xf0\xf9\xf0\x30\xc0'\
b'\x30\xc0\x19\x80\x0f\x00\x0f\x00\x06\x00\x06\x00\x06\x00\x06\x00'\
b'\x3f\xc0\x3f\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\xff\xc0\xff\xc0\xc1\x80\xc3\x00\x02\x00\x04\x00'\
b'\x0c\x00\x18\x00\x10\xc0\x20\xc0\x40\xc0\xff\xc0\xff\xc0\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xfc\x00\xfc\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfc\x00\xfc\x00'\
b'\x00\x00\x0e\x00\xc0\x00\x40\x00\x60\x00\x20\x00\x30\x00\x30\x00'\
b'\x18\x00\x18\x00\x0c\x00\x0c\x00\x04\x00\x06\x00\x02\x00\x03\x00'\
b'\x03\x00\x01\x80\x01\x80\x00\xc0\x00\x00\x00\x00\x0e\x00\xfc\x00'\
b'\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\xfc\x00\xfc\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x18\x00\x18\x00'\
b'\x3c\x00\x24\x00\x66\x00\xc6\x00\xc3\x00\x83\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\xff\xfc\xff\xfc\x00\x00\x00\x00\x00\x00\x0e\x00\xe0\x00\xf8\x00'\
b'\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x3e\x00\xff\x00\xc1\x80\x3f\x80\x7f\x80\xc1\x80\xc1\x80\xc3\x80'\
b'\xff\x80\x7c\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\xf0\x00\xf0\x00\x30\x00\x30\x00\x30\x00\x37\xc0\x3f\xe0\x38\x60'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x38\x60\xff\xe0\xf3\xc0\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x1f\x60\x7f\xe0\x70\xe0\xc0\x60\xc0\x00\xc0\x00'\
b'\xc0\x00\x70\x60\x7f\xc0\x1f\x80\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x03\xc0\x03\xc0\x00\xc0\x00\xc0\x00\xc0\x3e\xc0'\
b'\x7f\xc0\x61\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61\xc0\x7f\xf0'\
b'\x3c\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x7f\x80\x60\xc0\xff\xc0'\
b'\xff\xc0\xc0\x00\xc0\x00\x60\xc0\x7f\xc0\x1f\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x0f\x80\x1f\xc0\x38\x40\x30\x00'\
b'\x30\x00\xff\x80\xff\x80\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00'\
b'\x30\x00\xff\x80\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x78\x7f\xf8'\
b'\x60\xe0\xc0\x60\xc0\x60\xc0\x60\xc0\x60\x60\xe0\x7f\xe0\x1e\x60'\
b'\x00\x60\x20\xe0\x3f\xc0\x1f\x80\x00\x00\x0e\x00\xf0\x00\xf0\x00'\
b'\x30\x00\x30\x00\x30\x00\x37\xc0\x3f\xe0\x3c\x60\x38\x60\x30\x60'\
b'\x30\x60\x30\x60\x30\x60\xfd\xf8\xfd\xf8\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x0c\x00\x0c\x00\x0c\x00\x00\x00\x00\x00'\
b'\x7c\x00\x7c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x03\x00\x03\x00\x03\x00\x00\x00\x00\x00\x7f\x00\x7f\x00\x03\x00'\
b'\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00'\
b'\xc3\x00\xfe\x00\x3c\x00\x00\x00\x0e\x00\xf0\x00\xf0\x00\x30\x00'\
b'\x30\x00\x30\x00\x33\xe0\x33\xe0\x31\x80\x33\x00\x34\x00\x3a\x00'\
b'\x31\x80\x30\xc0\xfd\xf0\xfd\xf0\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x7c\x00\x7c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\xff\xc0'\
b'\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x70\xff\xf8\x3b\x98\x33\x18'\
b'\x33\x18\x33\x18\x33\x18\x33\x18\xfb\x9c\xfb\x9c\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\xf3\xc0\xf7\xe0\x38\x60\x38\x60\x30\x60\x30\x60\x30\x60'\
b'\x30\x60\xfd\xf8\xfd\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x80\x3f\xc0'\
b'\x70\xe0\xc0\x30\xc0\x30\xc0\x30\xc0\x30\x70\xe0\x3f\xc0\x1f\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\xf7\xc0\xff\xe0\x38\x60\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x38\x60\x3f\xe0\x37\xc0\x30\x00\x30\x00\xfc\x00'\
b'\xfc\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x3e\xf0\x7f\xf0\x61\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61\xc0'\
b'\x7f\xc0\x3c\xc0\x00\xc0\x00\xc0\x03\xf0\x03\xf0\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xc0\xf7\xe0\x3e\x40'\
b'\x38\x00\x30\x00\x30\x00\x30\x00\x30\x00\xff\x00\xff\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x7d\x80\xff\x80\xc1\x80\xc0\x80\x7c\x00\x03\x80'\
b'\xc0\xc0\xe1\xc0\xff\xc0\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x30\x00\x30\x00\x30\x00\x30\x00\xff\x80'\
b'\xff\x80\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\xc0\x1f\xc0'\
b'\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xe0\xf1\xe0\x30\x60\x30\x60'\
b'\x30\x60\x30\x60\x30\xe0\x30\xe0\x3f\xf8\x1e\x78\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\xf8\xf0\xf9\xf0\x20\x60\x30\xc0\x10\xc0\x18\x80\x09\x80'\
b'\x0d\x00\x0f\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x3c\xf8\x3c'\
b'\x63\x18\x63\x18\x27\x90\x37\xb0\x34\xb0\x1c\xe0\x1c\xe0\x18\x60'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\xf9\xf0\xf9\xf0\x30\xc0\x19\x80\x0f\x00'\
b'\x0f\x00\x19\x80\x30\xc0\xf9\xf0\xf9\xf0\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\xf8\xf0\xf9\xf0\x30\x60\x30\xc0\x18\xc0\x19\x80\x0d\x80\x07\x00'\
b'\x07\x00\x06\x00\x0c\x00\x1c\x00\x78\x00\x70\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\xff\x80\xc1\x00'\
b'\xc2\x00\x04\x00\x18\x00\x30\xc0\x60\xc0\xff\xc0\xff\xc0\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x06\x00\x0e\x00\x18\x00'\
b'\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x38\x00\xf0\x00\xf0\x00'\
b'\x38\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x1e\x00\x0e\x00'\
b'\x00\x00\x0e\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0e\x00\xc0\x00'\
b'\xe0\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x38\x00'\
b'\x1e\x00\x1e\x00\x38\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00'\
b'\xf0\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x78\x40\xff\xc0\x87\x80\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
_index =\
b'\x00\x00\x2a\x00\x54\x00\x7e\x00\xa8\x00\xd2\x00\xfc\x00\x26\x01'\
b'\x50\x01\x7a\x01\xa4\x01\xce\x01\xf8\x01\x22\x02\x4c\x02\x76\x02'\
b'\xa0\x02\xca\x02\xf4\x02\x1e\x03\x48\x03\x72\x03\x9c\x03\xc6\x03'\
b'\xf0\x03\x1a\x04\x44\x04\x6e\x04\x98\x04\xc2\x04\xec\x04\x16\x05'\
b'\x40\x05\x6a\x05\x94\x05\xbe\x05\xe8\x05\x12\x06\x3c\x06\x66\x06'\
b'\x90\x06\xba\x06\xe4\x06\x0e\x07\x38\x07\x62\x07\x8c\x07\xb6\x07'\
b'\xe0\x07\x0a\x08\x34\x08\x5e\x08\x88\x08\xb2\x08\xdc\x08\x06\x09'\
b'\x30\x09\x5a\x09\x84\x09\xae\x09\xd8\x09\x02\x0a\x2c\x0a\x56\x0a'\
b'\x80\x0a\xaa\x0a\xd4\x0a\xfe\x0a\x28\x0b\x52\x0b\x7c\x0b\xa6\x0b'\
b'\xd0\x0b\xfa\x0b\x24\x0c\x4e\x0c\x78\x0c\xa2\x0c\xcc\x0c\xf6\x0c'\
b'\x20\x0d\x4a\x0d\x74\x0d\x9e\x0d\xc8\x0d\xf2\x0d\x1c\x0e\x46\x0e'\
b'\x70\x0e\x9a\x0e\xc4\x0e\xee\x0e\x18\x0f\x42\x0f\x6c\x0f\x96\x0f'\
b'\xc0\x0f'
_mvfont = memoryview(_font)
def _chr_addr(ordch):
offset = 2 * (ordch - 32)
return int.from_bytes(_index[offset:offset + 2], 'little')
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
offset = _chr_addr(ordch)
width = int.from_bytes(_font[offset:offset + 2], 'little')
next_offs = _chr_addr(ordch +1)
return _mvfont[offset + 2:next_offs], 20, width

100
drivers/ADAFRUIT.md 100644
Wyświetl plik

@ -0,0 +1,100 @@
# Adafruit and other OLED displays
###### [Main README](../README.md)
# SPI Pin names and wiring
The names used on the Pyboard are the correct names for SPI signals. Some OLED
displays use different names. Adafruit use abbreviated names where space is at
a premium. The following table shows the correct names followed by others I
have seen. The column labelled "Adafruit" references pin numbers on the 1.27
and 1.5 inch displays. Pin numbering on the 0.96 inch display differs: pin
names are as below (SCK is CLK on this unit).
Pyboard pins are for SPI(1). Adapt for SPI(2) or other hardware.
| Pin | Pyboard | Display | Adafruit | Alternative names |
|:---:|:-------:|:-------:|:--------:|:---------:|
| 3V3 | 3V3 | | Vin (10) | |
| Gnd | Gnd | | Gnd (11) | |
| X1 | X1 | | DC (3) | |
| X2 | X2 | | CS (5) | OC OLEDCS |
| X3 | X3 | | Rst (4) | R RESET |
| X6 | SCK | SCK | CL (2) | SCK CLK |
| X8 | MOSI | MOSI | SI (1) | DATA SI |
| X7 | MISO | MISO | SO (7) | MISO (see below) |
| X21 | X21 | | SC (6) | SDCS (see below) |
The last two pins above are specific to Adafruit 1.27 and 1.5 inch displays and
only need to be connected if the SD card is to be used. The pin labelled CD on
those displays is a card detect signal; it can be ignored. The pin labelled 3Vo
is an output: these displays can be powered from +5V.
Pyboard pins are arbitrary with the exception of MOSI, SCK and MISO. These can
be changed if software SPI is used.
# I2C pin names and wiring
I2C is generally only available on monochrome displays. Monochrome OLED panels
typically use the SSD1306 chip which is
[officially supported](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
At the time of writing (Sept 2018) this works only with software SPI. See
[this issue](https://github.com/micropython/micropython/pull/4020). Wiring
details:
| Pin | Pyboard | Display |
|:---:|:-------:|:-------:|
| 3V3 | 3V3 | Vin |
| Gnd | Gnd | Gnd |
| Y9 | SCL | CLK |
| Y10 | SDA | DATA |
Typical initialisation on a Pyboard:
```python
pscl = machine.Pin('Y9', machine.Pin.OPEN_DRAIN)
psda = machine.Pin('Y10', machine.Pin.OPEN_DRAIN)
i2c = machine.I2C(scl=pscl, sda=psda)
```
# Adafruit - use of the onboard SD card
If the SD card is to be used, the official `scdard.py` driver should be
employed. This may be found
[here](https://github.com/micropython/micropython/tree/master/drivers/sdcard).
Note that `sdtest.py` initialises the SPI bus before accessing the SD card.
This is necessary because the display drivers use a high baudrate unsupported
by SD cards. Ensure applications do this before the first SD card access and
before subsequent ones if the display has been refreshed.
# Hardware note: SPI clock rate
For performance reasons the drivers for the Adafruit color displays run the SPI
bus at a high rate (currently 10.5MHz). Leads should be short and direct. An
attempt to use 21MHz failed. The datasheet limit is 20MHz. Whether a 5%
overclock caused this is moot: with very short leads or a PCB this might well
work. Note that the Pyboard hardware SPI supports only 10.5MHz and 21MHz.
In practice the 41ms update time is visually fast for most purposes except some
games.
# Notes on OLED displays
## Power consumption
The power consumption of OLED displays is roughly proportional to the number
and brightness of illuminated pixels. I tested a 1.27 inch Adafruit display
running the `clock.py` demo. It consumed 19.7mA. Initial current with screen
blank was 3.3mA.
## Wearout
OLED displays suffer gradual loss of luminosity over long periods of
illumination. Wikipedia refers to 15,000 hours for significant loss, which
equates to 1.7 years of 24/7 usage. However it also refers to fabrication
techniques which ameliorate this which implies the likelihood of better
figures. I have not seen figures for the Adafruit displays.
Options are to blank the display when not required, or to design screens where
the elements are occasionally moved slightly to preserve individual pixels.
###### [Main README](../README.md)

Wyświetl plik

@ -0,0 +1,92 @@
# SSD1331.py MicroPython driver for Adafruit 0.96" OLED display
# https://www.adafruit.com/product/684
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Show command
# 0x15, 0, 0x5f, 0x75, 0, 0x3f Col 0-95 row 0-63
# Initialisation command
# 0xae display off (sleep mode)
# 0xa0, 0x32 256 color RGB, horizontal RAM increment
# 0xa1, 0x00 Startline row 0
# 0xa2, 0x00 Vertical offset 0
# 0xa4 Normal display
# 0xa8, 0x3f Set multiplex ratio
# 0xad, 0x8e Ext supply
# 0xb0, 0x0b Disable power save mode
# 0xb1, 0x31 Phase period
# 0xb3, 0xf0 Oscillator frequency
# 0x8a, 0x64, 0x8b, 0x78, 0x8c, 0x64, # Precharge
# 0xbb, 0x3a Precharge voltge
# 0xbe, 0x3e COM deselect level
# 0x87, 0x06 master current attenuation factor
# 0x81, 0x91 contrast for all color "A" segment
# 0x82, 0x50 contrast for all color "B" segment
# 0x83, 0x7d contrast for all color "C" segment
# 0xaf Display on
import framebuf
import utime
import gc
class SSD1331(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# acceptable to hardware: rrrgggbb
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
def __init__(self, spi, pincs, pindc, pinrs, height=64, width=96):
self.spi = spi
self.rate = 6660000 # Data sheet: 150ns min clock period
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
# Save color mode for use by writer_gui (blit)
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
self.buffer = bytearray(self.height * self.width)
super().__init__(self.buffer, self.width, self.height, self.mode)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
self._write(b'\xae\xa0\x32\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\
b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\
b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0)
gc.collect()
self.show()
def _write(self, buf, dc):
self.spi.init(baudrate=self.rate, polarity=1, phase=1)
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(buf)
self.pincs(1)
def show(self, _cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate
self._write(_cmd, 0)
self._write(self.buffer, 1)

Wyświetl plik

@ -0,0 +1,20 @@
# Drivers for SSD1351
There are two versions.
* `ssd1351.py` This is optimised for STM (e.g. Pyboard) platforms.
* `ssd1351_generic.py` Cross-platform version.
The cross-platform version includes the `micropythn.viper` decorator. If your
platform does not support this, comment it out and remove the type annotations.
You may be able to use the native decorator.
If the platform supports the viper emitter performance should still be good: on
a Pyboard V1 this driver perorms a refresh of a 128*128 color display in 47ms.
The STM version is faster but not by a large margin: a refresh takes 41ms. 32ms
of these figures is consumed by the data transfer over the SPI interface.
If the viper and native decorators are unsupported a screen redraw takes 272ms
(on Pyboard 1.0) which is visibly slow.
This driver was tested on official Adafruit 1.5 and 1.27 inch displays, also a
Chinese 1.5 inch unit.

Wyświetl plik

@ -0,0 +1,160 @@
# SSD1351.py MicroPython driver for Adafruit color OLED displays.
# STM (Pyboard etc) version. Display refresh takes 41ms on Pyboard V1.0
# 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.
# This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import framebuf
import utime
import gc
import micropython
from uctypes import addressof
# Timings with standard emitter
# 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms
# Buffer transfer time = 272-240 = 32ms which accords with expected:
# 128*128*2/10500000 = 31.2ms (2 bytes/pixel, baudrate = 10.5MHz)
# With assembler .show() takes 41ms
# Copy a buffer with 8 bit rrrgggbb pixels to a buffer of 16 bit pixels.
@micropython.asm_thumb
def _lcopy(r0, r1, r2): # r0 dest, r1 source, r2 no. of bytes
label(LOOP)
ldrb(r3, [r1, 0]) # Get source byte to r3, r5, r6
mov(r5, r3)
mov(r6, r3)
mov(r4, 3)
and_(r3, r4)
mov(r4, 6)
lsl(r3, r4)
mov(r4, 0x1c)
and_(r5, r4)
mov(r4, 2)
lsr(r5, r4)
orr(r3, r5)
strb(r3, [r0, 0])
mov(r4, 0xe0)
and_(r6, r4)
mov(r4, 2)
lsr(r6, r4)
strb(r6, [r0, 1])
add(r0, 2)
add(r1, 1)
sub(r2, 1)
bne(LOOP)
# Initialisation commands in cmd_init:
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
# 0xae, # display off (sleep mode)
# 0xb3, 0xf1, # clock div
# 0xca, 0x7f, # mux ratio
# 0xa0, 0x74, # setremap 0x74
# 0x15, 0, 0x7f, # setcolumn
# 0x75, 0, 0x7f, # setrow
# 0xa1, 0, # set display start line
# 0xa2, 0, # displayoffset
# 0xb5, 0, # setgpio
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
# 0xb1, 0x32, # Precharge
# 0xbe, 0x05, # vcommh
# 0xa6, # normaldisplay
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
# 0xc7, 0x0f, # Master contrast
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
# 0xb6, 1, # Precharge 2
# 0xaf, # Display on
# SPI baudrate: Pyboard can produce 10.5MHz or 21MHz. Datasheet gives max of 20MHz.
# Attempt to use 21MHz failed but might work on a PCB or with very short leads.
class SSD1351(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# acceptable to hardware: rrrgggbb
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128):
if height not in (96, 128):
raise ValueError('Unsupported height {}'.format(height))
self.spi = spi
self.rate = 11000000 # See baudrate note above.
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
# Save color mode for use by writer_gui (blit)
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
self.buffer = bytearray(self.height * self.width)
super().__init__(self.buffer, self.width, self.height, self.mode)
self.linebuf = bytearray(self.width * 2)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
# See above comment to explain this allocation-saving gibberish.
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
self.show()
gc.collect()
def _write(self, buf, dc):
self.spi.init(baudrate=self.rate, polarity=1, phase=1)
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(buf)
self.pincs(1)
# Write lines from the framebuf out of order to match the mapping of the
# SSD1351 RAM to the OLED device.
def show(self):
lb = self.linebuf
buf = self.buffer
self._write(b'\x5c', 0) # Enable data write
if self.height == 128:
for l in range(128):
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126 .. 96
start = l0 * self.width
_lcopy(lb, addressof(buf) + start, self.width)
self._write(lb, 1) # Send a line
else:
for l in range(128):
if l < 64:
start = (63 -l) * self.width # 63 62 .. 1 0
_lcopy(lb, addressof(buf) + start, self.width)
self._write(lb, 1) # Send a line
elif l < 96: # This is daft but I can't get setrow to work
self._write(lb, 1) # Let RAM counter increase
else:
start = (191 - l) * self.width # 127 126 .. 95
_lcopy(lb, addressof(buf) + start, self.width)
self._write(lb, 1) # Send a line

Wyświetl plik

@ -0,0 +1,141 @@
# SSD1351_generic.py MicroPython driver for Adafruit color OLED displays.
# This is cross-platform. It lacks STM optimisations and is slower than the
# standard version.
# 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.
# This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import framebuf
import utime
import gc
import micropython
from uctypes import addressof
# Timings with standard emitter
# 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms
# Buffer transfer time = 272-240 = 32ms which accords with expected:
# 128*128*2/10500000 = 31.2ms (2 bytes/pixel, baudrate = 10.5MHz)
# With viper emitter show() takes 47ms vs 41ms for assembler.
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, length:int):
n = 0
for x in range(length):
c = source[x]
dest[n] = ((c & 3) << 6) | ((c & 0x1c) >> 2) # Blue green
n += 1
dest[n] = (c & 0xe0) >> 3 # Red
n += 1
# Initialisation commands in cmd_init:
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
# 0xae, # display off (sleep mode)
# 0xb3, 0xf1, # clock div
# 0xca, 0x7f, # mux ratio
# 0xa0, 0x74, # setremap 0x74
# 0x15, 0, 0x7f, # setcolumn
# 0x75, 0, 0x7f, # setrow
# 0xa1, 0, # set display start line
# 0xa2, 0, # displayoffset
# 0xb5, 0, # setgpio
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
# 0xb1, 0x32, # Precharge
# 0xbe, 0x05, # vcommh
# 0xa6, # normaldisplay
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
# 0xc7, 0x0f, # Master contrast
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
# 0xb6, 1, # Precharge 2
# 0xaf, # Display on
class SSD1351(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# acceptable to hardware: rrrgggbb
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128):
if height not in (96, 128):
raise ValueError('Unsupported height {}'.format(height))
self.spi = spi
self.rate = 20000000 # Data sheet: should support 20MHz
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
# Save color mode for use by writer_gui (blit)
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
self.buffer = bytearray(self.height * self.width)
super().__init__(self.buffer, self.width, self.height, self.mode)
self.linebuf = bytearray(self.width * 2)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
# See above comment to explain this allocation-saving gibberish.
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
gc.collect()
self.show()
def _write(self, buf, dc):
self.spi.init(baudrate=self.rate, polarity=1, phase=1)
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(buf)
self.pincs(1)
# Write lines from the framebuf out of order to match the mapping of the
# SSD1351 RAM to the OLED device.
def show(self):
lb = self.linebuf
buf = memoryview(self.buffer)
self._write(b'\x5c', 0) # Enable data write
if self.height == 128:
for l in range(128):
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126...
start = l0 * self.width
_lcopy(lb, buf[start : start + self.width], self.width)
self._write(lb, 1) # Send a line
else:
for l in range(128):
if l < 64:
start = (63 -l) * self.width
_lcopy(lb, buf[start : start + self.width], self.width)
self._write(lb, 1) # Send a line
elif l < 96: # This is daft but I can't get setrow to work
self._write(lb, 1) # Let RAM counter increase
else:
start = (191 - l) * self.width
_lcopy(lb, buf[start : start + self.width], self.width)
self._write(lb, 1) # Send a line

Wyświetl plik

@ -0,0 +1,18 @@
# test128_row.py Test for device driver on 96 row display
import machine
from ssd1351 import SSD1351 as SSD
# Initialise hardware
def setup():
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)
ssd = SSD(spi, pcs, pdc, prst) # Create a display instance
return ssd
ssd = setup()
ssd.fill(0)
ssd.line(0, 0, 127, 127, ssd.rgb(0, 255, 0))
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0))
ssd.show()

Wyświetl plik

@ -0,0 +1,18 @@
# test96.py Test for device driver on 96 row display
import machine
from ssd1351 import SSD1351 as SSD
# Initialise hardware
def setup():
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)
ssd = SSD(spi, pcs, pdc, prst, height=96) # Create a display instance
return ssd
ssd = setup()
ssd.fill(0)
ssd.line(0, 0, 127, 95, ssd.rgb(0, 255, 0))
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0))
ssd.show()

176
font6.py 100644
Wyświetl plik

@ -0,0 +1,176 @@
# Code generated by font-to-py.py.
# Font: FreeSans.ttf
version = '0.2'
def height():
return 14
def max_width():
return 14
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x08\x00\x00\x78\x8c\x84\x04\x18\x30\x20\x20\x00\x20\x00\x00\x00'\
b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x05\x00\x00\x80\x80\x80\x80\x80\x80\x80\x80\x00\x80\x00\x00\x00'\
b'\x05\x00\x00\xa0\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x08\x00\x00\x00\x12\x14\x7f\x24\x24\xfe\x28\x48\x48\x00\x00\x00'\
b'\x08\x00\x20\x78\xac\xa4\xa0\xa0\x78\x2c\xa4\xac\x78\x20\x00\x00'\
b'\x0c\x00\x00\x00\x70\x80\x89\x00\x89\x00\x8a\x00\x72\x00\x04\xe0'\
b'\x05\x10\x09\x10\x09\x10\x10\xe0\x00\x00\x00\x00\x00\x00\x09\x00'\
b'\x00\x00\x30\x00\x48\x00\x48\x00\x78\x00\x20\x00\x52\x00\x9e\x00'\
b'\x8c\x00\x8e\x00\x73\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x80'\
b'\x80\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x20'\
b'\x40\x40\x80\x80\x80\x80\x80\x80\x80\x40\x40\x20\x05\x00\x00\x80'\
b'\x40\x40\x20\x20\x20\x20\x20\x20\x20\x40\x40\x80\x05\x00\x00\x20'\
b'\xf8\x20\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00'\
b'\x00\x00\x00\x20\x20\xf8\x20\x20\x20\x00\x00\x00\x04\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x80\x80\x80\x00\x05\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x04\x00\x00\x10'\
b'\x10\x20\x20\x20\x40\x40\x40\x80\x80\x00\x00\x00\x08\x00\x00\x78'\
b'\x48\x84\x84\x84\x84\x84\x84\x48\x78\x00\x00\x00\x08\x00\x00\x20'\
b'\x60\xe0\x20\x20\x20\x20\x20\x20\x20\x00\x00\x00\x08\x00\x00\x78'\
b'\xcc\x84\x04\x0c\x18\x60\x40\x80\xfc\x00\x00\x00\x08\x00\x00\x78'\
b'\xc4\x84\x04\x38\x04\x04\x84\xcc\x78\x00\x00\x00\x08\x00\x00\x08'\
b'\x18\x38\x28\x48\x88\xfc\x08\x08\x08\x00\x00\x00\x08\x00\x00\x7c'\
b'\x80\x80\xb8\xcc\x04\x04\x04\x88\x78\x00\x00\x00\x08\x00\x00\x38'\
b'\x48\x84\x80\xf8\xcc\x84\x84\x4c\x78\x00\x00\x00\x08\x00\x00\xfc'\
b'\x0c\x08\x10\x10\x20\x20\x20\x40\x40\x00\x00\x00\x08\x00\x00\x78'\
b'\x84\x84\x84\x78\xcc\x84\x84\xcc\x78\x00\x00\x00\x08\x00\x00\x78'\
b'\xc8\x84\x84\xcc\x74\x04\x04\x88\x70\x00\x00\x00\x04\x00\x00\x00'\
b'\x00\x80\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x04\x00\x00\x00'\
b'\x00\x00\x80\x00\x00\x00\x00\x00\x80\x80\x80\x00\x08\x00\x00\x00'\
b'\x00\x00\x00\x1c\x70\x80\x60\x1c\x04\x00\x00\x00\x08\x00\x00\x00'\
b'\x00\x00\x00\x00\xfc\x00\xfc\x00\x00\x00\x00\x00\x08\x00\x00\x00'\
b'\x00\x00\x00\xe0\x38\x06\x1c\x60\x80\x00\x00\x00\x08\x00\x00\x78'\
b'\x8c\x84\x04\x18\x30\x20\x20\x00\x20\x00\x00\x00\x0e\x00\x00\x00'\
b'\x07\xc0\x18\x60\x20\x10\x43\x48\x84\xc8\x88\xc8\x88\x88\x89\x90'\
b'\xc6\xe0\x60\x00\x30\x00\x0f\xc0\x00\x00\x09\x00\x00\x00\x0c\x00'\
b'\x1c\x00\x14\x00\x16\x00\x32\x00\x22\x00\x7f\x00\x41\x00\x41\x80'\
b'\xc1\x80\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\xfc\x00\x82\x00'\
b'\x82\x00\x82\x00\xfc\x00\x86\x00\x82\x00\x82\x00\x86\x00\xfc\x00'\
b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x3c\x00\x42\x00\x41\x00'\
b'\x80\x00\x80\x00\x80\x00\x81\x00\xc1\x00\x62\x00\x3c\x00\x00\x00'\
b'\x00\x00\x00\x00\x0a\x00\x00\x00\xfc\x00\x82\x00\x83\x00\x81\x00'\
b'\x81\x00\x81\x00\x81\x00\x83\x00\x82\x00\xfc\x00\x00\x00\x00\x00'\
b'\x00\x00\x09\x00\x00\x00\xfe\x00\x80\x00\x80\x00\x80\x00\xfc\x00'\
b'\x80\x00\x80\x00\x80\x00\x80\x00\xfe\x00\x00\x00\x00\x00\x00\x00'\
b'\x08\x00\x00\xfc\x80\x80\x80\xfc\x80\x80\x80\x80\x80\x00\x00\x00'\
b'\x0b\x00\x00\x00\x1e\x00\x61\x00\x40\x80\x80\x00\x80\x00\x87\x80'\
b'\x80\x80\xc0\x80\x61\x80\x3e\x80\x00\x00\x00\x00\x00\x00\x0a\x00'\
b'\x00\x00\x81\x00\x81\x00\x81\x00\x81\x00\xff\x00\x81\x00\x81\x00'\
b'\x81\x00\x81\x00\x81\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x80'\
b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00\x00\x00\x07\x00\x00\x04'\
b'\x04\x04\x04\x04\x04\x04\x84\x84\x78\x00\x00\x00\x09\x00\x00\x00'\
b'\x82\x00\x84\x00\x88\x00\x90\x00\xb0\x00\xd8\x00\x88\x00\x84\x00'\
b'\x86\x00\x82\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x80\x80\x80'\
b'\x80\x80\x80\x80\x80\x80\xfc\x00\x00\x00\x0c\x00\x00\x00\xc1\x80'\
b'\xc1\x80\xc1\x80\xa2\x80\xa2\x80\xa2\x80\x94\x80\x94\x80\x94\x80'\
b'\x88\x80\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\xc1\x00\xc1\x00'\
b'\xe1\x00\xb1\x00\x91\x00\x89\x00\x8d\x00\x87\x00\x83\x00\x83\x00'\
b'\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x3e\x00\x63\x00\xc1\x00'\
b'\x80\x80\x80\x80\x80\x80\x80\x80\xc1\x00\x63\x00\x3e\x00\x00\x00'\
b'\x00\x00\x00\x00\x09\x00\x00\x00\xfc\x00\x86\x00\x82\x00\x82\x00'\
b'\x86\x00\xfc\x00\x80\x00\x80\x00\x80\x00\x80\x00\x00\x00\x00\x00'\
b'\x00\x00\x0b\x00\x00\x00\x3e\x00\x63\x00\xc1\x00\x80\x80\x80\x80'\
b'\x80\x80\x80\x80\xc5\x80\x63\x00\x3f\x00\x00\x80\x00\x00\x00\x00'\
b'\x0a\x00\x00\x00\xfc\x00\x82\x00\x82\x00\x82\x00\x82\x00\xfc\x00'\
b'\x82\x00\x82\x00\x82\x00\x83\x00\x00\x00\x00\x00\x00\x00\x09\x00'\
b'\x00\x00\x7c\x00\xc6\x00\x82\x00\xc0\x00\x78\x00\x0e\x00\x02\x00'\
b'\x82\x00\xc6\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00'\
b'\xfe\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\
b'\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x81\x00'\
b'\x81\x00\x81\x00\x81\x00\x81\x00\x81\x00\x81\x00\x81\x00\xc3\x00'\
b'\x3c\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\xc1\x80\x41\x00'\
b'\x41\x00\x63\x00\x22\x00\x32\x00\x16\x00\x14\x00\x1c\x00\x08\x00'\
b'\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xc2\x18\x45\x18\x45\x10'\
b'\x65\x10\x65\xb0\x28\xa0\x28\xa0\x38\xa0\x38\xe0\x10\x40\x00\x00'\
b'\x00\x00\x00\x00\x09\x00\x00\x00\x41\x00\x63\x00\x32\x00\x14\x00'\
b'\x0c\x00\x1c\x00\x16\x00\x22\x00\x63\x00\x41\x80\x00\x00\x00\x00'\
b'\x00\x00\x09\x00\x00\x00\xc1\x80\x63\x00\x22\x00\x36\x00\x14\x00'\
b'\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x00\x00\x00\x00\x00\x00'\
b'\x09\x00\x00\x00\x7f\x00\x03\x00\x06\x00\x04\x00\x0c\x00\x18\x00'\
b'\x30\x00\x20\x00\x40\x00\xff\x00\x00\x00\x00\x00\x00\x00\x04\x00'\
b'\x00\xc0\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\xc0\x04\x00'\
b'\x00\x80\x80\x40\x40\x40\x20\x20\x20\x10\x10\x00\x00\x00\x04\x00'\
b'\x00\xc0\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\xc0\x07\x00'\
b'\x00\x20\x60\x50\x90\x88\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x04\x00'\
b'\x00\x40\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00'\
b'\x00\x00\x00\x78\x84\x04\x04\x7c\x84\x8c\x76\x00\x00\x00\x08\x00'\
b'\x00\x80\x80\xb8\xcc\x84\x84\x84\x84\xc8\xb8\x00\x00\x00\x07\x00'\
b'\x00\x00\x00\x78\x44\x80\x80\x80\x80\x44\x78\x00\x00\x00\x08\x00'\
b'\x00\x02\x02\x3a\x46\x82\x82\x82\x82\x46\x3a\x00\x00\x00\x07\x00'\
b'\x00\x00\x00\x3c\x44\x82\xfe\x80\x80\x46\x3c\x00\x00\x00\x04\x00'\
b'\x00\x60\x40\xe0\x40\x40\x40\x40\x40\x40\x40\x00\x00\x00\x08\x00'\
b'\x00\x00\x00\x3a\x46\x82\x82\x82\x82\x46\x7a\x02\x84\x7c\x08\x00'\
b'\x00\x80\x80\xb0\xc8\x88\x88\x88\x88\x88\x88\x00\x00\x00\x03\x00'\
b'\x00\x80\x00\x80\x80\x80\x80\x80\x80\x80\x80\x00\x00\x00\x03\x00'\
b'\x00\x40\x00\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\xc0\x07\x00'\
b'\x00\x80\x80\x88\x90\xa0\xe0\x90\x98\x88\x8c\x00\x00\x00\x03\x00'\
b'\x00\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00\x00\x00\x0b\x00'\
b'\x00\x00\x00\x00\x00\x00\xb7\x00\xcc\x80\x88\x80\x88\x80\x88\x80'\
b'\x88\x80\x88\x80\x88\x80\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00'\
b'\x00\xb8\xc4\x84\x84\x84\x84\x84\x84\x00\x00\x00\x07\x00\x00\x00'\
b'\x00\x38\x44\x82\x82\x82\x82\x44\x38\x00\x00\x00\x08\x00\x00\x00'\
b'\x00\xb8\xc8\x84\x84\x84\x84\xc8\xb8\x80\x80\x00\x08\x00\x00\x00'\
b'\x00\x3a\x46\x82\x82\x82\x82\x46\x7a\x02\x02\x00\x05\x00\x00\x00'\
b'\x00\xa0\xc0\x80\x80\x80\x80\x80\x80\x00\x00\x00\x07\x00\x00\x00'\
b'\x00\x70\x88\x80\xc0\x70\x08\x88\x70\x00\x00\x00\x04\x00\x00\x00'\
b'\x40\xe0\x40\x40\x40\x40\x40\x40\x60\x00\x00\x00\x08\x00\x00\x00'\
b'\x00\x84\x84\x84\x84\x84\x84\x8c\x74\x00\x00\x00\x07\x00\x00\x00'\
b'\x00\xc6\x44\x44\x6c\x28\x28\x38\x10\x00\x00\x00\x0a\x00\x00\x00'\
b'\x00\x00\x00\x00\x8c\x40\xcc\xc0\x4c\x80\x5c\x80\x52\x80\x73\x80'\
b'\x33\x00\x33\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x44'\
b'\x68\x28\x30\x30\x28\x4c\xc4\x00\x00\x00\x07\x00\x00\x00\x00\xc6'\
b'\x44\x44\x6c\x28\x28\x30\x10\x10\x20\x60\x07\x00\x00\x00\x00\x7c'\
b'\x0c\x08\x10\x30\x60\x40\xfc\x00\x00\x00\x05\x00\x00\x60\x40\x40'\
b'\x40\x40\x40\x80\x40\x40\x40\x40\x40\x60\x04\x00\x00\x80\x80\x80'\
b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x05\x00\x00\xc0\x40\x40'\
b'\x40\x40\x40\x20\x40\x40\x40\x40\x40\xc0\x07\x00\x00\x00\x00\x00'\
b'\x00\x62\x9e\x00\x00\x00\x00\x00\x00\x00'
_index =\
b'\x00\x00\x10\x00\x20\x00\x30\x00\x40\x00\x50\x00\x60\x00\x7e\x00'\
b'\x9c\x00\xac\x00\xbc\x00\xcc\x00\xdc\x00\xec\x00\xfc\x00\x0c\x01'\
b'\x1c\x01\x2c\x01\x3c\x01\x4c\x01\x5c\x01\x6c\x01\x7c\x01\x8c\x01'\
b'\x9c\x01\xac\x01\xbc\x01\xcc\x01\xdc\x01\xec\x01\xfc\x01\x0c\x02'\
b'\x1c\x02\x2c\x02\x4a\x02\x68\x02\x86\x02\xa4\x02\xc2\x02\xe0\x02'\
b'\xf0\x02\x0e\x03\x2c\x03\x3c\x03\x4c\x03\x6a\x03\x7a\x03\x98\x03'\
b'\xb6\x03\xd4\x03\xf2\x03\x10\x04\x2e\x04\x4c\x04\x6a\x04\x88\x04'\
b'\xa6\x04\xc4\x04\xe2\x04\x00\x05\x1e\x05\x2e\x05\x3e\x05\x4e\x05'\
b'\x5e\x05\x6e\x05\x7e\x05\x8e\x05\x9e\x05\xae\x05\xbe\x05\xce\x05'\
b'\xde\x05\xee\x05\xfe\x05\x0e\x06\x1e\x06\x2e\x06\x3e\x06\x5c\x06'\
b'\x6c\x06\x7c\x06\x8c\x06\x9c\x06\xac\x06\xbc\x06\xcc\x06\xdc\x06'\
b'\xec\x06\x0a\x07\x1a\x07\x2a\x07\x3a\x07\x4a\x07\x5a\x07\x6a\x07'\
b'\x7a\x07'
_mvfont = memoryview(_font)
def _chr_addr(ordch):
offset = 2 * (ordch - 32)
return int.from_bytes(_index[offset:offset + 2], 'little')
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
offset = _chr_addr(ordch)
width = int.from_bytes(_font[offset:offset + 2], 'little')
next_offs = _chr_addr(ordch +1)
return _mvfont[offset + 2:next_offs], 14, width

288
freesans20.py 100644
Wyświetl plik

@ -0,0 +1,288 @@
# Code generated by font-to-py.py.
# Font: FreeSans.ttf
version = '0.25'
def height():
return 20
def max_width():
return 20
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x0b\x00\x00\x00\x3c\x00\x7e\x00\xc7\x00\xc3\x00\x03\x00\x03\x00'\
b'\x06\x00\x0c\x00\x08\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00'\
b'\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x07\x00\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00'\
b'\xc0\xc0\x00\x00\x00\x00\x07\x00\x00\x00\xd8\xd8\xd8\xd8\x90\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x0c\xc0\x08\x80\x08\x80\x7f\xe0\x7f\xe0\x19\x80\x11\x00'\
b'\x11\x00\xff\xc0\xff\xc0\x33\x00\x33\x00\x22\x00\x22\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0b\x00\x08\x00\x3e\x00\x7f\x80\xe9\xc0'\
b'\xc8\xc0\xc8\xc0\xc8\x00\xe8\x00\x7c\x00\x1f\x80\x09\xc0\x08\xc0'\
b'\xc8\xc0\xe9\xc0\x7f\x80\x3e\x00\x08\x00\x00\x00\x00\x00\x00\x00'\
b'\x12\x00\x00\x00\x00\x00\x00\x00\x38\x10\x00\x7c\x10\x00\xc6\x20'\
b'\x00\xc6\x20\x00\xc6\x40\x00\x7c\xc0\x00\x38\x80\x00\x01\x1e\x00'\
b'\x01\x3f\x00\x02\x73\x80\x02\x61\x80\x04\x73\x80\x04\x3f\x00\x08'\
b'\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00'\
b'\x00\x00\x00\x00\x0e\x00\x1f\x00\x31\x80\x31\x80\x31\x80\x1f\x00'\
b'\x1c\x00\x76\x60\xe3\x60\xc1\xc0\xc0\xc0\xe1\xc0\x7f\x60\x3e\x30'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\xc0\xc0\xc0\xc0'\
b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00'\
b'\x00\x10\x10\x20\x20\x60\x40\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x40\x60'\
b'\x20\x30\x10\x18\x07\x00\x00\x40\x40\x20\x20\x30\x10\x18\x18\x18'\
b'\x18\x18\x18\x18\x10\x30\x20\x60\x40\xc0\x08\x00\x00\x20\x20\xf8'\
b'\x20\x50\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x18\x00\x18\x00\x18\x00\xff\x00\xff\x00\x18\x00\x18\x00\x18\x00'\
b'\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xc0\x40\x40\x80\x00'\
b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\xf8\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\xc0\xc0\x00\x00\x00\x00\x06\x00\x00\x04'\
b'\x0c\x08\x08\x18\x10\x10\x30\x20\x20\x60\x40\x40\xc0\x80\x00\x00'\
b'\x00\x00\x0b\x00\x00\x00\x00\x00\x3e\x00\x7f\x00\x63\x00\xe3\x80'\
b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x80\x63\x00'\
b'\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x10\x00\x30\x00\xf0\x00\xf0\x00\x30\x00\x30\x00\x30\x00'\
b'\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x3e\x00\x7f\x00'\
b'\xe3\x80\xc1\x80\x01\x80\x01\x80\x03\x00\x0e\x00\x1c\x00\x30\x00'\
b'\x60\x00\xc0\x00\xff\x80\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0b\x00\x00\x00\x00\x00\x3e\x00\x7f\x00\xe3\x80\xc1\x80\x01\x80'\
b'\x0f\x00\x0f\x00\x03\x80\x01\x80\x01\x80\xc1\x80\xe3\x80\x7f\x00'\
b'\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00'\
b'\x06\x00\x06\x00\x0e\x00\x1e\x00\x16\x00\x26\x00\x46\x00\x46\x00'\
b'\x86\x00\xff\x00\xff\x00\x06\x00\x06\x00\x06\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x7f\x00\x7f\x00\x60\x00'\
b'\x60\x00\xde\x00\xff\x00\xe3\x80\x01\x80\x01\x80\x01\x80\x01\x80'\
b'\xc3\x00\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00'\
b'\x00\x00\x00\x00\x1e\x00\x3f\x00\x63\x00\x61\x80\xc0\x00\xde\x00'\
b'\xff\x00\xe3\x80\xc1\x80\xc1\x80\xc1\x80\x63\x80\x7f\x00\x3e\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xff\x80'\
b'\xff\x80\x01\x00\x03\x00\x02\x00\x06\x00\x04\x00\x0c\x00\x08\x00'\
b'\x18\x00\x18\x00\x10\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0b\x00\x00\x00\x00\x00\x1c\x00\x3e\x00\x63\x00\x63\x00'\
b'\x63\x00\x3e\x00\x3e\x00\x63\x00\xc1\x80\xc1\x80\xc1\x80\x63\x00'\
b'\x7f\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x3e\x00\x7f\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xe3\x80'\
b'\x7f\x80\x3d\x80\x01\x80\x03\x00\xe3\x00\x7e\x00\x3c\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\xc0\xc0\x00'\
b'\x00\x00\x00\x00\x00\x00\xc0\xc0\x00\x00\x00\x00\x05\x00\x00\x00'\
b'\x00\x00\x00\x00\xc0\xc0\x00\x00\x00\x00\x00\x00\xc0\xc0\x40\x40'\
b'\x80\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x40\x01\xc0\x07\x00\x3c\x00\xe0\x00\xe0\x00\x78\x00\x0f\x00'\
b'\x03\xc0\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0'\
b'\xff\xc0\x00\x00\x00\x00\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\xe0\x00\x78\x00\x0e\x00\x03\xc0\x01\xc0'\
b'\x07\x00\x3c\x00\xf0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0b\x00\x00\x00\x3c\x00\x7e\x00\xc7\x00\xc3\x00\x03\x00\x03\x00'\
b'\x06\x00\x0c\x00\x08\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00'\
b'\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x03'\
b'\xf0\x00\x0f\xfc\x00\x1e\x0f\x00\x38\x03\x80\x71\xe1\x80\x63\xe9'\
b'\xc0\x67\x18\xc0\xce\x18\xc0\xcc\x18\xc0\xcc\x10\xc0\xcc\x31\x80'\
b'\xce\x73\x80\x67\xff\x00\x63\x9e\x00\x30\x00\x00\x3c\x00\x00\x0f'\
b'\xf8\x00\x03\xf0\x00\x00\x00\x00\x0d\x00\x00\x00\x07\x00\x07\x00'\
b'\x07\x80\x0d\x80\x0d\x80\x08\xc0\x18\xc0\x18\xc0\x10\x60\x3f\xe0'\
b'\x3f\xe0\x30\x30\x60\x30\x60\x38\xc0\x18\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0d\x00\x00\x00\xff\x00\xff\x80\xc1\xc0\xc0\xc0\xc0\xc0'\
b'\xc1\xc0\xff\x00\xff\x80\xc0\xc0\xc0\x60\xc0\x60\xc0\x60\xc0\xe0'\
b'\xff\xc0\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x0f\x80\x3f\xe0\x70\x60\x60\x30\xe0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xe0\x30\x60\x70\x70\x60\x3f\xe0\x0f\x80\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\xff\x00\xff\x80\xc1\xc0'\
b'\xc0\xc0\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60'\
b'\xc0\xc0\xc1\xc0\xff\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0d\x00\x00\x00\xff\xc0\xff\xc0\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xff\x80\xff\x80\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xff\xc0'\
b'\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\xff\x80'\
b'\xff\x80\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xff\x00\xff\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0f\x00\x00\x00\x0f\xc0\x3f\xf0\x38\x30\x60\x18'\
b'\x60\x00\xc0\x00\xc0\x00\xc1\xf8\xc1\xf8\xc0\x18\xe0\x18\x60\x38'\
b'\x78\x78\x3f\xd8\x0f\x88\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xff\xe0'\
b'\xff\xe0\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\xc0\xc0\xc0\xc0\xc0'\
b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0b\x00'\
b'\x00\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00'\
b'\x03\x00\x03\x00\x03\x00\xc3\x00\xc3\x00\xe7\x00\x7e\x00\x3c\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xc0\x60\xc0\xc0'\
b'\xc1\x80\xc3\x00\xc6\x00\xcc\x00\xdc\x00\xf6\x00\xe6\x00\xc3\x00'\
b'\xc1\x80\xc1\x80\xc0\xc0\xc0\x60\xc0\x60\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0b\x00\x00\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xff\x80\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\
b'\x00\xe0\x1c\x00\xe0\x1c\x00\xf0\x3c\x00\xf0\x3c\x00\xd0\x2c\x00'\
b'\xd8\x6c\x00\xd8\x6c\x00\xc8\x4c\x00\xcc\xcc\x00\xcc\xcc\x00\xc4'\
b'\x8c\x00\xc6\x8c\x00\xc7\x8c\x00\xc3\x0c\x00\xc3\x0c\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\xe0\x60'\
b'\xe0\x60\xf0\x60\xf0\x60\xd8\x60\xd8\x60\xcc\x60\xc4\x60\xc6\x60'\
b'\xc2\x60\xc3\x60\xc1\xe0\xc1\xe0\xc0\xe0\xc0\xe0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x10\x00\x00\x00\x0f\xc0\x1f\xe0\x38\x70\x60\x18'\
b'\x60\x1c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\x60\x1c\x60\x18'\
b'\x38\x70\x1f\xe0\x0f\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00'\
b'\x00\x00\xff\x00\xff\x80\xc1\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc1\xc0'\
b'\xff\x80\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x0f\xc0\x1f\xe0'\
b'\x38\x70\x60\x18\x60\x1c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c'\
b'\x60\x18\x60\xd8\x38\x70\x1f\xf8\x0f\x98\x00\x08\x00\x00\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\xff\x80\xff\xc0\xc0\xe0\xc0\x60\xc0\x60'\
b'\xc0\x60\xc0\xc0\xff\x80\xff\xc0\xc0\xe0\xc0\x60\xc0\x60\xc0\x60'\
b'\xc0\x60\xc0\x70\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00'\
b'\x1f\x80\x7f\xe0\xe0\x70\xc0\x30\xc0\x00\xe0\x00\x78\x00\x3f\x80'\
b'\x03\xe0\x00\x70\xc0\x30\xc0\x30\x70\x60\x7f\xe0\x1f\x80\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xff\xc0\xff\xc0\x0c\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60'\
b'\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\x60\xc0\x7f\xc0'\
b'\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xc0\x30'\
b'\x60\x30\x60\x30\x20\x20\x30\x60\x30\x60\x10\x40\x18\xc0\x18\xc0'\
b'\x08\x80\x0d\x80\x0d\x80\x07\x00\x07\x00\x07\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x13\x00\x00\x00\x00\xc0\xc0\xc0\x60\xe0\xc0\x60'\
b'\xe0\xc0\x61\xe0\xc0\x61\xb1\x80\x31\xb1\x80\x31\xb1\x80\x33\x11'\
b'\x80\x33\x19\x00\x13\x1b\x00\x1f\x1b\x00\x1e\x0b\x00\x1e\x0e\x00'\
b'\x0e\x0e\x00\x0c\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0d\x00\x00\x00\x60\x30\x30\x70\x30\x60\x18\xc0\x0c\xc0'\
b'\x0d\x80\x07\x00\x07\x00\x07\x00\x0d\x80\x18\xc0\x18\xe0\x30\x60'\
b'\x70\x30\x60\x38\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\
b'\x60\x18\x70\x38\x30\x30\x18\x60\x18\x60\x0c\xc0\x0f\xc0\x07\x80'\
b'\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\xff\xe0\xff\xe0\x00\xc0'\
b'\x01\x80\x03\x80\x03\x00\x06\x00\x0c\x00\x1c\x00\x38\x00\x30\x00'\
b'\x60\x00\xc0\x00\xff\xe0\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x06\x00\x00\xe0\xe0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\
b'\xc0\xc0\xc0\xc0\xe0\xe0\x06\x00\x00\x80\xc0\x40\x40\x60\x20\x20'\
b'\x30\x10\x10\x18\x08\x08\x0c\x04\x00\x00\x00\x00\x06\x00\x00\xe0'\
b'\xe0\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\xe0\xe0\x09\x00\x00\x00\x00\x00\x18\x00\x38\x00\x28\x00\x2c\x00'\
b'\x64\x00\x46\x00\xc2\x00\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xf0'\
b'\x00\x00\x00\x00\x00\x00\x05\x00\x00\xc0\x60\x30\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x3e\x00\xff\x80\xc1\x80\x01\x80'\
b'\x01\x80\x3f\x80\xf1\x80\xc1\x80\xc3\x80\xff\xc0\x78\xc0\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc0\x00\xdf\x00\xff\x80\xe1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\
b'\xc0\xc0\xe1\x80\xff\x80\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x7f\x00'\
b'\x61\x80\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc1\x80\x63\x80\x7f\x00'\
b'\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x01\x80'\
b'\x01\x80\x01\x80\x01\x80\x3d\x80\x7f\x80\x63\x80\xc1\x80\xc1\x80'\
b'\xc1\x80\xc1\x80\xc1\x80\x63\x80\x7f\x80\x3d\x80\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x3e\x00\x7f\x00\x63\x00\xc1\x80\xff\x80\xff\x80\xc0\x00\xc0\x00'\
b'\x63\x80\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00'\
b'\x00\x30\x70\x60\x60\xf0\xf0\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x3d\x80\x7f\x80\x63\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\
b'\x63\x80\x7f\x80\x3d\x80\x01\x80\xc3\x80\x7f\x00\x3e\x00\x0b\x00'\
b'\x00\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xdf\x00\xdf\x80\xe3\x80'\
b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\xc0\xc0\x00\x00\xc0'\
b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x05\x00'\
b'\x00\x30\x30\x00\x00\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\xf0\xe0\x0a\x00\x00\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\
b'\xc3\x00\xc6\x00\xcc\x00\xd8\x00\xf8\x00\xec\x00\xce\x00\xc6\x00'\
b'\xc3\x00\xc3\x00\xc1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00'\
b'\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\
b'\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\xde\x78\xfe\xfc\xe3\x8c\xc3\x0c\xc3\x0c\xc3\x0c\xc3\x0c\xc3\x0c'\
b'\xc3\x0c\xc3\x0c\xc3\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf\x00\xdf\x80\xe3\x80'\
b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x3e\x00\x7f\x00\x63\x00\xc1\x80\xc1\x80\xc1\x80'\
b'\xc1\x80\xc1\x80\x63\x00\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\x00'\
b'\xff\x80\xe1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xe1\x80'\
b'\xff\x80\xde\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x3d\x80\x7f\x80\x63\x80\xc1\x80'\
b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\x63\x80\x7f\x80\x3d\x80\x01\x80'\
b'\x01\x80\x01\x80\x00\x00\x07\x00\x00\x00\x00\x00\x00\xd8\xf8\xe0'\
b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0a\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x00\x7f\x00\xc3\x00\xc0\x00'\
b'\xf0\x00\x7e\x00\x0f\x00\x03\x00\xc3\x00\xfe\x00\x7c\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x60\x60\xf0\xf0\x60'\
b'\x60\x60\x60\x60\x60\x60\x70\x70\x00\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\
b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x80\xfd\x80\x79\x80\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\xc0\xc0\x61\x80\x61\x80\x61\x00\x23\x00\x33\x00\x32\x00'\
b'\x16\x00\x1e\x00\x1c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc3\x0c\xc3\x8c'\
b'\x63\x8c\x67\x88\x66\x98\x24\xd8\x34\xd0\x3c\xd0\x3c\x70\x18\x70'\
b'\x18\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x61\x80\x63\x00\x33\x00\x1e\x00\x1c\x00'\
b'\x0c\x00\x1c\x00\x16\x00\x33\x00\x63\x00\x41\x80\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\xc0\x80\x41\x80\x61\x80\x61\x00\x23\x00\x33\x00\x32\x00\x16\x00'\
b'\x1c\x00\x1c\x00\x0c\x00\x08\x00\x18\x00\x78\x00\x70\x00\x0a\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x06\x00'\
b'\x06\x00\x0c\x00\x18\x00\x30\x00\x60\x00\xc0\x00\xff\x00\xff\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x18\x38\x30\x30\x30'\
b'\x30\x30\x30\x70\xc0\x70\x30\x30\x30\x30\x30\x30\x38\x18\x05\x00'\
b'\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\
b'\xc0\xc0\xc0\xc0\x07\x00\x00\xc0\xe0\x60\x60\x60\x60\x60\x60\x70'\
b'\x18\x70\x60\x60\x60\x60\x60\x60\xe0\xc0\x0a\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\xf1\x00\x9f\x00'\
b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00'
_index =\
b'\x00\x00\x2a\x00\x2a\x00\x40\x00\x40\x00\x56\x00\x56\x00\x6c\x00'\
b'\x6c\x00\x96\x00\x96\x00\xc0\x00\xc0\x00\xfe\x00\xfe\x00\x28\x01'\
b'\x28\x01\x3e\x01\x3e\x01\x54\x01\x54\x01\x6a\x01\x6a\x01\x80\x01'\
b'\x80\x01\xaa\x01\xaa\x01\xc0\x01\xc0\x01\xd6\x01\xd6\x01\xec\x01'\
b'\xec\x01\x02\x02\x02\x02\x2c\x02\x2c\x02\x56\x02\x56\x02\x80\x02'\
b'\x80\x02\xaa\x02\xaa\x02\xd4\x02\xd4\x02\xfe\x02\xfe\x02\x28\x03'\
b'\x28\x03\x52\x03\x52\x03\x7c\x03\x7c\x03\xa6\x03\xa6\x03\xbc\x03'\
b'\xbc\x03\xd2\x03\xd2\x03\xfc\x03\xfc\x03\x26\x04\x26\x04\x50\x04'\
b'\x50\x04\x7a\x04\x7a\x04\xb8\x04\xb8\x04\xe2\x04\xe2\x04\x0c\x05'\
b'\x0c\x05\x36\x05\x36\x05\x60\x05\x60\x05\x8a\x05\x8a\x05\xb4\x05'\
b'\xb4\x05\xde\x05\xde\x05\x08\x06\x08\x06\x1e\x06\x1e\x06\x48\x06'\
b'\x48\x06\x72\x06\x72\x06\x9c\x06\x9c\x06\xda\x06\xda\x06\x04\x07'\
b'\x04\x07\x2e\x07\x2e\x07\x58\x07\x58\x07\x82\x07\x82\x07\xac\x07'\
b'\xac\x07\xd6\x07\xd6\x07\x00\x08\x00\x08\x2a\x08\x2a\x08\x54\x08'\
b'\x54\x08\x92\x08\x92\x08\xbc\x08\xbc\x08\xe6\x08\xe6\x08\x10\x09'\
b'\x10\x09\x26\x09\x26\x09\x3c\x09\x3c\x09\x52\x09\x52\x09\x7c\x09'\
b'\x7c\x09\xa6\x09\xa6\x09\xbc\x09\xbc\x09\xe6\x09\xe6\x09\x10\x0a'\
b'\x10\x0a\x3a\x0a\x3a\x0a\x64\x0a\x64\x0a\x8e\x0a\x8e\x0a\xa4\x0a'\
b'\xa4\x0a\xce\x0a\xce\x0a\xf8\x0a\xf8\x0a\x0e\x0b\x0e\x0b\x24\x0b'\
b'\x24\x0b\x4e\x0b\x4e\x0b\x64\x0b\x64\x0b\x8e\x0b\x8e\x0b\xb8\x0b'\
b'\xb8\x0b\xe2\x0b\xe2\x0b\x0c\x0c\x0c\x0c\x36\x0c\x36\x0c\x4c\x0c'\
b'\x4c\x0c\x76\x0c\x76\x0c\x8c\x0c\x8c\x0c\xb6\x0c\xb6\x0c\xe0\x0c'\
b'\xe0\x0c\x0a\x0d\x0a\x0d\x34\x0d\x34\x0d\x5e\x0d\x5e\x0d\x88\x0d'\
b'\x88\x0d\x9e\x0d\x9e\x0d\xb4\x0d\xb4\x0d\xca\x0d\xca\x0d\xf4\x0d'\
_mvfont = memoryview(_font)
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
idx_offs = 4 * (ordch - 32)
offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little')
next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little')
width = int.from_bytes(_font[offset:offset + 2], 'little')
return _mvfont[offset + 2:next_offs], 20, width

BIN
images/IMG_2885.png 100644

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 50 KiB

BIN
images/IMG_2887.png 100644

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 44 KiB

116
mono_test.py 100644
Wyświetl plik

@ -0,0 +1,116 @@
# mono_test.py Demo program for nano_gui on an SSD1306 OLED display.
# The MIT License (MIT)
#
# Copyright (c) 2018 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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
import utime
import uos
from ssd1306_setup import WIDTH, HEIGHT, setup
from writer import Writer, CWriter
from nanogui import Label, Meter, refresh
# Fonts
import courier20 as fixed
import font6 as small
import arial10
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
wri = Writer(ssd, fixed, verbose=False)
wri.set_clip(False, False, False)
textfield = Label(wri, 0, 2, wri.stringlen('longer'))
numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bdcolor=None)
countfield = Label(wri, 0, 90, wri.stringlen('1'))
n = 1
for s in ('short', 'longer', '1', ''):
textfield.value(s)
numfield.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000))
countfield.value('{:1d}'.format(n))
n += 1
refresh(ssd)
utime.sleep(2)
textfield.value('Done', True)
refresh(ssd)
def multi_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
wri = Writer(ssd, small, verbose=False)
wri.set_clip(False, False, False)
nfields = []
dy = small.height() + 6
y = 2
col = 15
width = wri.stringlen('99.99')
for txt in ('X:', 'Y:', 'Z:'):
Label(wri, y, 0, txt)
nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border
y += dy
for _ in range(10):
for field in nfields:
value = int.from_bytes(uos.urandom(3),'little')/167772
field.value('{:5.2f}'.format(value))
refresh(ssd)
utime.sleep(1)
Label(wri, 0, 64, ' DONE ', True)
refresh(ssd)
def meter(use_spi=False, soft=True):
ssd = setup(use_spi, soft)
wri = Writer(ssd, arial10, verbose=False)
ssd.fill(0)
refresh(ssd)
m0 = Meter(wri, 5, 2, height = 50, divisions = 4, legends=('0.0', '0.5', '1.0'))
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
for n in range(steps + 1):
m0.value(int.from_bytes(uos.urandom(3),'little')/16777216)
m1.value(n/steps)
m2.value(1 - n/steps)
refresh(ssd)
utime.sleep(1)
tstr = '''Test assumes a 128*64 (w*h) display. Edit WIDTH and HEIGHT in ssd1306_setup.py for others.
Device pinouts are comments in ssd1306_setup.py.
All tests take two boolean args:
use_spi = False. Set True for SPI connected device
soft=True set False to use hardware I2C/SPI. Hardware I2C option currently fails with official SSD1306 driver.
Available tests:
fields() Label test with dynamic data.
multi_fields() More Labels.
meter() Demo of Meter object.
'''
print(tstr)

385
nanogui.py 100644
Wyświetl plik

@ -0,0 +1,385 @@
# nanogui.py Displayable objects based on the Writer and CWriter classes
# V0.3 Peter Hinch 26th Aug 2018
# The MIT License (MIT)
#
# Copyright (c) 2018 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Base class for a displayable object. Subclasses must implement .show() and .value()
# Has position, colors and border definition.
# border: False no border None use bgcolor, int: treat as color
import cmath
from writer import Writer
import framebuf
import gc
def _circle(dev, x0, y0, r, color): # Single pixel circle
x = -r
y = 0
err = 2 -2*r
while x <= 0:
dev.pixel(x0 -x, y0 +y, color)
dev.pixel(x0 +x, y0 +y, color)
dev.pixel(x0 +x, y0 -y, color)
dev.pixel(x0 -x, y0 -y, color)
e2 = err
if (e2 <= y):
y += 1
err += y*2 +1
if (-x == y and e2 <= x):
e2 = 0
if (e2 > x):
x += 1
err += x*2 +1
def circle(dev, x0, y0, r, color, width =1): # Draw circle
x0, y0, r = int(x0), int(y0), int(r)
for r in range(r, r -width, -1):
_circle(dev, x0, y0, r, color)
def fillcircle(dev, x0, y0, r, color): # Draw filled circle
x0, y0, r = int(x0), int(y0), int(r)
x = -r
y = 0
err = 2 -2*r
while x <= 0:
dev.line(x0 -x, y0 -y, x0 -x, y0 +y, color)
dev.line(x0 +x, y0 -y, x0 +x, y0 +y, color)
e2 = err
if (e2 <= y):
y +=1
err += y*2 +1
if (-x == y and e2 <= x):
e2 = 0
if (e2 > x):
x += 1
err += x*2 +1
# Line defined by polar coords; origin and line are complex
def polar(dev, origin, line, color):
xs, ys = origin.real, origin.imag
theta = cmath.polar(line)[1]
dev.line(round(xs), round(ys), round(xs + line.real), round(ys - line.imag), color)
def conj(v): # complex conjugate
return v.real - v.imag * 1j
# Draw an arrow; origin and vec are complex, scalar lc defines length of chevron.
# cw and ccw are unit vectors of +-3pi/4 radians for chevrons (precompiled)
def arrow(dev, origin, vec, lc, color, ccw=cmath.exp(3j * cmath.pi/4), cw=cmath.exp(-3j * cmath.pi/4)):
length, theta = cmath.polar(vec)
uv = cmath.rect(1, theta) # Unit rotation vector
start = -vec
if length > 3 * lc: # If line is long
ds = cmath.rect(lc, theta)
start += ds # shorten to allow for length of tail chevrons
chev = lc + 0j
polar(dev, origin, vec, color) # Origin to tip
polar(dev, origin, start, color) # Origin to tail
polar(dev, origin + conj(vec), chev*ccw*uv, color) # Tip chevron
polar(dev, origin + conj(vec), chev*cw*uv, color)
if length > lc: # Confusing appearance of very short vectors with tail chevron
polar(dev, origin + conj(start), chev*ccw*uv, color) # Tail chevron
polar(dev, origin + conj(start), chev*cw*uv, color)
# If a (framebuf based) device is passed to refresh, the screen is cleared.
# None causes pending widgets to be drawn and the result to be copied to hardware.
# The pend mechanism enables a displayable object to postpone its renedering
# until it is complete: efficient for e.g. Dial which may have multiple Pointers
def refresh(device, clear=False):
if not isinstance(device, framebuf.FrameBuffer):
raise ValueError('Device must be derived from FrameBuffer.')
if device not in DObject.devices:
DObject.devices[device] = set()
device.fill(0)
else:
if clear:
DObject.devices[device].clear() # Clear the pending set
device.fill(0)
else:
for obj in DObject.devices[device]:
obj.show()
DObject.devices[device].clear()
device.show()
# Displayable object: effectively an ABC for all GUI objects.
class DObject():
devices = {} # Index device instance, value is a set of pending objects
@classmethod
def _set_pend(cls, obj):
cls.devices[obj.device].add(obj)
def __init__(self, writer, row, col, height, width, fgcolor, bgcolor, bdcolor):
writer.set_clip(True, True, False) # Disable scrolling text
self.writer = writer
device = writer.device
self.device = device
if row < 0:
row = 0
self.warning()
elif row + height >= device.height:
row = device.height - height - 1
self.warning()
if col < 0:
col = 0
self.warning()
elif col + width >= device.width:
row = device.width - width - 1
self.warning()
self.row = row
self.col = col
self.width = width
self.height = height
self._value = None # Type depends on context but None means don't display.
# Current colors
if fgcolor is None:
fgcolor = writer.fgcolor
if bgcolor is None:
bgcolor = writer.bgcolor
if bdcolor is None:
bdcolor = fgcolor
self.fgcolor = fgcolor
self.bgcolor = bgcolor
# bdcolor is False if no border is to be drawn
self.bdcolor = bdcolor
# Default colors allow restoration after dynamic change
self.def_fgcolor = fgcolor
self.def_bgcolor = bgcolor
self.def_bdcolor = bdcolor
# has_border is True if a border was drawn
self.has_border = False
def warning(self):
print('Warning: attempt to create {} outside screen dimensions.'.format(self.__class__.__name__))
# Blank working area
# Draw a border if .bdcolor specifies a color. If False, erase an existing border
def show(self):
wri = self.writer
dev = self.device
dev.fill_rect(self.col, self.row, self.width, self.height, self.bgcolor)
if isinstance(self.bdcolor, bool): # No border
if self.has_border: # Border exists: erase it
dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bgcolor)
self.has_border = False
elif self.bdcolor: # Border is required
dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bdcolor)
self.has_border = True
def value(self, v=None):
if v is not None:
self._value = v
return self._value
def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
if hasattr(self, 'label'):
self.label.value(text, invert, fgcolor, bgcolor, bdcolor)
else:
raise ValueError('Attempt to update nonexistent label.')
# text: str display string int save width
class Label(DObject):
def __init__(self, writer, row, col, text, invert=False, fgcolor=None, bgcolor=None, bdcolor=False):
# Determine width of object
if isinstance(text, int):
width = text
text = None
else:
width = writer.stringlen(text)
height = writer.height
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
if text is not None:
self.value(text, invert)
def value(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
txt = super().value(text)
# Redraw even if no text supplied: colors may have changed.
self.invert = invert
self.fgcolor = self.def_fgcolor if fgcolor is None else fgcolor
self.bgcolor = self.def_bgcolor if bgcolor is None else bgcolor
if bdcolor is False:
self.def_bdcolor = False
self.bdcolor = self.def_bdcolor if bdcolor is None else bdcolor
self.show()
return txt
def show(self):
txt = super().value()
if txt is None: # No content to draw. Future use.
return
super().show() # Draw or erase border
wri = self.writer
dev = self.device
wri.setcolor(self.fgcolor, self.bgcolor)
Writer.set_textpos(dev, self.row, self.col)
wri.setcolor(self.fgcolor, self.bgcolor)
wri.printstring(txt, self.invert)
wri.setcolor() # Restore defaults
class Meter(DObject):
BAR = 1
LINE = 0
def __init__(self, writer, row, col, *, height=50, width=10,
fgcolor=None, bgcolor=None, ptcolor=None, bdcolor=None,
divisions=5, label=None, style=0, legends=None, value=None):
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
self.divisions = divisions
if label is not None:
Label(writer, row + height + 3, col, label)
self.style = style
self.legends = legends
self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor
self.value(value)
def value(self, n=None, color=None):
if n is None:
return super().value()
n = super().value(min(1, max(0, n)))
if color is not None:
self.ptcolor = color
self.show()
return n
def show(self):
super().show() # Draw or erase border
val = super().value()
wri = self.writer
dev = self.device
width = self.width
height = self.height
legends = self.legends
x0 = self.col
x1 = self.col + width
y0 = self.row
y1 = self.row + height
if self.divisions > 0:
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
ypos = int(y0 + dy * tick)
dev.hline(x0 + 2, ypos, x1 - x0 - 4, self.fgcolor)
if legends is not None: # Legends
dy = 0 if len(legends) <= 1 else height / (len(legends) -1)
yl = y1 - wri.height / 2 # Start at bottom
for legend in legends:
Label(wri, int(yl), x1 + 4, legend)
yl -= dy
y = int(y1 - val * height) # y position of slider
if self.style == self.LINE:
dev.hline(x0, y, width, self.ptcolor) # Draw pointer
else:
w = width / 2
dev.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)
class LED(DObject):
def __init__(self, writer, row, col, *, height=12,
fgcolor=None, bgcolor=None, bdcolor=None, label=None):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
if label is not None:
self.label = Label(writer, row + height + 3, col, label)
self.radius = self.height // 2
def color(self, c=None):
self.fgcolor = self.bgcolor if c is None else c
self.show()
def show(self):
super().show()
wri = self.writer
dev = self.device
r = self.radius
fillcircle(dev, self.col + r, self.row + r, r, self.fgcolor)
if isinstance(self.bdcolor, int):
circle(dev, self.col + r, self.row + r, r, self.bdcolor)
class Pointer():
def __init__(self, dial):
self.dial = dial
self.val = 0 + 0j
self.color = None
def value(self, v=None, color=None):
self.color = color
if v is not None:
if isinstance(v, complex):
l = cmath.polar(v)[0]
if l > 1:
self.val = v/l
else:
self.val = v
else:
raise ValueError('Pointer value must be complex.')
self.dial.vectors.add(self)
self.dial._set_pend(self.dial) # avoid redrawing for each vector
return self.val
class Dial(DObject):
CLOCK = 0
COMPASS = 1
def __init__(self, writer, row, col, *, height=50,
fgcolor=None, bgcolor=None, bdcolor=False, ticks=4,
label=None, style=0, pip=None):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
self.style = style
self.pip = self.fgcolor if pip is None else pip
if label is not None:
self.label = Label(writer, row + height + 3, col, label)
radius = int(height / 2)
self.radius = radius
self.ticks = ticks
self.xorigin = col + radius
self.yorigin = row + radius
self.vectors = set()
def show(self):
super().show()
# cache bound variables
dev = self.device
ticks = self.ticks
radius = self.radius
xo = self.xorigin
yo = self.yorigin
# vectors (complex)
vor = xo + 1j * yo
vtstart = 0.9 * radius + 0j # start of tick
vtick = 0.1 * radius + 0j # tick
vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation
for _ in range(ticks):
polar(dev, vor + conj(vtstart), vtick, self.fgcolor)
vtick *= vrot
vtstart *= vrot
circle(dev, xo, yo, radius, self.fgcolor)
vshort = 1000 # Length of shortest vector
for v in self.vectors:
color = self.fgcolor if v.color is None else v.color
val = v.value() * radius # val is complex
vshort = min(vshort, cmath.polar(val)[0])
if self.style == Dial.CLOCK:
polar(dev, vor, val, color)
else:
arrow(dev, vor, val, 5, color)
if isinstance(self.pip, int) and vshort > 5:
fillcircle(dev, xo, yo, 2, self.pip)

267
plot/FPLOT.md 100644
Wyświetl plik

@ -0,0 +1,267 @@
# fplot module
This provides a rudimentary means of displaying two dimensional Cartesian (xy)
and polar graphs on `framebuf` based displays. It is an optional extension to
the MicroPython [nano-gui](https://github.com/peterhinch/micropython-nano-gui)
library: this should be installed, configured and tested before use.
This was ported from the
[lcd160cr-gui library](https://github.com/peterhinch/micropython-lcd160cr-gui).
Like `nanogui.py` it uses synchronous code.
# Contents
1. [Python files](./FPLOT.md#1-python-files)
2. [Concepts](./FPLOT.md#2-concepts)
2.1 [Graph classes](./FPLOT.md#21-graph-classes)
2.2 [Curve clsses](./FPLOT.md#22-curve-classes)
2.3 [Coordinates](./FPLOT.md#23-coordinates)
3. [Graph classes](./FPLOT.md#3-graph-classes)
3.1 [Class CartesianGraph](./FPLOT.md#31-class-cartesiangraph)
3.2 [Class PolarGraph](./FPLOT.md#32-class-polargraph)
4. [Curve classes](./FPLOT.md#4-curve-classes)
4.1 [class Curve](./FPLOT.md#41-class-curve)
4.1.1 [Scaling](./FPLOT.md#411-scaling) Optional scaling of data values.
4.2 [class PolarCurve](./FPLOT.md#42-class-polarcurve)
4.2.1 [Scaling](./FPLOT.md#421-scaling) Required scaling of complex points.
4.3 [class TSequence](./FPLOT.md#43-class-tsequence) Plot Y values on time axis.
###### [Main README](../README.md)
# 1. Python files
These are located in the `plot` directory.
1. `fplot.py` The plot library
2. `fpt.py` Test program. Usage examples.
# 2. Concepts
Data for Cartesian graphs constitutes a set of x, y pairs, for polar graphs
it is a set of complex `z` values. The module supports three common cases:
1. The dataset is complete at the outset.
2. Arbitrary data arrives gradually and needs to be plotted as it arrives.
3. One or more `y` values arrive gradually. The `X` axis represents time. This
is a simplifying case of 2.
## 2.1 Graph classes
A user program first instantiates a graph object (`PolarGraph` or
`CartesianGraph`). This creates an empty graph image upon which one or more
curves may be plotted.
## 2.2 Curve classes
The user program then instantiates one or more curves (`Curve` or
`PolarCurve`) as appropriate to the graph. Curves may be assigned colors to
distinguish them.
A curve is plotted by means of a user defined `populate` generator. This
assigns points to the curve in the order in which they are to be plotted. The
curve will be displayed on the graph as a sequence of straight line segments
between successive points.
Where it is required to plot realtime data as it arrives, this is achieved
via calls to the curve's `point` method.
## 2.3 Coordinates
Graph objects are sized and positioned in terms of TFT screen pixel
coordinates, with (0, 0) being the top left corner of the display, with x
increasing to the right and y increasing downwards. The coordinate system
within a graph conforms to normal mathematical conventions.
Scaling is provided on Cartesian curves enabling user defined ranges for x and
y values. Points lying outside of the defined range will produce lines which
are clipped at the graph boundary.
Points on polar curves are defined as Python `complex` types and should lie
within the unit circle. Points which are out of range may be plotted beyond the
unit circle but will be clipped to the rectangular graph boundary.
###### [Contents](./FPLOT.md#contents)
# 3. Graph classes
## 3.1 Class CartesianGraph
Constructor.
Mandatory positional arguments:
1. `writer` A `CWriter` instance.
2. `row` Position of the graph in screen coordinates.
3. `col`
Keyword only arguments (all optional):
* `height=90` Dimension of the bounding box.
* `width=110` Dimension of the bounding box.
* `fgcolor=None` Color of the axis lines. Defaults to Writer forgeround color.
* `bgcolor=None` Background color of graph. Defaults to Writer background.
* `bdcolor=None` Border color. If `False` no border is displayed. If `None` a
border is shown in the `Writer` forgeround color. If a color is passed, it is
used.
* `gridcolor=None` Color of grid. Default: Writer forgeround color.
* `xdivs=10` Number of divisions (grid lines) on x axis.
* `ydivs=10` Number of divisions on y axis.
* `xorigin=5` Location of origin in terms of grid divisions.
* `yorigin=5` As `xorigin`. The default of 5, 5 with 10 grid lines on each
axis puts the origin at the centre of the graph. Settings of 0, 0 would be
used to plot positive values only.
Methods:
1. `clear` No args. Clears all curves from the graph.
2. `show` No args. Redraws the graph. For future/subclass use.
## 3.2 Class PolarGraph
Constructor.
Mandatory positional arguments:
1. `writer` A `CWriter` instance.
2. `row` Position of the graph in screen coordinates.
3. `col`
Keyword only arguments (all optional):
* `height=90` Dimension of the square bounding box.
* `fgcolor=None` Color of the axis lines. Defaults to Writer forgeround color.
* `bgcolor=None` Background color of graph. Defaults to Writer background.
* `bdcolor=None` Border color. If `False` no border is displayed. If `None` a
border is shown in the `Writer` forgeround color. If a color is passed, it is
used.
* `gridcolor=None` Color of grid. Default: Writer forgeround color.
* `adivs=3` Number of angle divisions per quadrant.
* `rdivs=4` Number radius divisions.
Methods:
1. `clear` No args. Clears all curves from the graph.
2. `show` No args. Redraws the graph. For future/subclass use.
###### [Contents](./FPLOT.md#contents)
# 4. Curve classes
## 4.1 class Curve
The Cartesian curve constructor takes the following positional arguments:
Mandatory arguments:
1. `graph` The `CartesianGraph` instance.
2. `color`
Optional arguments:
3. `populate=None` A generator to populate the curve. See below.
4. `origin=(0,0)` 2-tuple containing x and y values for the origin. Provides
for an optional shift of the data's origin.
5. `excursion=(1,1)` 2-tuple containing scaling values for x and y.
Methods:
* `point` Arguments x, y. Defaults `None`. Adds a point to the curve. If a
prior point exists a line will be drawn between it and the current point. If a
point is out of range or if either arg is `None` no line will be drawn.
Passing no args enables discontinuous curves to be plotted. This method is
normally used for real time plotting.
The `populate` generator may take zero or more positional arguments. It should
repeatedly yield `x, y` values before returning. Where a curve is discontinuous
`None, None` may be yielded: this causes the line to stop. It is resumed when
the next valid `x, y` pair is yielded.
If `populate` is not provided the curve may be plotted by successive calls to
the `point` method. This may be of use where data points are acquired in real
time, and realtime plotting is required. See function `rt_rect` in `fpt.py`.
### 4.1.1 Scaling
By default, with symmetrical axes, x and y values are assumed to lie between -1
and +1.
To plot x values from 1000 to 4000 we would set the `origin` x value to 1000
and the `excursion` x value to 3000. The `excursion` values scale the plotted
values to fit the corresponding axis.
## 4.2 class PolarCurve
The constructor takes the following positional arguments:
Mandatory arguments:
1. `graph` The `PolarGraph` instance.
2. `color`
Optional arguments:
3. `populate=None` A generator to populate the curve. See below.
Methods:
* `point` Argument `z=None`. Normally a `complex`. Adds a point
to the curve. If a prior point exists a line will be drawn between it and the
current point. If the arg is `None` no line will be drawn. Passing no args
enables discontinuous curves to be plotted. Lines are clipped at the square
region bounded by (-1, -1) to (+1, +1).
The `populate` generator may take zero or more positional arguments. It should
yield a complex `z` value for each point before returning. Where a curve is
discontinuous a value of `None` may be yielded: this causes plotting to stop.
It is resumed when the next valid `z` point is yielded.
If `populate` is not provided the curve may be plotted by successive calls to
the `point` method. This may be of use where data points are acquired in real
time, and realtime plotting is required.
### 4.2.1 Scaling
Complex points should lie within the unit circle to be drawn within the grid.
###### [Contents](./FPLOT.md#contents)
## 4.3 class TSequence
A common task is the acquisition and plotting of real time data against time,
such as hourly temperature and air pressure readings. This class facilitates
this. Time is on the x-axis with the most recent data on the right. Older
points are plotted to the left until they reach the left hand edge when they
are discarded. This is akin to old fashioned pen plotters where the pen was at
the rightmost edge (corresponding to time now) with old values scrolling to the
left with the time axis in the conventional direction.
The user instantiates a graph with the X origin at the right hand side and then
instantiates one or more `TSequence` objects. As each set of data arrives it is
appended to its `TSequence` using the `add` method. See the example below.
The constructor takes the following args:
Mandatory arguments:
1. `graph` The `PolarGraph` instance.
2. `color`
3. `size` Integer. The number of time samples to be plotted. See below.
Optional arguments:
4. `yorigin=0` These args provide scaling of Y axis values as per the `Curve`
class.
5 `yexc=1`
Method:
1. `add` Arg `v` the value to be plotted. This should lie between -1 and +1
unless scaling is applied.
Note that there is little point in setting the `size` argument to a value
greater than the number of X-axis pixels on the graph. It will work but RAM
and execution time will be wasted: the constructor instantiates an array of
floats of this size.
Each time a data set arrives the graph should be cleared, a data value should
be added to each `TSequence` instance, and the display instance should be
refreshed. The following example assumes that `ssd` is the display device and
`wri` is a `Writer` or `CWriter` instance.
```python
def foo():
refresh(ssd, True) # Clear any prior image
g = CartesianGraph(wri, 2, 2, xorigin = 10, fgcolor=WHITE, gridcolor=LIGHTGREEN)
tsy = TSequence(g, YELLOW, 50)
tsr = TSequence(g, RED, 50)
for t in range(100):
g.clear()
tsy.add(0.9*math.sin(t/10))
tsr.add(0.4*math.cos(t/10))
refresh(ssd)
utime.sleep_ms(100)
```
###### [Contents](./FPLOT.md#contents)

272
plot/fplot.py 100644
Wyświetl plik

@ -0,0 +1,272 @@
# fplot.py Graph plotting extension for nanogui
# Now clips out of range lines
# The MIT License (MIT)
#
# Copyright (c) 2018 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from nanogui import DObject, circle
from cmath import rect, pi
from micropython import const
from array import array
type_gen = type((lambda: (yield))())
# Line clipping outcode bits
_TOP = const(1)
_BOTTOM = const(2)
_LEFT = const(4)
_RIGHT = const(8)
# Bounding box for line clipping
_XMAX = const(1)
_XMIN = const(-1)
_YMAX = const(1)
_YMIN = const(-1)
class Curve():
@staticmethod
def _outcode(x, y):
oc = _TOP if y > 1 else 0
oc |= _BOTTOM if y < -1 else 0
oc |= _RIGHT if x > 1 else 0
oc |= _LEFT if x < -1 else 0
return oc
def __init__(self, graph, color, populate=None, origin=(0, 0), excursion=(1, 1)):
self.graph = graph
self.origin = origin
self.excursion = excursion
self.color = color
self.lastpoint = None
self.newpoint = None
if populate is not None and self._valid(populate):
for x, y in populate:
self.point(x, y)
def _valid(self, populate):
if not isinstance(populate, type_gen):
raise ValueError('populate must be a generator.')
return True
def point(self, x=None, y=None):
if x is None or y is None:
self.newpoint = None
self.lastpoint = None
return
self.newpoint = self._scale(x, y) # In-range points scaled to +-1 bounding box
if self.lastpoint is None: # Nothing to plot. Save for next line.
self.lastpoint = self.newpoint
return
res = self._clip(*(self.lastpoint + self.newpoint)) # Clip to +-1 box
if res is not None: # Ignore lines which don't intersect
self.graph.line(res[0:2], res[2:], self.color)
self.lastpoint = self.newpoint # Scaled but not clipped
# Cohen–Sutherland line clipping algorithm
# If self.newpoint and self.lastpoint are valid clip them so that both lie
# in +-1 range. If both are outside the box return None.
def _clip(self, x0, y0, x1, y1):
oc1 = self._outcode(x0, y0)
oc2 = self._outcode(x1, y1)
while True:
if not oc1 | oc2: # OK to plot
return x0, y0, x1, y1
if oc1 & oc2: # Nothing to do
return
oc = oc1 if oc1 else oc2
if oc & _TOP:
x = x0 + (_YMAX - y0)*(x1 - x0)/(y1 - y0)
y = _YMAX
elif oc & _BOTTOM:
x = x0 + (_YMIN - y0)*(x1 - x0)/(y1 - y0)
y = _YMIN
elif oc & _RIGHT:
y = y0 + (_XMAX - x0)*(y1 - y0)/(x1 - x0)
x = _XMAX
elif oc & _LEFT:
y = y0 + (_XMIN - x0)*(y1 - y0)/(x1 - x0)
x = _XMIN
if oc is oc1:
x0, y0 = x, y
oc1 = self._outcode(x0, y0)
else:
x1, y1 = x, y
oc2 = self._outcode(x1, y1)
def _scale(self, x, y): # Scale to +-1.0
x0, y0 = self.origin
xr, yr = self.excursion
xs = (x - x0) / xr
ys = (y - y0) / yr
return xs, ys
class PolarCurve(Curve): # Points are complex
def __init__(self, graph, color, populate=None):
super().__init__(graph, color)
if populate is not None and self._valid(populate):
for z in populate:
self.point(z)
def point(self, z=None):
if z is None:
self.newpoint = None
self.lastpoint = None
return
self.newpoint = self._scale(z.real, z.imag) # In-range points scaled to +-1 bounding box
if self.lastpoint is None: # Nothing to plot. Save for next line.
self.lastpoint = self.newpoint
return
res = self._clip(*(self.lastpoint + self.newpoint)) # Clip to +-1 box
if res is not None: # At least part of line was in box
start = res[0] + 1j*res[1]
end = res[2] + 1j*res[3]
self.graph.cline(start, end, self.color)
self.lastpoint = self.newpoint # Scaled but not clipped
class TSequence(Curve):
def __init__(self, graph, color, size, yorigin=0, yexc=1):
super().__init__(graph, color, origin=(0, yorigin), excursion=(1, yexc))
self.data = array('f', (0 for _ in range(size)))
self.cur = 0
self.size = size
self.count = 0
def add(self, v):
p = self.cur
size = self.size
self.data[self.cur] = v
self.cur += 1
self.cur %= size
if self.count < size:
self.count += 1
x = 0
dx = 1/size
for _ in range(self.count):
self.point(x, self.data[p])
x -= dx
p -= 1
p %= size
self.point()
class Graph(DObject):
def __init__(self, writer, row, col, height, width, fgcolor, bgcolor, bdcolor, gridcolor):
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
super().show() # Draw border
self.x0 = col
self.x1 = col + width
self.y0 = row
self.y1 = row + height
if gridcolor is None:
gridcolor = self.fgcolor
self.gridcolor = gridcolor
def clear(self):
self.show() # Clear working area
class CartesianGraph(Graph):
def __init__(self, writer, row, col, *, height=90, width = 120, fgcolor=None, bgcolor=None, bdcolor=None,
gridcolor=None, xdivs=10, ydivs=10, xorigin=5, yorigin=5):
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, gridcolor)
self.xdivs = xdivs
self.ydivs = ydivs
self.x_axis_len = max(xorigin, xdivs - xorigin) * width / xdivs # Max distance from origin in pixels
self.y_axis_len = max(yorigin, ydivs - yorigin) * height / ydivs
self.xp_origin = self.x0 + xorigin * width / xdivs # Origin in pixels
self.yp_origin = self.y0 + (ydivs - yorigin) * height / ydivs
self.xorigin = xorigin
self.yorigin = yorigin
self.show()
def show(self):
super().show() # Clear working area
ssd = self.device
x0 = self.x0
x1 = self.x1
y0 = self.y0
y1 = self.y1
if self.ydivs > 0:
dy = self.height / (self.ydivs) # Y grid line
for line in range(self.ydivs + 1):
color = self.fgcolor if line == self.yorigin else self.gridcolor
ypos = round(self.y1 - dy * line)
ssd.hline(x0, ypos, x1 - x0, color)
if self.xdivs > 0:
width = x1 - x0
dx = width / (self.xdivs) # X grid line
for line in range(self.xdivs + 1):
color = self.fgcolor if line == self.xorigin else self.gridcolor
xpos = round(x0 + dx * line)
ssd.vline(xpos, y0, y1 - y0, color)
# Called by Curve
def line(self, start, end, color): # start and end relative to origin and scaled -1 .. 0 .. +1
xs = round(self.xp_origin + start[0] * self.x_axis_len)
ys = round(self.yp_origin - start[1] * self.y_axis_len)
xe = round(self.xp_origin + end[0] * self.x_axis_len)
ye = round(self.yp_origin - end[1] * self.y_axis_len)
self.device.line(xs, ys, xe, ye, color)
class PolarGraph(Graph):
def __init__(self, writer, row, col, *, height=90, fgcolor=None, bgcolor=None, bdcolor=None,
gridcolor=None, adivs=3, rdivs=4):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor, gridcolor)
self.adivs = adivs * 2 # No. of divisions of Pi radians
self.rdivs = rdivs
self.radius = round(height / 2) # Unit: pixels
self.xp_origin = self.x0 + self.radius # Origin in pixels
self.yp_origin = self.y0 + self.radius
self.show()
def show(self):
super().show() # Clear working area
ssd = self.device
x0 = self.x0
y0 = self.y0
radius = self.radius
adivs = self.adivs
rdivs = self.rdivs
diam = 2 * radius
if rdivs > 0:
for r in range(1, rdivs + 1):
circle(ssd, self.xp_origin, self.yp_origin, round(radius * r / rdivs), self.gridcolor)
if adivs > 0:
v = complex(1)
m = rect(1, pi / adivs)
for _ in range(adivs):
self.cline(-v, v, self.gridcolor)
v *= m
ssd.vline(x0 + radius, y0, diam, self.fgcolor)
ssd.hline(x0, y0 + radius, diam, self.fgcolor)
def cline(self, start, end, color): # start and end are complex, 0 <= magnitude <= 1
height = self.radius # Unit: pixels
xs = round(self.xp_origin + start.real * height)
ys = round(self.yp_origin - start.imag * height)
xe = round(self.xp_origin + end.real * height)
ye = round(self.yp_origin - end.imag * height)
self.device.line(xs, ys, xe, ye, color)

214
plot/fpt.py 100644
Wyświetl plik

@ -0,0 +1,214 @@
# fpt.py Test/demo program for framebuf plot
# 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
# The MIT License (MIT)
# Copyright (c) 2018 Peter Hinch
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# WIRING (Adafruit pin nos and names)
# Pyb SSD
# 3v3 Vin (10)
# Gnd Gnd (11)
# X1 DC (3 DC)
# X2 CS (5 OC OLEDCS)
# X3 Rst (4 R RESET)
# X6 CLK (2 CL SCK)
# X8 DATA (1 SI MOSI)
height = 96 # 1.27 inch 96*128 (rows*cols) display
# height = 128 # 1.5 inch 128*128 display
import machine
import gc
from ssd1351 import SSD1351 as SSD
# Initialise hardware and framebuf before importing modules
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)
gc.collect() # Precaution befor instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
import cmath
import math
import utime
import uos
from writer import Writer, CWriter
from fplot import PolarGraph, PolarCurve, CartesianGraph, Curve, TSequence
from nanogui import Label, refresh
refresh(ssd)
# Fonts
import arial10, freesans20
GREEN = SSD.rgb(0, 255, 0)
RED = SSD.rgb(255, 0, 0)
BLUE = SSD.rgb(0, 0, 255)
YELLOW = SSD.rgb(255, 255, 0)
WHITE = SSD.rgb(255, 255, 255)
BLACK = 0
LIGHTGREEN = SSD.rgb(0, 100, 0)
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)
def cart():
print('Cartesian data test.')
def populate_1(func):
x = -1
while x < 1.01:
yield x, func(x) # x, y
x += 0.1
def populate_2():
x = -1
while x < 1.01:
yield x, x**2 # x, y
x += 0.1
refresh(ssd, True) # Clear any prior image
g = CartesianGraph(wri, 2, 2, yorigin = 2, fgcolor=WHITE, gridcolor=LIGHTGREEN) # Asymmetric y axis
curve1 = Curve(g, YELLOW, populate_1(lambda x : x**3 + x**2 -x,)) # args demo
curve2 = Curve(g, RED, populate_2())
refresh(ssd)
def polar():
print('Polar data test.')
def populate():
def f(theta):
return cmath.rect(math.sin(3 * theta), theta) # complex
nmax = 150
for n in range(nmax + 1):
yield f(2 * cmath.pi * n / nmax) # complex z
refresh(ssd, True) # Clear any prior image
g = PolarGraph(wri, 2, 2, fgcolor=WHITE, gridcolor=LIGHTGREEN)
curve = PolarCurve(g, YELLOW, populate())
refresh(ssd)
def polar_clip():
print('Test of polar data clipping.')
def populate(rot):
f = lambda theta : cmath.rect(1.15 * math.sin(5 * theta), theta) * rot # complex
nmax = 150
for n in range(nmax + 1):
yield f(2 * cmath.pi * n / nmax) # complex z
refresh(ssd, True) # Clear any prior image
g = PolarGraph(wri, 2, 2, fgcolor=WHITE, gridcolor=LIGHTGREEN)
curve = PolarCurve(g, YELLOW, populate(1))
curve1 = PolarCurve(g, RED, populate(cmath.rect(1, cmath.pi/5),))
refresh(ssd)
def rt_polar():
print('Simulate realtime polar data acquisition.')
refresh(ssd, True) # Clear any prior image
g = PolarGraph(wri, 2, 2, fgcolor=WHITE, gridcolor=LIGHTGREEN)
curvey = PolarCurve(g, YELLOW)
curver = PolarCurve(g, RED)
for x in range(100):
curvey.point(cmath.rect(x/100, -x * cmath.pi/30))
curver.point(cmath.rect((100 - x)/100, -x * cmath.pi/30))
utime.sleep_ms(60)
refresh(ssd)
def rt_rect():
print('Simulate realtime data acquisition of discontinuous data.')
refresh(ssd, True) # Clear any prior image
g = CartesianGraph(wri, 2, 2, fgcolor=WHITE, gridcolor=LIGHTGREEN)
curve = Curve(g, RED)
x = -1
for _ in range(40):
y = 0.1/x if abs(x) > 0.05 else None # Discontinuity
curve.point(x, y)
utime.sleep_ms(100)
refresh(ssd)
x += 0.05
g.clear()
curve = Curve(g, YELLOW)
x = -1
for _ in range(40):
y = -0.1/x if abs(x) > 0.05 else None # Discontinuity
curve.point(x, y)
utime.sleep_ms(100)
refresh(ssd)
x += 0.05
def lem():
print('Lemniscate of Bernoulli.')
def populate():
t = -math.pi
while t <= math.pi + 0.1:
x = 0.5*math.sqrt(2)*math.cos(t)/(math.sin(t)**2 + 1)
y = math.sqrt(2)*math.cos(t)*math.sin(t)/(math.sin(t)**2 + 1)
yield x, y
t += 0.1
refresh(ssd, True) # Clear any prior image
Label(wri, 82, 2, 'To infinity and beyond...')
g = CartesianGraph(wri, 2, 2, height = 75, fgcolor=WHITE, gridcolor=LIGHTGREEN)
curve = Curve(g, YELLOW, populate())
refresh(ssd)
def liss():
print('Lissajous figure.')
def populate():
t = -math.pi
while t <= math.pi:
yield math.sin(t), math.cos(3*t) # x, y
t += 0.1
refresh(ssd, True) # Clear any prior image
g = CartesianGraph(wri, 2, 2, fgcolor=WHITE, gridcolor=LIGHTGREEN)
curve = Curve(g, YELLOW, populate())
refresh(ssd)
def seq():
print('Time sequence test - sine and cosine.')
refresh(ssd, True) # Clear any prior image
# y axis at t==now, no border
g = CartesianGraph(wri, 2, 2, xorigin = 10, fgcolor=WHITE,
gridcolor=LIGHTGREEN, bdcolor=False)
tsy = TSequence(g, YELLOW, 50)
tsr = TSequence(g, RED, 50)
for t in range(100):
g.clear()
tsy.add(0.9*math.sin(t/10))
tsr.add(0.4*math.cos(t/10))
refresh(ssd)
utime.sleep_ms(100)
seq()
utime.sleep(1.5)
liss()
utime.sleep(1.5)
rt_rect()
utime.sleep(1.5)
rt_polar()
utime.sleep(1.5)
polar()
utime.sleep(1.5)
cart()
utime.sleep(1.5)
polar_clip()
utime.sleep(1.5)
lem()