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
|
I have tested on the first of these. However the
|
||||||
[Adfruit driver](https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay)
|
[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
|
# 1. Display characteristics
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ in comparison to stop mode and battery powered applications should be easily
|
||||||
realised.
|
realised.
|
||||||
|
|
||||||
The 2.7" display has excellent resolution and can display fine lines and small
|
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
|
fonts. In other respects the display quality is not as good as ePaper. For good
|
||||||
best results are achieved if the viewing angle and the direction of the light
|
contrast best results are achieved if the viewing angle and the direction of
|
||||||
source are positioned to achieve reflection.
|
the light source are positioned to achieve reflection.
|
||||||
|
|
||||||
## 1.1 The VCOM bit
|
## 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
|
control, ensuring that the driver's `show` method is called at regular
|
||||||
intervals of at least 1Hz.
|
intervals of at least 1Hz.
|
||||||
|
|
||||||
|
## 1.2 Refresh rate
|
||||||
|
|
||||||
|
The datasheet specifies a minimum refresh rate of 1Hz.
|
||||||
|
|
||||||
# 2. Test scripts
|
# 2. Test scripts
|
||||||
|
|
||||||
1. `sharptest.py` Basic functionality test.
|
1. `sharptest.py` Basic functionality test.
|
||||||
2. `clocktest.py` Digital and analog clock display.
|
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
|
`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.
|
the display. It tests `writer.py` and some `framebuffer` graphics primitives.
|
||||||
`clocktest` tests `nanogui.py`.
|
`clocktest` tests `nanogui.py`.
|
||||||
|
|
||||||
To run the tests the fonts in the directory, `writer.py` and `nanogui.py` must
|
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
|
be copied to the device or frozen as bytecode. The `clack_batt.py` demo needs
|
||||||
SF6W: frozen bytecode was not required. I suspect a Pyboard 1.x would require
|
`upower.py` from
|
||||||
it to prevent memory errors.
|
[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)
|
[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
|
# clocktest.py Test/demo program for Adafruit sharp 2.7" display
|
||||||
|
|
||||||
# Copyright (c) 2020 Peter Hinch
|
# Copyright (c) 2020 Peter Hinch
|
||||||
|
# Released under the MIT license. See LICENSE
|
||||||
|
|
||||||
# The MIT License (MIT)
|
# WIRING
|
||||||
|
|
||||||
# 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
|
|
||||||
# Pyb SSD
|
# Pyb SSD
|
||||||
# 3v3 Vin
|
# Vin Vin Pyboard: Vin is an output when powered by USB
|
||||||
# Gnd Gnd
|
# Gnd Gnd
|
||||||
# X1 DC
|
# Y8 DI
|
||||||
# X2 CS
|
# Y6 CLK
|
||||||
# X3 Rst
|
# Y5 CS
|
||||||
# X6 CLK
|
|
||||||
# X8 DATA
|
|
||||||
|
|
||||||
|
|
||||||
# Demo of initialisation procedure designed to minimise risk of memory fail
|
# 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)
|
spi = machine.SPI(2)
|
||||||
gc.collect() # Precaution before instantiating framebuf
|
gc.collect() # Precaution before instantiating framebuf
|
||||||
ssd = SSD(spi, pcs)
|
ssd = SSD(spi, pcs)
|
||||||
from nanogui import Dial, Pointer, refresh, Label
|
|
||||||
refresh(ssd) # Initialise and clear display.
|
|
||||||
|
|
||||||
# Now import other modules
|
# Now import other modules
|
||||||
|
from nanogui import Dial, Pointer, refresh, Label
|
||||||
import cmath
|
import cmath
|
||||||
import utime
|
import utime
|
||||||
from writer import Writer
|
from writer import Writer
|
||||||
|
|
||||||
# Font for Writer
|
# Fonts for Writer
|
||||||
import freesans20 as font_small
|
import freesans20 as font_small
|
||||||
import arial35 as font_large
|
import arial35 as font_large
|
||||||
|
|
||||||
|
refresh(ssd) # Initialise display.
|
||||||
|
|
||||||
def aclock():
|
def aclock():
|
||||||
uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi
|
uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi
|
||||||
pi = cmath.pi
|
pi = cmath.pi
|
||||||
|
@ -83,7 +64,9 @@ def aclock():
|
||||||
sstart = 0 + 0.92j
|
sstart = 0 + 0.92j
|
||||||
while True:
|
while True:
|
||||||
t = utime.localtime()
|
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))
|
mins.value(mstart * uv(-t[4] * pi/30))
|
||||||
secs.value(sstart * uv(-t[5] * pi/30))
|
secs.value(sstart * uv(-t[5] * pi/30))
|
||||||
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
|
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
|
||||||
|
|
|
@ -24,22 +24,22 @@ _VCOM = const(2)
|
||||||
|
|
||||||
class SHARP(framebuf.FrameBuffer):
|
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
|
spi.init(baudrate=2_000_000, firstbit=machine.SPI.LSB) # Data sheet: should support 2MHz
|
||||||
self._spi = spi
|
self._spi = spi
|
||||||
self._pincs = pincs
|
self._pincs = pincs
|
||||||
self.height = 240 # Required by Writer class and nanogui
|
self.height = height # Required by Writer class and nanogui
|
||||||
self.width = 400
|
self.width = width
|
||||||
self._buffer = bytearray(self.height * self.width // 8)
|
self._buffer = bytearray(self.height * self.width // 8)
|
||||||
self._mvb = memoryview(self._buffer)
|
self._mvb = memoryview(self._buffer)
|
||||||
super().__init__(self._buffer, self.width, self.height, framebuf.MONO_HMSB)
|
super().__init__(self._buffer, self.width, self.height, framebuf.MONO_HMSB)
|
||||||
self._cmd = bytearray(1) # Buffer for command. Holds current VCOM bit
|
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._lno = bytearray(1) # Line no.
|
||||||
self._dummy = bytearray(1) # Dummy (0)
|
self._dummy = bytearray(1) # Dummy (0)
|
||||||
|
|
||||||
# .show should be called periodically to avoid frame inversion flag
|
# .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):
|
def show(self):
|
||||||
spi = self._spi
|
spi = self._spi
|
||||||
bpl = self.width // 8 # Bytes per line
|
bpl = self.width // 8 # Bytes per line
|
||||||
|
@ -57,3 +57,11 @@ class SHARP(framebuf.FrameBuffer):
|
||||||
spi.write(self._dummy)
|
spi.write(self._dummy)
|
||||||
self._pincs(0)
|
self._pincs(0)
|
||||||
self._cmd[0] ^= _VCOM # Toggle frame inversion flag
|
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
|
# Copyright (c) Peter Hinch 2020
|
||||||
# Released under the MIT license see LICENSE
|
# 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
|
import machine
|
||||||
from sharp import SHARP
|
from sharp import SHARP
|
||||||
|
|
Ładowanie…
Reference in New Issue