Temporary commit

ili9341
Peter Hinch 2020-11-03 14:27:34 +00:00
rodzic daba21f5a6
commit dfe912712f
19 zmienionych plików z 372 dodań i 31 usunięć

Wyświetl plik

@ -17,12 +17,12 @@ dynamically with low values showing green, intermediate yellow and high red.
The alevel.py demo. The Pyboard was mounted vertically: the length and angle
of the vector arrow varies as the Pyboard is moved.
There is an optional [graph plotting module](./plot/FPLOT.md) for basic
There is an optional [graph plotting module](./FPLOT.md) for basic
Cartesian and polar plots, also realtime plotting including time series.
![Image](plot/images/sine.png) A sample image from the plot module.
![Image](images/sine.png) A sample image from the plot module.
Notes on [Adafruit and other OLED displays](./drivers/ADAFRUIT.md) including
Notes on [Adafruit and other OLED displays](./ADAFRUIT.md) including
wiring details, pin names and hardware issues.
# Contents
@ -44,8 +44,12 @@ wiring details, pin names and hardware issues.
# 1. Introduction
This library has been refactored as a Python package. The aim is to reduce RAM
usage: widgets are imported on demand rather than unconditionally. This enabled
the addition of new widgets with zero impact on existsing applications.
This library provides a limited set of GUI objects (widgets) for displays whose
display driver is subclassed from the `framebuf` class. Examples are:
display driver is subclassed from the `framebuf` class. Display drivers include:
* The official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
* The [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git).
@ -62,11 +66,14 @@ 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.
GUI's for the official lcd160cr, for RA8875 based displays or for SSD1963 based
displays.
Widgets are drawn using graphics primitives rather than icons to minimise RAM
usage. It also enables them to be effciently rendered at arbitrary scale on
devices with restricted processing power.
devices with restricted processing power. The approach also enables widgets to
provide information in ways that are difficult with icons, in particular using
dynamic color changes in conjunction with moving elements.
Owing to RAM requirements and limitations on communication speed, `framebuf`
based display drivers are intended for physically small displays with limited
@ -86,9 +93,31 @@ demos illustrate this.
# 2. Files and Dependencies
In general installation comprises copying the `ngui` and `drivers` directories,
with their contents, to the target hardware
## 2.1 Files
### 2.1.1 Core files
The `ngui/core` directory contains the GUI core and its principal dependencies:
* `nanogui.py` The library.
* `writer.py` Module for rendering Python fonts.
* `fplot.py` The graph plotting module.
* `ssd1306_setup.py` Applications using an SSD1306 monochrome OLED display
import this file to determine hardware initialisation. On non Pyboard targets
this will require adaptation to match the hardware connections.
* `framebuf_utils.mpy` Accelerator for the `CWriter` class. This optional file
is compiled for STM hardware and will be ignored on other ports. Instructions
and code for compiling for other architectures may be found
[here](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost).
### 2.1.2 Demo scripts
The `ngui/demos` directory contains test/demo scripts. In general these will
need minor adaptation to match your display hardware.
* `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.
@ -100,13 +129,19 @@ 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):
### 2.1.3 Fonts
Python font files are in the root directory. This facilitates freezing them to
conserve RAM. Python fonts may be created using
[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git).
Supplied examples are:
* `arial10.py`
* `courier20.py`
* `font6.py`
* `freesans20.py`
Demos showing the use of `nanogui` with `uasyncio` may be found [here](./async/ASYNC.md).
Demos showing the use of `nanogui` with `uasyncio` may be found [here](./ASYNC.md).
## 2.2 Dependencies

Wyświetl plik

Wyświetl plik

Wyświetl plik

Wyświetl plik

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,5 +1,5 @@
# nanogui.py Displayable objects based on the Writer and CWriter classes
# V0.3 Peter Hinch 26th Aug 2018
# V0.4 Peter Hinch 1st Nov 2020
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch
@ -9,7 +9,7 @@
# border: False no border None use bgcolor, int: treat as color
import cmath
from gui.core.writer import Writer
from ngui.core.writer import Writer
import framebuf
import gc

323
ngui/core/writer.py 100644
Wyświetl plik

@ -0,0 +1,323 @@
# writer.py Implements the Writer class.
# V0.35 Peter Hinch Sept 2020 Fast rendering option for color displays
# Handles colour, upside down diplays, word wrap and tab stops
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2020 Peter Hinch
# A Writer supports rendering text to a Display instance in a given font.
# Multiple Writer instances may be created, each rendering a font to the
# same Display object.
# Timings based on a 20 pixel high proportional font, run on a pyboard V1.0.
# Using CWriter's slow rendering: _printchar 9.5ms typ, 13.5ms max.
# Using Writer's fast rendering: _printchar 115μs min 480μs typ 950μs max.
# CWriter on Pyboard D SF2W at standard clock rate
# Fast method 500-600μs typical, up to 1.07ms on larger fonts
# Revised fast method 691μs avg, up to 2.14ms on larger fonts
# Slow method 2700μs typical, up to 11ms on larger fonts
import framebuf
fast_mode = False
try:
from framebuf_utils import render
fast_mode = True
except ImportError:
pass
except ValueError:
print('Ignoring framebuf_utils.mpy: it was compiled for an incorrect architecture.')
from uctypes import bytearray_at, addressof
class DisplayState():
def __init__(self):
self.text_row = 0
self.text_col = 0
self.usd = False
def _get_id(device):
if not isinstance(device, framebuf.FrameBuffer):
raise ValueError('Device must be derived from FrameBuffer.')
return id(device)
# Basic Writer class for monochrome displays
class Writer():
state = {} # Holds a display state for each device
@staticmethod
def set_textpos(device, row=None, col=None):
devid = _get_id(device)
if devid not in Writer.state:
Writer.state[devid] = DisplayState()
s = Writer.state[devid] # Current state
if row is not None:
if row < 0 or row >= device.height:
raise ValueError('row is out of range')
s.text_row = device.height - 1 - row if s.usd else row
if col is not None:
if col < 0 or col >= device.width:
raise ValueError('col is out of range')
s.text_col = device.width -1 - col if s.usd else col
return s.text_row, s.text_col
def __init__(self, device, font, verbose=True):
self.devid = _get_id(device)
self.device = device
if self.devid not in Writer.state:
Writer.state[self.devid] = DisplayState()
self.font = font
self.usd = Writer.state[self.devid].usd
# Allow to work with reverse or normal font mapping
if font.hmap():
self.map = framebuf.MONO_HMSB if font.reverse() else framebuf.MONO_HLSB
else:
raise ValueError('Font must be horizontally mapped.')
if verbose:
fstr = 'Orientation: Horizontal. Reversal: {}. Width: {}. Height: {}.'
print(fstr.format(font.reverse(), device.width, device.height))
print('Start row = {} col = {}'.format(self._getstate().text_row, self._getstate().text_col))
self.screenwidth = device.width # In pixels
self.screenheight = device.height
self.bgcolor = 0 # Monochrome background and foreground colors
self.fgcolor = 1
self.row_clip = False # Clip or scroll when screen full
self.col_clip = False # Clip or new line when row is full
self.wrap = True # Word wrap
self.cpos = 0
self.tab = 4
self.glyph = None # Current char
self.char_height = 0
self.char_width = 0
def _getstate(self):
return Writer.state[self.devid]
def _newline(self):
s = self._getstate()
height = self.font.height()
if self.usd:
s.text_row -= height
s.text_col = self.screenwidth - 1
margin = s.text_row - height
y = 0
else:
s.text_row += height
s.text_col = 0
margin = self.screenheight - (s.text_row + height)
y = self.screenheight + margin
if margin < 0:
if not self.row_clip:
if self.usd:
margin = -margin
self.device.scroll(0, margin)
self.device.fill_rect(0, y, self.screenwidth, abs(margin), self.bgcolor)
s.text_row += margin
def set_clip(self, row_clip=None, col_clip=None, wrap=None):
if row_clip is not None:
self.row_clip = row_clip
if col_clip is not None:
self.col_clip = col_clip
if wrap is not None:
self.wrap = wrap
return self.row_clip, self.col_clip, self.wrap
@property
def height(self): # Property for consistency with device
return self.font.height()
def printstring(self, string, invert=False):
# word wrapping. Assumes words separated by single space.
while True:
lines = string.split('\n', 1)
s = lines[0]
if s:
self._printline(s, invert)
if len(lines) == 1:
break
else:
self._printchar('\n')
string = lines[1]
def _printline(self, string, invert):
rstr = None
if self.wrap and self.stringlen(string) > self.screenwidth:
pos = 0
lstr = string[:]
while self.stringlen(lstr) > self.screenwidth:
pos = lstr.rfind(' ')
lstr = lstr[:pos].rstrip()
if pos > 0:
rstr = string[pos + 1:]
string = lstr
for char in string:
self._printchar(char, invert)
if rstr is not None:
self._printchar('\n')
self._printline(rstr, invert) # Recurse
def stringlen(self, string):
l = 0
for char in string:
l += self._charlen(char)
return l
def _charlen(self, char):
if char == '\n':
char_width = 0
else:
_, _, char_width = self.font.get_ch(char)
return char_width
def _get_char(self, char, recurse):
if not recurse: # Handle tabs
if char == '\n':
self.cpos = 0
elif char == '\t':
nspaces = self.tab - (self.cpos % self.tab)
if nspaces == 0:
nspaces = self.tab
while nspaces:
nspaces -= 1
self._printchar(' ', recurse=True)
self.glyph = None # All done
return
self.glyph = None # Assume all done
if char == '\n':
self._newline()
return
glyph, char_height, char_width = self.font.get_ch(char)
s = self._getstate()
if self.usd:
if s.text_row - char_height < 0:
if self.row_clip:
return
self._newline()
if s.text_col - char_width < 0:
if self.col_clip:
return
else:
self._newline()
else:
if s.text_row + char_height > self.screenheight:
if self.row_clip:
return
self._newline()
if s.text_col + char_width > self.screenwidth:
if self.col_clip:
return
else:
self._newline()
self.glyph = glyph
self.char_height = char_height
self.char_width = char_width
# Method using blitting. Efficient rendering for monochrome displays.
# Tested on SSD1306. Invert is for black-on-white rendering.
def _printchar(self, char, invert=False, recurse=False):
s = self._getstate()
self._get_char(char, recurse)
if self.glyph is None:
return # All done
buf = bytearray(self.glyph)
if invert:
for i, v in enumerate(buf):
buf[i] = 0xFF & ~ v
fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map)
self.device.blit(fbc, s.text_col, s.text_row)
s.text_col += self.char_width
self.cpos += 1
def tabsize(self, value=None):
if value is not None:
self.tab = value
return self.tab
def setcolor(self, *_):
return self.fgcolor, self.bgcolor
# Writer for colour displays or upside down rendering
class CWriter(Writer):
@staticmethod
def invert_display(device, value=True):
devid = id(device)
if devid not in Writer.state:
Writer.state[devid] = DisplayState()
Writer.state[devid].usd = value
def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True):
super().__init__(device, font, verbose)
if bgcolor is not None: # Assume monochrome.
self.bgcolor = bgcolor
if fgcolor is not None:
self.fgcolor = fgcolor
self.def_bgcolor = self.bgcolor
self.def_fgcolor = self.fgcolor
fm = fast_mode and not self.usd
self._printchar = self._pchfast if fm else self._pchslow
verbose and print('Render {} using fast mode'.format('is' if fm else 'not'))
def _pchfast(self, char, invert=False, recurse=False):
s = self._getstate()
self._get_char(char, recurse)
if self.glyph is None:
return # All done
buf = bytearray_at(addressof(self.glyph), len(self.glyph))
fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map)
fgcolor = self.bgcolor if invert else self.fgcolor
bgcolor = self.fgcolor if invert else self.bgcolor
render(self.device, fbc, s.text_col, s.text_row, fgcolor, bgcolor)
s.text_col += self.char_width
self.cpos += 1
def _pchslow(self, char, invert=False, recurse=False):
s = self._getstate()
self._get_char(char, recurse)
if self.glyph is None:
return # All done
char_height = self.char_height
char_width = self.char_width
div, mod = divmod(char_width, 8)
gbytes = div + 1 if mod else div # No. of bytes per row of glyph
device = self.device
fgcolor = self.bgcolor if invert else self.fgcolor
bgcolor = self.fgcolor if invert else self.bgcolor
usd = self.usd
drow = s.text_row # Destination row
wcol = s.text_col # Destination column of character start
for srow in range(char_height): # Source row
for scol in range(char_width): # Source column
# Destination column: add/subtract writer column
if usd:
dcol = wcol - scol
else:
dcol = wcol + scol
gbyte, gbit = divmod(scol, 8)
if gbit == 0: # Next glyph byte
data = self.glyph[srow * gbytes + gbyte]
pixel = fgcolor if data & (1 << (7 - gbit)) else bgcolor
device.pixel(dcol, drow, pixel)
drow += -1 if usd else 1
if drow >= self.screenheight or drow < 0:
break
s.text_col += -char_width if usd else char_width
self.cpos += 1
def setcolor(self, fgcolor=None, bgcolor=None):
if fgcolor is None and bgcolor is None:
self.fgcolor = self.def_fgcolor
self.bgcolor = self.def_bgcolor
else:
if fgcolor is not None:
self.fgcolor = fgcolor
if bgcolor is not None:
self.bgcolor = bgcolor
return self.fgcolor, self.bgcolor

Wyświetl plik

Wyświetl plik

@ -4,25 +4,8 @@
# 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.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch
# WIRING
# Pyb SSD
@ -43,7 +26,7 @@ height = 96 # 1.27 inch 96*128 (rows*cols) display
import machine
import gc
from ssd1351 import SSD1351 as SSD
from drivers.ssd1351 import SSD1351 as SSD
# Initialise hardware
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
@ -58,7 +41,7 @@ refresh(ssd) # Initialise and clear display.
# Now import other modules
import cmath
import utime
from writer import CWriter
from ngui.core.writer import CWriter
# Font for CWriter
import arial10