sharp: now supports 1.3 inch display and .update(). Add clock_batt.py.

ili9341
Peter Hinch 2020-10-06 09:48:47 +01:00
rodzic c3f444dadb
commit c5e7fbb5e0
5 zmienionych plików z 176 dodań i 44 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -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]))

Wyświetl plik

@ -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)

Wyświetl plik

@ -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