kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
sharp: now supports 1.3 inch display and .update(). Add clock_batt.py.
rodzic
c3f444dadb
commit
c5e7fbb5e0
|
@ -7,7 +7,7 @@ These monochrome SPI displays exist in three variants from Adafruit.
|
|||
|
||||
I have tested on the first of these. However the
|
||||
[Adfruit driver](https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay)
|
||||
supports all of these and I would expect this one to do so.
|
||||
supports all of these and I would expect this one also to do so.
|
||||
|
||||
# 1. Display characteristics
|
||||
|
||||
|
@ -24,9 +24,9 @@ in comparison to stop mode and battery powered applications should be easily
|
|||
realised.
|
||||
|
||||
The 2.7" display has excellent resolution and can display fine lines and small
|
||||
fonts. However the display quality is not as good as ePaper. For good contrast
|
||||
best results are achieved if the viewing angle and the direction of the light
|
||||
source are positioned to achieve reflection.
|
||||
fonts. In other respects the display quality is not as good as ePaper. For good
|
||||
contrast best results are achieved if the viewing angle and the direction of
|
||||
the light source are positioned to achieve reflection.
|
||||
|
||||
## 1.1 The VCOM bit
|
||||
|
||||
|
@ -63,21 +63,68 @@ In my opinion the easiest way to deal with this is usually to use software
|
|||
control, ensuring that the driver's `show` method is called at regular
|
||||
intervals of at least 1Hz.
|
||||
|
||||
## 1.2 Refresh rate
|
||||
|
||||
The datasheet specifies a minimum refresh rate of 1Hz.
|
||||
|
||||
# 2. Test scripts
|
||||
|
||||
1. `sharptest.py` Basic functionality test.
|
||||
2. `clocktest.py` Digital and analog clock display.
|
||||
3. `clock_batt.py` As above but designed for low power operation.
|
||||
|
||||
`sharptest` should not be run for long periods as it does not regularly refresh
|
||||
the display. It tests `writer.py` and some `framebuffer` graphics primitives.
|
||||
`clocktest` tests `nanogui.py`.
|
||||
|
||||
To run the tests the fonts in the directory, `writer.py` and `nanogui.py` must
|
||||
be copied to the device or frozen as bytecode. Testing was done on a Pyboard D
|
||||
SF6W: frozen bytecode was not required. I suspect a Pyboard 1.x would require
|
||||
it to prevent memory errors.
|
||||
be copied to the device or frozen as bytecode. The `clack_batt.py` demo needs
|
||||
`upower.py` from
|
||||
[micropython-micropower](https://github.com/peterhinch/micropython-micropower).
|
||||
|
||||
# 3. Resources
|
||||
Testing was done on a Pyboard D SF6W: frozen bytecode was not required. I
|
||||
suspect a Pyboard 1.x would require it to prevent memory errors.
|
||||
|
||||
# 3. Device driver constructor
|
||||
|
||||
Positional args:
|
||||
1. `spi` An SPI bus instance. The constructor initialises this to the baudrate
|
||||
and bit order required by the hardware.
|
||||
2. `pincs` A `Pin` instance. The caller should initialise this as an output
|
||||
with value 0 (unusually the hardware CS line is active high).
|
||||
3. `height=240` Dimensions in pixels. Defaults are for 2.7" display.
|
||||
4. `width=400`
|
||||
5. `vcom=False` Accept the default unless using `pyb.standby`. See 3.2.
|
||||
|
||||
# 3.1 Device driver methods
|
||||
|
||||
1. `show` No args. Transfers the framebuffer contents to the device, updating
|
||||
the display.
|
||||
2. `update` Toggles the `VCOM` bit without transferring the framebuffer. This
|
||||
is a power saving method for cases where the application calls `show` at a
|
||||
rate of < 1Hz. In such cases `update` should be called at a 1Hz rate.
|
||||
|
||||
# 3.2 The vcom arg
|
||||
|
||||
It purpose is to support micropower applications which use `pyb.standby`.
|
||||
Wakeup from standby is similar to a reboot in that program execution starts
|
||||
from scratch. In the case where the board wakes up, writes to the display, and
|
||||
returns to standby, the `VCOM` bit would never change. In this case the
|
||||
application should store a `bool` in peristent storage, toggling it on each
|
||||
restart, and pass that to the constructor.
|
||||
|
||||
Persistent storage exists in the RTC registers and backup RAM. See
|
||||
[micopython-micropower](https://github.com/peterhinch/micropython-micropower)
|
||||
for details of how to acces these resources.
|
||||
|
||||
# 4. Application design
|
||||
|
||||
In all cases the frame buffer is located on the target hardware. In the case of
|
||||
the 2.7 inch display this is 400*240//8 = 12000 bytes in size. This should be
|
||||
instantiated as soon as possible in the application to ensure that sufficient
|
||||
contiguous RAM is available.
|
||||
|
||||
# 5. Resources
|
||||
|
||||
[Schematic for 2.7" unit](https://learn.adafruit.com/assets/94077)
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
# clock_batt.py Battery powered clock demo for Pyboard/Adafruit sharp 2.7" display
|
||||
|
||||
# Copyright (c) 2020 Peter Hinch
|
||||
# Released under the MIT license. See LICENSE
|
||||
|
||||
# HARDWARE
|
||||
# This assumes a Pybaord D in WBUS-DIP28 and powered by a LiPo cell
|
||||
# WIRING
|
||||
# Pyb SSD
|
||||
# Vin Vin Pyboard D: Vin on DIP28 is an output when powered by LiPo
|
||||
# Gnd Gnd
|
||||
# Y8 DI
|
||||
# Y6 CLK
|
||||
# Y5 CS
|
||||
|
||||
|
||||
# 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 sharp import SHARP as SSD
|
||||
|
||||
# Initialise hardware
|
||||
pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high
|
||||
spi = machine.SPI(2)
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
ssd = SSD(spi, pcs)
|
||||
|
||||
# Now import other modules
|
||||
import upower
|
||||
from nanogui import Dial, Pointer, refresh, Label
|
||||
import pyb
|
||||
import cmath
|
||||
from writer import Writer
|
||||
|
||||
# Fonts for Writer
|
||||
import freesans20 as font_small
|
||||
import arial35 as font_large
|
||||
|
||||
refresh(ssd) # Initialise display.
|
||||
|
||||
def aclock():
|
||||
rtc = pyb.RTC()
|
||||
uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi
|
||||
pi = cmath.pi
|
||||
days = ('Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun')
|
||||
months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',
|
||||
'Aug', 'Sept', 'Oct', 'Nov', 'Dec')
|
||||
# Instantiate Writer
|
||||
Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it
|
||||
wri = Writer(ssd, font_small, verbose=False)
|
||||
wri.set_clip(True, True, False)
|
||||
wri_tim = Writer(ssd, font_large, verbose=False)
|
||||
wri_tim.set_clip(True, True, False)
|
||||
|
||||
# Instantiate displayable objects
|
||||
dial = Dial(wri, 2, 2, height = 215, ticks = 12, bdcolor=None, pip=True)
|
||||
lbltim = Label(wri_tim, 50, 230, '00.00.00')
|
||||
lbldat = Label(wri, 100, 230, 100)
|
||||
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 = rtc.datetime() # (year, month, day, weekday, hours, minutes, seconds, subseconds)
|
||||
hang = -t[4]*pi/6 - t[5]*pi/360 # Angles of hour and minute hands
|
||||
mang = -t[5] * pi/30
|
||||
sang = -t[6] * pi/30
|
||||
if abs(hang - mang) < pi/360: # Avoid overlap of hands
|
||||
hang += pi/18
|
||||
hrs.value(hstart * uv(hang))
|
||||
mins.value(mstart * uv(mang))
|
||||
secs.value(sstart * uv(sang))
|
||||
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[4], t[5], t[6]))
|
||||
lbldat.value('{} {} {} {}'.format(days[t[3] - 1], t[2], months[t[1] - 1], t[0]))
|
||||
refresh(ssd)
|
||||
# Power saving: only refresh every 10s
|
||||
for _ in range(10):
|
||||
upower.lpdelay(1000)
|
||||
ssd.update() # Toggle VCOM
|
||||
|
||||
aclock()
|
|
@ -1,36 +1,15 @@
|
|||
# clocktest.py Test/demo program for Adafruit sharp 2.7" display
|
||||
|
||||
# Copyright (c) 2020 Peter Hinch
|
||||
# Released under the MIT license. See LICENSE
|
||||
|
||||
# The MIT License (MIT)
|
||||
|
||||
# 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 TODO
|
||||
# WIRING
|
||||
# Pyb SSD
|
||||
# 3v3 Vin
|
||||
# Vin Vin Pyboard: Vin is an output when powered by USB
|
||||
# Gnd Gnd
|
||||
# X1 DC
|
||||
# X2 CS
|
||||
# X3 Rst
|
||||
# X6 CLK
|
||||
# X8 DATA
|
||||
# Y8 DI
|
||||
# Y6 CLK
|
||||
# Y5 CS
|
||||
|
||||
|
||||
# Demo of initialisation procedure designed to minimise risk of memory fail
|
||||
|
@ -46,17 +25,19 @@ pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high
|
|||
spi = machine.SPI(2)
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
ssd = SSD(spi, pcs)
|
||||
from nanogui import Dial, Pointer, refresh, Label
|
||||
refresh(ssd) # Initialise and clear display.
|
||||
|
||||
# Now import other modules
|
||||
from nanogui import Dial, Pointer, refresh, Label
|
||||
import cmath
|
||||
import utime
|
||||
from writer import Writer
|
||||
|
||||
# Font for Writer
|
||||
# Fonts for Writer
|
||||
import freesans20 as font_small
|
||||
import arial35 as font_large
|
||||
|
||||
refresh(ssd) # Initialise display.
|
||||
|
||||
def aclock():
|
||||
uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi
|
||||
pi = cmath.pi
|
||||
|
@ -83,7 +64,9 @@ def aclock():
|
|||
sstart = 0 + 0.92j
|
||||
while True:
|
||||
t = utime.localtime()
|
||||
hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360))
|
||||
# Add 0.5min offset to hour hand. This avoids a confusing display by
|
||||
# ensuring hour and minute hand never exactly overlap
|
||||
hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360) - pi/720)
|
||||
mins.value(mstart * uv(-t[4] * pi/30))
|
||||
secs.value(sstart * uv(-t[5] * pi/30))
|
||||
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
|
||||
|
|
|
@ -24,22 +24,22 @@ _VCOM = const(2)
|
|||
|
||||
class SHARP(framebuf.FrameBuffer):
|
||||
|
||||
def __init__(self, spi, pincs):
|
||||
def __init__(self, spi, pincs, height=240, width=400, vcom=False):
|
||||
spi.init(baudrate=2_000_000, firstbit=machine.SPI.LSB) # Data sheet: should support 2MHz
|
||||
self._spi = spi
|
||||
self._pincs = pincs
|
||||
self.height = 240 # Required by Writer class and nanogui
|
||||
self.width = 400
|
||||
self.height = height # Required by Writer class and nanogui
|
||||
self.width = width
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
super().__init__(self._buffer, self.width, self.height, framebuf.MONO_HMSB)
|
||||
self._cmd = bytearray(1) # Buffer for command. Holds current VCOM bit
|
||||
self._cmd[0] = _WRITECMD
|
||||
self._cmd[0] = _WRITECMD | _VCOM if vcom else _WRITECMD
|
||||
self._lno = bytearray(1) # Line no.
|
||||
self._dummy = bytearray(1) # Dummy (0)
|
||||
|
||||
# .show should be called periodically to avoid frame inversion flag
|
||||
# retaining the same value for long periods
|
||||
# (VCOM) retaining the same value for long periods
|
||||
def show(self):
|
||||
spi = self._spi
|
||||
bpl = self.width // 8 # Bytes per line
|
||||
|
@ -57,3 +57,11 @@ class SHARP(framebuf.FrameBuffer):
|
|||
spi.write(self._dummy)
|
||||
self._pincs(0)
|
||||
self._cmd[0] ^= _VCOM # Toggle frame inversion flag
|
||||
|
||||
# Toggle the VCOM bit without changing the display. Power saving method.
|
||||
def update(self):
|
||||
self._pincs(1)
|
||||
self._lno[0] = self._cmd[0] & _VCOM
|
||||
self._spi.write(self._lno)
|
||||
self._cmd[0] ^= _VCOM # Toggle frame inversion flag
|
||||
self._pincs(0)
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
# Copyright (c) Peter Hinch 2020
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
# WIRING
|
||||
# Pyb SSD
|
||||
# Vin Vin Pyboard: Vin is an output when powered by USB
|
||||
# Gnd Gnd
|
||||
# Y8 DI
|
||||
# Y6 CLK
|
||||
# Y5 CS
|
||||
|
||||
import machine
|
||||
from sharp import SHARP
|
||||
|
|
Ładowanie…
Reference in New Issue