kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
epd29.py passed initial tests.
rodzic
28afb7e1e5
commit
f4c6631c91
|
@ -0,0 +1,40 @@
|
||||||
|
# epd96_demo.py Allow standard demos to run on ePaper. Customise for your hardware config
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2020 Peter Hinch
|
||||||
|
|
||||||
|
# As written, supports Adafruit 2.9" monochrome EPD with interface board.
|
||||||
|
# Interface breakout: https://www.adafruit.com/product/4224
|
||||||
|
# Display: https://www.adafruit.com/product/4262
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# WIRING. Adafruit schematic is incorrect in that it references a nonexistent
|
||||||
|
# SD card so that interface has an extra pin.
|
||||||
|
# Pyb Breakout
|
||||||
|
# Vin Vin (1)
|
||||||
|
# Gnd Gnd (3)
|
||||||
|
# Y8 MOSI (6)
|
||||||
|
# Y6 SCK (4)
|
||||||
|
# Y4 BUSY (11) (Low = Busy)
|
||||||
|
# Y3 RST (10)
|
||||||
|
# Y2 CS (7)
|
||||||
|
# Y1 DC (8)
|
||||||
|
import machine
|
||||||
|
import gc
|
||||||
|
|
||||||
|
from drivers.epaper.epd29 import EPD as SSD
|
||||||
|
|
||||||
|
pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
|
||||||
|
pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
|
||||||
|
prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
|
||||||
|
pbusy = machine.Pin('Y4', machine.Pin.IN)
|
||||||
|
|
||||||
|
# Baudrate from https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2
|
||||||
|
# Datasheet P35 indicates up to 10MHz.
|
||||||
|
spi = machine.SPI(2, baudrate=1_000_000)
|
||||||
|
gc.collect() # Precaution before instantiating framebuf
|
||||||
|
ssd = SSD(spi, pcs, pdc, prst, pbusy) # Create a display instance
|
||||||
|
ssd.demo_mode = True
|
|
@ -19,8 +19,11 @@
|
||||||
|
|
||||||
import framebuf
|
import framebuf
|
||||||
import uasyncio as asyncio
|
import uasyncio as asyncio
|
||||||
|
from micropython import const
|
||||||
from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff
|
from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff
|
||||||
|
|
||||||
|
_MAX_BLOCK = const(20) # Maximum blocking time (ms) for asynchronous show.
|
||||||
|
|
||||||
class EPD(framebuf.FrameBuffer):
|
class EPD(framebuf.FrameBuffer):
|
||||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||||
# nothing breaks if users specify colors.
|
# nothing breaks if users specify colors.
|
||||||
|
@ -34,24 +37,22 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self._dc = dc
|
self._dc = dc
|
||||||
self._rst = rst # Active low.
|
self._rst = rst # Active low.
|
||||||
self._busy = busy # Active low on IL0373
|
self._busy = busy # Active low on IL0373
|
||||||
self._lsc = True # TODO this is here for test purposes. There is only one mode.
|
|
||||||
if asyn:
|
|
||||||
self._lock = asyncio.Lock()
|
|
||||||
self._asyn = asyn
|
self._asyn = asyn
|
||||||
# ._as_busy is set immediately on start of task. Cleared
|
# ._as_busy is set immediately on start of task. Cleared
|
||||||
# when busy pin is logically false (physically 1).
|
# when busy pin is logically false (physically 1).
|
||||||
self._as_busy = False
|
self._as_busy = False
|
||||||
# Public bound variables.
|
self._updated = asyncio.Event()
|
||||||
# Ones required by nanogui.
|
# Public bound variables required by nanogui.
|
||||||
# Dimensions in pixels as seen by nanogui (landscape mode).
|
# Dimensions in pixels as seen by nanogui (landscape mode).
|
||||||
self.width = 296
|
self.width = 296
|
||||||
self.height = 128
|
self.height = 128
|
||||||
|
# Other public bound variable.
|
||||||
# Special mode enables demos written for generic displays to run.
|
# Special mode enables demos written for generic displays to run.
|
||||||
self.demo_mode = False
|
self.demo_mode = False
|
||||||
|
|
||||||
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)
|
||||||
mode = framebuf.MONO_VLSB if self._lsc else framebuf.MONO_HLSB # TODO check this and set mode permanently
|
mode = framebuf.MONO_VLSB
|
||||||
super().__init__(self._buffer, self.width, self.height, mode)
|
super().__init__(self._buffer, self.width, self.height, mode)
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ class EPD(framebuf.FrameBuffer):
|
||||||
cmd(b'\x61', b'\x80\x01\x28') # Note hex(296) == 0x128
|
cmd(b'\x61', b'\x80\x01\x28') # Note hex(296) == 0x128
|
||||||
# Set VCM_DC. 0 is datasheet default. I think Adafruit send 0x50 (-2.6V) rather than 0x12 (-1.0V)
|
# Set VCM_DC. 0 is datasheet default. I think Adafruit send 0x50 (-2.6V) rather than 0x12 (-1.0V)
|
||||||
# https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/17
|
# https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/17
|
||||||
cmd(b'\x82', b'\x12') # Set Vcom to -1.0V
|
cmd(b'\x82', b'\x12') # Set Vcom to -1.0V is my guess at Adafruit's intention.
|
||||||
sleep_ms(50)
|
sleep_ms(50)
|
||||||
print('Init Done.')
|
print('Init Done.')
|
||||||
|
|
||||||
|
@ -116,53 +117,93 @@ class EPD(framebuf.FrameBuffer):
|
||||||
dt = ticks_diff(ticks_ms(), t)
|
dt = ticks_diff(ticks_ms(), t)
|
||||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||||
|
|
||||||
# Asynchronous wait on ready state.
|
# Asynchronous wait on ready state. Pause (4.9s) for physical refresh.
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.ready():
|
while not self.ready():
|
||||||
await asyncio.sleep_ms(100)
|
await asyncio.sleep_ms(100)
|
||||||
|
|
||||||
|
# Pause until framebuf has been copied to device.
|
||||||
|
async def updated(self):
|
||||||
|
await self._updated.wait()
|
||||||
|
|
||||||
# Return immediate status. Pin state: 0 == busy.
|
# Return immediate status. Pin state: 0 == busy.
|
||||||
def ready(self):
|
def ready(self):
|
||||||
return not(self._as_busy or (self._busy() == 0))
|
return not(self._as_busy or (self._busy() == 0))
|
||||||
|
|
||||||
# draw the current frame memory.
|
async def _as_show(self, buf1=bytearray(1)):
|
||||||
def show(self, buf1=bytearray(1)):
|
|
||||||
#if self._asyn:
|
|
||||||
#self._as_busy = True
|
|
||||||
#asyncio.create_task(self._as_show())
|
|
||||||
#return
|
|
||||||
t = ticks_us()
|
|
||||||
mvb = self._mvb
|
mvb = self._mvb
|
||||||
cmd = self._command
|
cmd = self._command
|
||||||
|
dat = self._data
|
||||||
|
cmd(b'\x13')
|
||||||
|
t = ticks_ms()
|
||||||
|
wid = self.width
|
||||||
|
tbc = self.height // 8 # Vertical bytes per column
|
||||||
|
iidx = wid * (tbc - 1) # Initial index
|
||||||
|
idx = iidx # Index into framebuf
|
||||||
|
vbc = 0 # Current vertical byte count
|
||||||
|
hpc = 0 # Horizontal pixel count
|
||||||
|
for i in range(len(mvb)):
|
||||||
|
buf1[0] = mvb[idx] ^ 0xff
|
||||||
|
dat(buf1)
|
||||||
|
idx -= wid
|
||||||
|
vbc += 1
|
||||||
|
vbc %= tbc
|
||||||
|
if not vbc:
|
||||||
|
hpc += 1
|
||||||
|
idx = iidx + hpc
|
||||||
|
if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK):
|
||||||
|
await asyncio.sleep_ms(0)
|
||||||
|
t = ticks_ms()
|
||||||
|
cmd(b'\x11') # Data stop
|
||||||
|
self._updated.set()
|
||||||
|
self._updated.clear()
|
||||||
|
sleep_us(20) # Allow for data coming back: currently ignore this
|
||||||
|
cmd(b'\x12') # DISPLAY_REFRESH
|
||||||
|
# busy goes low now, for ~4.9 seconds.
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
while self._busy() == 0:
|
||||||
|
await asyncio.sleep_ms(200)
|
||||||
|
self._as_busy = False
|
||||||
|
|
||||||
|
# draw the current frame memory.
|
||||||
|
def show(self, buf1=bytearray(1)):
|
||||||
|
if self._asyn:
|
||||||
|
if self._as_busy:
|
||||||
|
raise RuntimeError('Cannot refresh: display is busy.')
|
||||||
|
self._as_busy = True # Immediate busy flag. Pin goes low much later.
|
||||||
|
asyncio.create_task(self._as_show())
|
||||||
|
return
|
||||||
|
|
||||||
|
# t = ticks_us()
|
||||||
|
mvb = self._mvb
|
||||||
|
cmd = self._command
|
||||||
|
dat = self._data
|
||||||
# DATA_START_TRANSMISSION_2 Datasheet P31 indicates this sets
|
# DATA_START_TRANSMISSION_2 Datasheet P31 indicates this sets
|
||||||
# busy pin low (True) and that it stays logically True until
|
# busy pin low (True) and that it stays logically True until
|
||||||
# refresh is complete. Probably don't need _as_busy TODO
|
# refresh is complete. In my testing this doesn't happen.
|
||||||
cmd(b'\x13')
|
cmd(b'\x13')
|
||||||
|
wid = self.width
|
||||||
if self._lsc: # Landscape mode
|
tbc = self.height // 8 # Vertical bytes per column
|
||||||
wid = self.width
|
iidx = wid * (tbc - 1) # Initial index
|
||||||
tbc = self.height // 8 # Vertical bytes per column
|
idx = iidx # Index into framebuf
|
||||||
iidx = wid * (tbc - 1) # Initial index
|
vbc = 0 # Current vertical byte count
|
||||||
idx = iidx # Index into framebuf
|
hpc = 0 # Horizontal pixel count
|
||||||
vbc = 0 # Current vertical byte count
|
for _ in range(len(mvb)):
|
||||||
hpc = 0 # Horizontal pixel count
|
buf1[0] = mvb[idx] ^ 0xff
|
||||||
for _ in range(len(mvb)):
|
dat(buf1)
|
||||||
buf1[0] = mvb[idx] ^ 0xff
|
idx -= wid
|
||||||
self._data(buf1)
|
vbc += 1
|
||||||
idx -= self.width
|
vbc %= tbc
|
||||||
vbc += 1
|
if not vbc:
|
||||||
vbc %= tbc
|
hpc += 1
|
||||||
if not vbc:
|
idx = iidx + hpc
|
||||||
hpc += 1
|
|
||||||
idx = iidx + hpc
|
|
||||||
else:
|
|
||||||
self._data(self._buffer) # TODO if this works don't need ._mvb
|
|
||||||
cmd(b'\x11') # Data stop
|
cmd(b'\x11') # Data stop
|
||||||
sleep_us(20) # Allow for data coming back: currently ignore this
|
sleep_us(20) # Allow for data coming back: currently ignore this
|
||||||
# Datasheet P14 is ambiguous over whether a refresh command is necessary here TODO
|
|
||||||
cmd(b'\x12') # DISPLAY_REFRESH
|
cmd(b'\x12') # DISPLAY_REFRESH
|
||||||
te = ticks_us()
|
# 258ms to get here on Pyboard D
|
||||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
# Checking with scope, busy goes low now. For 4.9s.
|
||||||
|
# te = ticks_us()
|
||||||
|
# print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||||
if not self.demo_mode:
|
if not self.demo_mode:
|
||||||
# Immediate return to avoid blocking the whole application.
|
# Immediate return to avoid blocking the whole application.
|
||||||
# User should wait for ready before calling refresh()
|
# User should wait for ready before calling refresh()
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
# epd29_test.py Demo program for nano_gui on an Adafruit 2.9" flexible ePaper screen
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2020 Peter Hinch
|
||||||
|
|
||||||
|
# color_setup must set landcsape False, asyn True and must not set demo_mode
|
||||||
|
import uasyncio as asyncio
|
||||||
|
from color_setup import ssd
|
||||||
|
from gui.core.writer import Writer
|
||||||
|
from gui.core.nanogui import refresh
|
||||||
|
from gui.widgets.meter import Meter
|
||||||
|
from gui.widgets.label import Label
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
import gui.fonts.arial10 as arial10
|
||||||
|
#import gui.fonts.courier20 as fixed
|
||||||
|
import gui.fonts.font6 as small
|
||||||
|
|
||||||
|
# Some ports don't support uos.urandom.
|
||||||
|
# See https://github.com/peterhinch/micropython-samples/tree/master/random
|
||||||
|
def xorshift64star(modulo, seed = 0xf9ac6ba4):
|
||||||
|
x = seed
|
||||||
|
def func():
|
||||||
|
nonlocal x
|
||||||
|
x ^= x >> 12
|
||||||
|
x ^= ((x << 25) & 0xffffffffffffffff) # modulo 2**64
|
||||||
|
x ^= x >> 27
|
||||||
|
return (x * 0x2545F4914F6CDD1D) % modulo
|
||||||
|
return func
|
||||||
|
|
||||||
|
async def fields(evt):
|
||||||
|
wri = Writer(ssd, small, verbose=False)
|
||||||
|
wri.set_clip(False, False, False)
|
||||||
|
textfield = Label(wri, 0, 2, wri.stringlen('longer'))
|
||||||
|
numfield = Label(wri, 25, 2, wri.stringlen('99.990'), bdcolor=None)
|
||||||
|
countfield = Label(wri, 0, 60, wri.stringlen('1'))
|
||||||
|
n = 1
|
||||||
|
random = xorshift64star(65535)
|
||||||
|
while True:
|
||||||
|
for s in ('short', 'longer', '1', ''):
|
||||||
|
textfield.value(s)
|
||||||
|
numfield.value('{:5.2f}'.format(random() /1000))
|
||||||
|
countfield.value('{:1d}'.format(n))
|
||||||
|
n += 1
|
||||||
|
await evt.wait()
|
||||||
|
|
||||||
|
async def multi_fields(evt):
|
||||||
|
wri = Writer(ssd, small, verbose=False)
|
||||||
|
wri.set_clip(False, False, False)
|
||||||
|
|
||||||
|
nfields = []
|
||||||
|
dy = small.height() + 10
|
||||||
|
row = 2
|
||||||
|
col = 100
|
||||||
|
width = wri.stringlen('99.990')
|
||||||
|
for txt in ('X:', 'Y:', 'Z:'):
|
||||||
|
Label(wri, row, col, txt)
|
||||||
|
nfields.append(Label(wri, row, col, width, bdcolor=None)) # Draw border
|
||||||
|
row += dy
|
||||||
|
|
||||||
|
random = xorshift64star(2**24 - 1)
|
||||||
|
while True:
|
||||||
|
for _ in range(10):
|
||||||
|
for field in nfields:
|
||||||
|
value = random() / 167772
|
||||||
|
field.value('{:5.2f}'.format(value))
|
||||||
|
await evt.wait()
|
||||||
|
|
||||||
|
async def meter(evt):
|
||||||
|
wri = Writer(ssd, arial10, verbose=False)
|
||||||
|
row = 10
|
||||||
|
col = 150
|
||||||
|
m0 = Meter(wri, row, col, height = 80, width = 15, divisions = 4, legends=('0.0', '0.5', '1.0'))
|
||||||
|
m1 = Meter(wri, row, col + 50, height = 80, width = 15, divisions = 4, legends=('-1', '0', '+1'))
|
||||||
|
m2 = Meter(wri, row, col + 100, height = 80, width = 15, divisions = 4, legends=('-1', '0', '+1'))
|
||||||
|
random = xorshift64star(2**24 - 1)
|
||||||
|
while True:
|
||||||
|
steps = 10
|
||||||
|
for n in range(steps + 1):
|
||||||
|
m0.value(random() / 16777216)
|
||||||
|
m1.value(n/steps)
|
||||||
|
m2.value(1 - n/steps)
|
||||||
|
await evt.wait()
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
refresh(ssd, True) # Clear display
|
||||||
|
await ssd.wait()
|
||||||
|
print('Ready')
|
||||||
|
evt = asyncio.Event()
|
||||||
|
asyncio.create_task(meter(evt))
|
||||||
|
asyncio.create_task(multi_fields(evt))
|
||||||
|
asyncio.create_task(fields(evt))
|
||||||
|
while True:
|
||||||
|
# Normal procedure before refresh, but 10s sleep should mean it always returns immediately
|
||||||
|
await ssd.wait()
|
||||||
|
refresh(ssd) # Launches ._as_show()
|
||||||
|
await ssd.updated()
|
||||||
|
# Content has now been shifted out so coros can update
|
||||||
|
# framebuffer in background
|
||||||
|
evt.set()
|
||||||
|
evt.clear()
|
||||||
|
await asyncio.sleep(20) # Allow for slow refresh
|
||||||
|
|
||||||
|
|
||||||
|
tstr = '''Test of asynchronous code updating the EPD. This should
|
||||||
|
not be run for long periods as the EPD should not be updated more
|
||||||
|
frequently than every 180s.
|
||||||
|
'''
|
||||||
|
|
||||||
|
print(tstr)
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Defensive code: avoid leaving EPD hardware in an undefined state.
|
||||||
|
print('Waiting for display to become idle')
|
||||||
|
ssd.wait_until_ready() # Synchronous code
|
||||||
|
finally:
|
||||||
|
_ = asyncio.new_event_loop()
|
Ładowanie…
Reference in New Issue