abstracting display, touch and sd
This commit is contained in:
20
lib/Pico_ePaper_Code/pythonNanoGui/drivers/boolpalette.py
Normal file
20
lib/Pico_ePaper_Code/pythonNanoGui/drivers/boolpalette.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# boolpalette.py Implement BoolPalette class
|
||||
# This is a 2-value color palette for rendering monochrome glyphs to color
|
||||
# FrameBuffer instances. Supports destinations with up to 16 bit color.
|
||||
|
||||
# Copyright (c) Peter Hinch 2021
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
import framebuf
|
||||
|
||||
class BoolPalette(framebuf.FrameBuffer):
|
||||
|
||||
def __init__(self, mode):
|
||||
buf = bytearray(4) # OK for <= 16 bit color
|
||||
super().__init__(buf, 2, 1, mode)
|
||||
|
||||
def fg(self, color): # Set foreground color
|
||||
self.pixel(1, 0, color)
|
||||
|
||||
def bg(self, color):
|
||||
self.pixel(0, 0, color)
|
||||
286
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in13V3.py
Normal file
286
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in13V3.py
Normal file
@@ -0,0 +1,286 @@
|
||||
# ePaper2in13V3.py nanogui driver for Pico-ePpaper-2.13
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper2in13V3.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-10-10
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
WS_20_30_2IN13_V3 = b'\
|
||||
\x80\x4A\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x40\x4A\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x80\x4A\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x40\x4A\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x0F\x00\x00\x00\x00\x00\x00\
|
||||
\x0F\x00\x00\x0F\x00\x00\x02\
|
||||
\x0F\x00\x00\x00\x00\x00\x00\
|
||||
\x01\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x22\x22\x22\x22\x22\x22\x00\x00\x00'
|
||||
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 250 if landscape else 128
|
||||
self.height = 128 if landscape else 250
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
self.init()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
self._rst(0)
|
||||
sleep_ms(5)
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
# Initialisation
|
||||
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x12') # SWRESET
|
||||
self.wait_until_ready()
|
||||
|
||||
self._command(b'\x01') # Driver output control
|
||||
self._data(b'\xF9')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x11') # data entry mode
|
||||
self._data(b'\x03')
|
||||
|
||||
self._command(b'\x44')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x0F')
|
||||
self._command(b'\x45')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\xF9')
|
||||
self._data(b'\x00')
|
||||
self._command(b'\x4E')
|
||||
self._data(b'\x00')
|
||||
self._command(b'\x4F')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x3C') # BorderWaveform
|
||||
self._data(b'\x05')
|
||||
|
||||
self._command(b'\x21') # Display update control
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x80')
|
||||
|
||||
self._command(b'\x18') # Read built-in temperature sensor
|
||||
self._data(b'\x80')
|
||||
|
||||
self._command(b'\x32')
|
||||
self._data(WS_20_30_2IN13_V3)
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x3F')
|
||||
self._data(b'\x22')
|
||||
self._command(b'\x03')
|
||||
self._data(b'\x17')
|
||||
self._command(b'\x04')
|
||||
self._data(b'\x41')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x32')
|
||||
self._command(b'\x2C')
|
||||
self._data(b'\x36')
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 1 == busy.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 1)) # 1 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
|
||||
print('async full refresh')
|
||||
cmd(b'\x22\xC7')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 1:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
print('sync full refresh')
|
||||
cmd(b'\x22\xC7')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x10')
|
||||
self._data(b'\x01')
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
290
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in13V4.py
Normal file
290
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in13V4.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# ePaper2in13V4.py nanogui driver for Pico-ePpaper-2.13
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper2in13V4.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2023-08-12
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False, full=True):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._full = full
|
||||
self._updated = asyncio.Event()
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 250 if landscape else 128
|
||||
self.height = 128 if landscape else 250
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
if self._full:
|
||||
self.init()
|
||||
else:
|
||||
self.init_partial()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
self._rst(0)
|
||||
sleep_ms(5)
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
# Initialisation
|
||||
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x12') # SWRESET
|
||||
self.wait_until_ready()
|
||||
|
||||
self._command(b'\x01') # Driver output control
|
||||
self._data(b'\xF9')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x11') # data entry mode
|
||||
self._data(b'\x03')
|
||||
|
||||
self._command(b'\x44')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x0F')
|
||||
self._command(b'\x45')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\xF9')
|
||||
self._data(b'\x00')
|
||||
self._command(b'\x4E')
|
||||
self._data(b'\x00')
|
||||
self._command(b'\x4F')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x3C') # BorderWaveform
|
||||
self._data(b'\x05')
|
||||
|
||||
self._command(b'\x21') # Display update control
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x80')
|
||||
|
||||
self._command(b'\x18') # Read built-in temperature sensor
|
||||
self._data(b'\x80')
|
||||
self.wait_until_ready()
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def displayPartial(self, image):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
self._rst(0)
|
||||
sleep_ms(20) # 5ms in Waveshare code
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
# Initialisation
|
||||
cmd = self._command
|
||||
cmd(b'\x3C', b'\x80') # BorderWavefrom
|
||||
|
||||
scmd(b'\01', b'\xF9\x00\x00') # Driver output control
|
||||
|
||||
cmd(b'\x01', b'\x03') # data entry mode
|
||||
|
||||
cmd(b'\x44', b'\x00\x0F')
|
||||
cmd(b'\x45', b'\x00\x00\x0F\x00')
|
||||
cmd(b'\x4E', b'\x00')
|
||||
cmd(b'\x4F', b'\x00\x00')
|
||||
self.wait_until_ready()
|
||||
|
||||
print('Init Partial Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 1 == busy.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 1)) # 1 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
|
||||
if self._full:
|
||||
print('sync full refresh')
|
||||
cmd(b'\x22', b'\xF7') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
else:
|
||||
print('sync partial refresh')
|
||||
cmd(b'\x22', b'\xFF') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 1:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
if self._full:
|
||||
print('sync full refresh')
|
||||
cmd(b'\x22', b'\xF7') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
else:
|
||||
print('sync partial refresh')
|
||||
cmd(b'\x22', b'\xFF') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x10')
|
||||
self._data(b'\x01')
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
327
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in13bV4.py
Normal file
327
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in13bV4.py
Normal file
@@ -0,0 +1,327 @@
|
||||
# ePaper2in13bV4.py nanogui driver for Pico-ePpaper-2.13-B
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper2in13bV4.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-10-10
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 250 if landscape else 128
|
||||
self.height = 128 if landscape else 250
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
self.init()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
self._rst(0)
|
||||
sleep_ms(5)
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
# Initialisation
|
||||
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x12') # SWRESET
|
||||
self.wait_until_ready()
|
||||
|
||||
self._command(b'\x01') # Driver output control
|
||||
self._data(b'\xF9')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x11') # data entry mode
|
||||
self._data(b'\x03')
|
||||
|
||||
self._command(b'\x44')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x0F')
|
||||
self._command(b'\x45')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\xF9')
|
||||
self._data(b'\x00')
|
||||
self._command(b'\x4E')
|
||||
self._data(b'\x00')
|
||||
self._command(b'\x4F')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x3C') # BorderWaveform
|
||||
self._data(b'\x05')
|
||||
|
||||
self._command(b'\x18') # Read built-in temperature sensor
|
||||
self._data(b'\x80')
|
||||
|
||||
self._command(b'\x21') # Display update control
|
||||
self._data(b'\x80')
|
||||
self._data(b'\x80')
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 1 == busy.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 1)) # 1 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
|
||||
print('async full refresh')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 1:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
print('sync full refresh')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x10')
|
||||
self._data(b'\x01')
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
|
||||
|
||||
|
||||
class EPDred(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 250 if landscape else 128
|
||||
self.height = 128 if landscape else 250
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x26')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
268
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in7.py
Normal file
268
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in7.py
Normal file
@@ -0,0 +1,268 @@
|
||||
# ePaper2in7.py nanogui driver for Pico-ePpaper-2.7
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper2in7.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-06-08
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Dimensions in pixels. Waveshare code is portrait mode.
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 264 if landscape else 176
|
||||
self.height = 176 if landscape else 264
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
self.init()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
self._rst(0)
|
||||
sleep_ms(200) # 5ms in Waveshare code
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
# Initialisation
|
||||
cmd = self._command
|
||||
cmd(b'\x01', b'\x03\x00\x2B\x2B\x09') # POWER_SETTING: VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR
|
||||
cmd(b'\x06', b'\x07\x07\x17') # BOOSTER_SOFT_START
|
||||
cmd(b'\xf8', b'\x60\xA5') # POWER_OPTIMIZATION
|
||||
cmd(b'\xf8', b'\x89\xA5')
|
||||
cmd(b'\xf8', b'\x90\x00')
|
||||
cmd(b'\xf8', b'\x93\x2A')
|
||||
cmd(b'\xf8', b'\xA0\xA5')
|
||||
cmd(b'\xf8', b'\xA1\x00')
|
||||
cmd(b'\xf8', b'\x73\x41')
|
||||
cmd(b'\x16', b'\x00') # PARTIAL_DISPLAY_REFRESH
|
||||
cmd(b'\x04') # POWER_ON
|
||||
self.wait_until_ready()
|
||||
cmd(b'\x00', b'\xAF') # PANEL_SETTING: KW-BF, KWR-AF, BWROTP 0f
|
||||
cmd(b'\x30', b'\x3A') # PLL_CONTROL: 3A 100HZ, 29 150Hz, 39 200HZ 31 171HZ
|
||||
cmd(b'\x50', b'\x57') # Vcom and data interval setting (PGH)
|
||||
cmd(b'\x82', b'\x12') # VCM_DC_SETTING_REGISTER
|
||||
sleep_ms(2) # No delay in official code
|
||||
# Set LUT. Local bytes objects reduce RAM usage.
|
||||
|
||||
# Values from official code:
|
||||
lut_vcom_dc =\
|
||||
b'\x00\x00\x00\x08\x00\x00\x00\x02\x60\x28\x28\x00\x00\x01\x00'\
|
||||
b'\x14\x00\x00\x00\x01\x00\x12\x12\x00\x00\x01\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
lut_ww =\
|
||||
b'\x40\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x40\x14\x00'\
|
||||
b'\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
lut_bb =\
|
||||
b'\x80\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x80\x14\x00'\
|
||||
b'\x00\x00\x01\x50\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
|
||||
# Both agree on this:
|
||||
lut_bw = lut_ww # R22H r
|
||||
lut_wb = lut_bb # R23H w
|
||||
cmd(b'\x20', lut_vcom_dc) # LUT_FOR_VCOM vcom
|
||||
cmd(b'\x21', lut_ww) # LUT_WHITE_TO_WHITE ww --
|
||||
cmd(b'\x22', lut_bw) # LUT_BLACK_TO_WHITE bw r
|
||||
cmd(b'\x23', lut_bb) # LUT_WHITE_TO_BLACK wb w
|
||||
cmd(b'\x24', lut_wb) # LUT_BLACK_TO_BLACK bb b
|
||||
print('Init Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 0 == busy. Comment in official code is wrong. Code is correct.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 0)) # 0 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
cmd(b'\x10') # DATA_START_TRANSMISSION_1
|
||||
self._dc(1) # For some reason don't need to deassert CS here
|
||||
buf1[0] = 0xff
|
||||
t = ticks_ms()
|
||||
for i in range(len(mvb)):
|
||||
self._cs(0) # but do when copying the framebuf
|
||||
send(buf1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
self._cs(1)
|
||||
cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
cmd(b'\x12') # DISPLAY_REFRESH
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 0:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
cmd(b'\x10') # DATA_START_TRANSMISSION_1
|
||||
self._dc(1) # For some reason don't need to deassert CS here
|
||||
buf1[0] = 0xff
|
||||
for i in range(len(mvb)):
|
||||
self._cs(0) # but do when copying the framebuf
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
cmd(b'\x12') # DISPLAY_REFRESH
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
cmd = self._command
|
||||
cmd(b'\x50', b'\xf7') # From Waveshare code
|
||||
cmd(b'\x02') # POWER_OFF
|
||||
cmd(b'\x07', b'\xA5') # DEEP_SLEEP (Waveshare and mcauser)
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
254
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in7V2.py
Normal file
254
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in7V2.py
Normal file
@@ -0,0 +1,254 @@
|
||||
# ePaper2in7V2.py nanogui driver for Pico-ePpaper-2.7_V2
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper2in7V2.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-03-15
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False, full=True):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._full = full
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Dimensions in pixels. Waveshare code is portrait mode.
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 264 if landscape else 176
|
||||
self.height = 176 if landscape else 264
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
if self._full:
|
||||
self.init()
|
||||
else:
|
||||
self.init_partial()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
self._rst(0)
|
||||
sleep_ms(20) # 5ms in Waveshare code
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
# Initialisation
|
||||
cmd = self._command
|
||||
cmd(b'\x12') #SWRESET
|
||||
self.wait_until_ready()
|
||||
|
||||
cmd(b'\x45', b'\x00\x00\x07\x01') #set Ram-Y address start/end position
|
||||
cmd(b'\x4F', b'\x00\x00') # set RAM y address count to 0
|
||||
cmd(b'\x11', b'\x03') # data entry mode
|
||||
self.wait_until_ready()
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def init_partial(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
self._rst(0)
|
||||
sleep_ms(20) # 5ms in Waveshare code
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
# Initialisation
|
||||
cmd = self._command
|
||||
cmd(b'\x3c', b'\x80')
|
||||
|
||||
cmd(b'\x45', b'\x00\x00\x07\x01') #set Ram-Y address start/end position
|
||||
cmd(b'\x4F', b'\x00\x00') # set RAM y address count to 0
|
||||
cmd(b'\x11', b'\x03') # data entry mode
|
||||
self.wait_until_ready()
|
||||
|
||||
print('Init Partial Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 1 == busy. Comment in official code is wrong. Code is correct.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 1)) # 1 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24') # DATA_START_TRANSMISSION_2 not in datasheet
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
if self._full:
|
||||
print('sync full refresh')
|
||||
cmd(b'\x22', b'\xF7') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
else:
|
||||
print('sync partial refresh')
|
||||
cmd(b'\x22', b'\xFF') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 1:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24') # DATA_START_TRANSMISSION_2 not in datasheet
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
if self._full:
|
||||
print('sync full refresh')
|
||||
cmd(b'\x22', b'\xF7') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
else:
|
||||
print('sync partial refresh')
|
||||
cmd(b'\x22', b'\xFF') # DISPLAY_REFRESH
|
||||
cmd(b'\x20')
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
cmd = self._command
|
||||
cmd(b'\x10', b'\01') # DEEP_SLEEP (Waveshare and mcauser)
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
287
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in9.py
Normal file
287
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper2in9.py
Normal file
@@ -0,0 +1,287 @@
|
||||
# ePaper2in9.py nanogui driver for Pico-ePpaper-2.9
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper2in9.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-09-08
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False, full=True):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._full = full
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Dimensions in pixels. Waveshare code is portrait mode.
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 296 if landscape else 128
|
||||
self.height = 128 if landscape else 296
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
self.init()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
self._rst(0)
|
||||
sleep_ms(20) # 5ms in Waveshare code
|
||||
self._rst(1)
|
||||
sleep_ms(200)
|
||||
# Initialisation
|
||||
cmd = self._command
|
||||
self.wait_until_ready()
|
||||
cmd(b'\x12')
|
||||
self.wait_until_ready() #SWRESET
|
||||
cmd(b'\x01', b'\x27\x01\x00') # Driver output control
|
||||
cmd(b'\x11', b'\x03')
|
||||
cmd(b'\x21', b'\x00\x80')
|
||||
|
||||
cmd(b'\x44', b'\x00\x0F')
|
||||
cmd(b'\x45', b'\x00\x00\x27\x01')
|
||||
cmd(b'\x4E', b'\x00')
|
||||
cmd(b'\x4F', b'\x00\x00')
|
||||
self.wait_until_ready()
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def init_partial(self):
|
||||
# Hardware reset
|
||||
self._rst(0)
|
||||
sleep_ms(2)
|
||||
self._rst(1)
|
||||
sleep_ms(2)
|
||||
# Initialisation
|
||||
cmd = self._command
|
||||
lut_wf_2in9 =\
|
||||
b'\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x80\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x40\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x0A\x00\x00\x00\x00\x00\x01'\
|
||||
b'\x01\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x01\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x00\x00\x00\x00\x00\x00\x00'\
|
||||
b'\x22\x22\x22\x22\x22\x22\x00\x00\x00'\
|
||||
b'\x22\x17\x41\xB0\x32\x36'
|
||||
|
||||
cmd(b'\x32', lut_wf_2in9)
|
||||
cmd(b'\x37', b'\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00')
|
||||
cmd(b'\x3c', b'\x80')
|
||||
cmd(b'\x22', b'\xc0')
|
||||
cmd(b'\x20')
|
||||
|
||||
self.wait_until_ready()
|
||||
|
||||
cmd(b'\x44', b'\x00\x0F')
|
||||
cmd(b'\x45', b'\x00\x00\x27\x01')
|
||||
cmd(b'\x4E', b'\x00')
|
||||
cmd(b'\x4F', b'\x00\x00')
|
||||
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 1 == busy.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 1)) # 1 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24') # DATA_START_TRANSMISSION_2 not in datasheet
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
if self._full:
|
||||
print('async full refresh')
|
||||
cmd(b'\x22', b'\xF7')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
else:
|
||||
print('async partial refresh')
|
||||
cmd(b'\x22', b'\x0F')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 1:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
|
||||
if not self._full:
|
||||
self.init_partial()
|
||||
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24') # DATA_START_TRANSMISSION_2 not in datasheet
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
if self._full:
|
||||
print('sync full refresh')
|
||||
cmd(b'\x22', b'\xF7')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
else:
|
||||
print('sync partial refresh')
|
||||
cmd(b'\x22', b'\x0F')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
cmd = self._command
|
||||
cmd(b'\x10', b'\x01') # DEEP_SLEEP
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
|
||||
|
||||
301
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper3in7.py
Normal file
301
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper3in7.py
Normal file
@@ -0,0 +1,301 @@
|
||||
# ePaper3in7.py nanogui driver for Pico-ePpaper-3.7
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper3in7.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-10-08
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
EPD_3IN7_lut_1Gray_GC =b"\
|
||||
\x2A\x05\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x05\x2A\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x2A\x15\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x05\x0A\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x02\x03\x0A\x00\x02\x06\x0A\x05\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x22\x22\x22\x22\x22"
|
||||
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 480 if landscape else 280
|
||||
self.height = 280 if landscape else 480
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
self.init()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
self._rst(0)
|
||||
sleep_ms(5)
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
# Initialisation
|
||||
|
||||
self._command(b'\x12')
|
||||
sleep_ms(300)
|
||||
|
||||
self._command(b'\x46')
|
||||
self._data(b'\xF7')
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x47')
|
||||
self._data(b'\xF7')
|
||||
self.wait_until_ready()
|
||||
|
||||
self._command(b'\x01') # setting gaet number
|
||||
self._data(b'\xDF')
|
||||
self._data(b'\x01')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x03') # set gate voltage
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x04') # set source voltage
|
||||
self._data(b'\x41')
|
||||
self._data(b'\xA8')
|
||||
self._data(b'\x32')
|
||||
|
||||
self._command(b'\x11') # set data entry sequence
|
||||
self._data(b'\x03')
|
||||
|
||||
self._command(b'\x3C') # set border
|
||||
self._data(b'\x03')
|
||||
|
||||
self._command(b'\x0C') # set booster strength
|
||||
self._data(b'\xAE')
|
||||
self._data(b'\xC7')
|
||||
self._data(b'\xC3')
|
||||
self._data(b'\xC0')
|
||||
self._data(b'\xC0')
|
||||
|
||||
self._command(b'\x18') # set internal sensor on
|
||||
self._data(b'\x80')
|
||||
|
||||
self._command(b'\x2C') # set vcom value
|
||||
self._data(b'\x44')
|
||||
|
||||
self._command(b'\x37') # set display option, these setting turn on previous function
|
||||
self._data(b'\x00') # can switch 1 gray or 4 gray
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\x4F')
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\xFF')
|
||||
self._data(b'\xFF')
|
||||
|
||||
self._command(b'\x44') # setting X direction start/end position of RAM
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x17')
|
||||
self._data(b'\x01')
|
||||
|
||||
self._command(b'\x45') # setting Y direction start/end position of RAM
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\xDF')
|
||||
self._data(b'\x01')
|
||||
|
||||
self._command(b'\x22') # Display Update Control 2
|
||||
self._data(b'\xCF')
|
||||
|
||||
self._command(b'\x32')
|
||||
self._data(EPD_3IN7_lut_1Gray_GC)
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 1 == busy.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 1)) # 1 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
|
||||
print('async full refresh')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 1:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x24')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
print('sync full refresh')
|
||||
cmd(b'\x20') # DISPLAY_REFRESH
|
||||
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x10')
|
||||
self._data(b'\x03')
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
|
||||
|
||||
234
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper4in2.py
Normal file
234
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper4in2.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# pico_epaper_42.py A 1-bit monochrome display driver for the Waveshare Pico
|
||||
# ePaper 4.2" display.
|
||||
# Adapted from the Waveshare driver by Peter Hinch Sept 2022.
|
||||
|
||||
# *****************************************************************************
|
||||
# * | File : ePaper4in2.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-10-09
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation 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
|
||||
# furished 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 OR 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.
|
||||
#
|
||||
|
||||
from machine import Pin, SPI
|
||||
import framebuf
|
||||
import time
|
||||
import uasyncio as asyncio
|
||||
from drivers.boolpalette import BoolPalette
|
||||
|
||||
# Display resolution
|
||||
_EPD_WIDTH = const(400)
|
||||
_BWIDTH = _EPD_WIDTH // 8
|
||||
_EPD_HEIGHT = const(300)
|
||||
|
||||
RST_PIN = 12
|
||||
DC_PIN = 8
|
||||
CS_PIN = 9
|
||||
BUSY_PIN = 13
|
||||
|
||||
EPD_lut_vcom0 = b"\x00\x08\x08\x00\x00\x02\x00\x0F\x0F\x00\x00\x01\x00\x08\x08\x00\
|
||||
\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
EPD_lut_ww = b"\x50\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\xA0\x08\x08\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
EPD_lut_bw = b"\x50\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\xA0\x08\x08\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
EPD_lut_wb = b"\xA0\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\x50\x08\x08\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
EPD_lut_bb = b"\x20\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\x10\x08\x08\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi=None, cs=None, dc=None, rst=None, busy=None, landscape=None, asyn=False):
|
||||
self.reset_pin = Pin(RST_PIN, Pin.OUT) if rst is None else rst
|
||||
self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) if busy is None else busy
|
||||
self.cs_pin = Pin(CS_PIN, Pin.OUT) if cs is None else cs
|
||||
self.dc_pin = Pin(DC_PIN, Pin.OUT) if dc is None else dc
|
||||
self.spi = SPI(1) if spi is None else spi
|
||||
self.spi.init(baudrate=4_000_000)
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
|
||||
self.width = _EPD_WIDTH
|
||||
self.height = _EPD_HEIGHT
|
||||
self.buf = bytearray(_EPD_HEIGHT * _BWIDTH)
|
||||
self.mvb = memoryview(self.buf)
|
||||
mode = framebuf.MONO_HLSB
|
||||
self.palette = BoolPalette(mode)
|
||||
super().__init__(self.buf, _EPD_WIDTH, _EPD_HEIGHT, mode)
|
||||
self.init()
|
||||
time.sleep_ms(500)
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
for v in (1, 0, 1):
|
||||
self.reset_pin(v)
|
||||
time.sleep_ms(20)
|
||||
|
||||
def send_command(self, command):
|
||||
self.dc_pin(0)
|
||||
self.cs_pin(0)
|
||||
self.spi.write(command)
|
||||
self.cs_pin(1)
|
||||
|
||||
def send_bytes(self, data):
|
||||
self.dc_pin(1)
|
||||
self.cs_pin(0)
|
||||
self.spi.write(data)
|
||||
self.cs_pin(1)
|
||||
|
||||
def display_on(self):
|
||||
self.send_command(b"\x12")
|
||||
time.sleep_ms(100)
|
||||
self.wait_until_ready()
|
||||
|
||||
def init(self):
|
||||
self.reset()
|
||||
self.send_command(b"\x01") # POWER SETTING
|
||||
self.send_bytes(b"\x03")
|
||||
self.send_bytes(b"\x00")
|
||||
self.send_bytes(b"\x2b")
|
||||
self.send_bytes(b"\x2b")
|
||||
|
||||
self.send_command(b"\x06") # boost soft start
|
||||
self.send_bytes(b"\x17") # A
|
||||
self.send_bytes(b"\x17") # B
|
||||
self.send_bytes(b"\x17") # C
|
||||
|
||||
self.send_command(b"\x04") # POWER_ON
|
||||
self.wait_until_ready()
|
||||
|
||||
self.send_command(b"\x00") # panel setting
|
||||
self.send_bytes(b"\xbf") # KW-BF KWR-AF BWROTP 0f BWOTP 1f
|
||||
self.send_bytes(b"\x0d")
|
||||
|
||||
self.send_command(b"\x30") # PLL setting
|
||||
self.send_bytes(b"\x3C") # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
|
||||
|
||||
self.send_command(b"\x61") # resolution setting
|
||||
self.send_bytes(b"\x01")
|
||||
self.send_bytes(b"\x90") # 128
|
||||
self.send_bytes(b"\x01")
|
||||
self.send_bytes(b"\x2c")
|
||||
|
||||
self.send_command(b"\x82") # vcom_DC setting
|
||||
self.send_bytes(b"\x28")
|
||||
|
||||
self.send_command(b"\x50") # VCOM AND DATA INTERVAL SETTING
|
||||
self.send_bytes(b"\x97") # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7
|
||||
self.send_command(b"\x20")
|
||||
self.send_bytes(EPD_lut_vcom0)
|
||||
|
||||
self.send_command(b"\x21")
|
||||
self.send_bytes(EPD_lut_ww)
|
||||
|
||||
self.send_command(b"\x22")
|
||||
self.send_bytes(EPD_lut_bw)
|
||||
|
||||
self.send_command(b"\x23")
|
||||
self.send_bytes(EPD_lut_wb)
|
||||
|
||||
self.send_command(b"\x24")
|
||||
self.send_bytes(EPD_lut_bb)
|
||||
# Clear display
|
||||
self.send_command(b"\x10")
|
||||
for j in range(_EPD_HEIGHT):
|
||||
self.send_bytes(b"\xff" * _BWIDTH)
|
||||
|
||||
self.send_command(b"\x13")
|
||||
for j in range(_EPD_HEIGHT):
|
||||
self.send_bytes(b"\xff" * _BWIDTH)
|
||||
|
||||
self.send_command(b"\x12")
|
||||
time.sleep_ms(10)
|
||||
self.display_on()
|
||||
|
||||
def wait_until_ready(self):
|
||||
while(not self.ready()):
|
||||
self.send_command(b"\x71")
|
||||
time.sleep_ms(100)
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
self.send_command(b"\x71")
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 0 == busy. Comment in official code is wrong. Code is correct.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self.busy_pin() == 0)) # 0 == busy
|
||||
|
||||
def _line(self, n, buf=bytearray(_BWIDTH)):
|
||||
img = self.mvb
|
||||
s = n * _BWIDTH
|
||||
for x, b in enumerate(img[s : s + _BWIDTH]):
|
||||
buf[x] = b ^ 0xFF
|
||||
self.send_bytes(buf)
|
||||
|
||||
async def _as_show(self):
|
||||
self.send_command(b"\x13")
|
||||
for j in range(_EPD_HEIGHT): # Loop would take ~300ms
|
||||
self._line(j)
|
||||
await asyncio.sleep_ms(0)
|
||||
self.send_command(b"\x12") # Async .display_on()
|
||||
while not self.busy_pin():
|
||||
await asyncio.sleep_ms(10) # About 1.7s
|
||||
self._updated.set()
|
||||
self._updated.clear()
|
||||
self._as_busy = False
|
||||
|
||||
def show(self):
|
||||
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
|
||||
self.send_command(b"\x13")
|
||||
for j in range(_EPD_HEIGHT):
|
||||
self._line(j)
|
||||
self.display_on()
|
||||
self.wait_until_ready()
|
||||
|
||||
def sleep(self):
|
||||
# self.send_command(b"\x02") # power off
|
||||
# self.wait_until_ready()
|
||||
self.send_command(b"\x07") # deep sleep
|
||||
self.send_bytes(b"\xA5")
|
||||
207
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper4in2V2.py
Normal file
207
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper4in2V2.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# pico_epaper_42.py A 1-bit monochrome display driver for the Waveshare Pico
|
||||
# ePaper 4.2" display.
|
||||
# Adapted from the Waveshare driver by Peter Hinch Sept 2022.
|
||||
|
||||
# *****************************************************************************
|
||||
# * | File : ePaper4in2.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-10-09
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation 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
|
||||
# furished 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 OR 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.
|
||||
#
|
||||
|
||||
from machine import Pin, SPI
|
||||
import framebuf
|
||||
import time
|
||||
import uasyncio as asyncio
|
||||
from drivers.boolpalette import BoolPalette
|
||||
|
||||
# Display resolution
|
||||
_EPD_WIDTH = const(400)
|
||||
_BWIDTH = _EPD_WIDTH // 8
|
||||
_EPD_HEIGHT = const(300)
|
||||
|
||||
RST_PIN = 12
|
||||
DC_PIN = 8
|
||||
CS_PIN = 9
|
||||
BUSY_PIN = 13
|
||||
|
||||
EPD_LUT_ALL=b"\x01\x0A\x1B\x0F\x03\x01\x01\x05\x0A\x01\x0A\x01\x01\x01\x05\x08\x03\x02\x04\x01\x01\x01\x04\x04\x02\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\
|
||||
\x01\x00\x00\x00\x00\x01\x01\x01\x0A\x1B\x0F\x03\x01\x01\x05\x4A\x01\x8A\x01\x01\x01\x05\x48\x03\x82\x84\x01\x01\x01\x84\x84\x82\x00\x01\x01\
|
||||
\x01\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x0A\x1B\x8F\x03\x01\x01\x05\x4A\x01\x8A\x01\x01\x01\x05\x48\x83\x82\x04\x01\x01\
|
||||
\x01\x04\x04\x02\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x8A\x1B\x8F\x03\x01\x01\
|
||||
\x05\x4A\x01\x8A\x01\x01\x01\x05\x48\x83\x02\x04\x01\x01\x01\x04\x04\x02\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\
|
||||
\x01\x00\x00\x00\x00\x01\x01\x01\x8A\x9B\x8F\x03\x01\x01\x05\x4A\x01\x8A\x01\x01\x01\x05\x48\x03\x42\x04\x01\x01\
|
||||
\x01\x04\x04\x42\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x07\x17\x41\xA8\x32\x30"
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi=None, cs=None, dc=None, rst=None, busy=None, landscape=None, asyn=False):
|
||||
self.reset_pin = Pin(RST_PIN, Pin.OUT) if rst is None else rst
|
||||
self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) if busy is None else busy
|
||||
self.cs_pin = Pin(CS_PIN, Pin.OUT) if cs is None else cs
|
||||
self.dc_pin = Pin(DC_PIN, Pin.OUT) if dc is None else dc
|
||||
self.spi = SPI(1) if spi is None else spi
|
||||
self.spi.init(baudrate=4_000_000)
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
|
||||
self.width = _EPD_WIDTH
|
||||
self.height = _EPD_HEIGHT
|
||||
self.buf = bytearray(_EPD_HEIGHT * _BWIDTH)
|
||||
self.mvb = memoryview(self.buf)
|
||||
mode = framebuf.MONO_HLSB
|
||||
self.palette = BoolPalette(mode)
|
||||
super().__init__(self.buf, _EPD_WIDTH, _EPD_HEIGHT, mode)
|
||||
self.init()
|
||||
time.sleep_ms(500)
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
for v in (1, 0, 1):
|
||||
self.reset_pin(v)
|
||||
time.sleep_ms(20)
|
||||
|
||||
def send_command(self, command):
|
||||
self.dc_pin(0)
|
||||
self.cs_pin(0)
|
||||
self.spi.write(command)
|
||||
self.cs_pin(1)
|
||||
|
||||
def send_bytes(self, data):
|
||||
self.dc_pin(1)
|
||||
self.cs_pin(0)
|
||||
self.spi.write(data)
|
||||
self.cs_pin(1)
|
||||
|
||||
def display_on(self):
|
||||
self.send_command(b"\x22")
|
||||
self.send_bytes(b"\xF7")
|
||||
self.send_command(b"\x20")
|
||||
time.sleep_ms(100)
|
||||
self.wait_until_ready()
|
||||
|
||||
def init(self):
|
||||
self.reset()
|
||||
self.wait_until_ready()
|
||||
|
||||
self.send_command(b"\x12") #SWRESET
|
||||
self.wait_until_ready()
|
||||
|
||||
self.send_command(b"\x21") # Display update control
|
||||
self.send_bytes(b"\x40")
|
||||
self.send_bytes(b"\x00")
|
||||
|
||||
self.send_command(b"\x3C") # BorderWavefrom
|
||||
self.send_bytes(b"\x05")
|
||||
|
||||
self.send_command(b"\x11") # data entry mode
|
||||
self.send_bytes(b"\x03") # X-mode
|
||||
|
||||
self.send_command(b"\x44")
|
||||
self.send_bytes(b"\x00")
|
||||
self.send_bytes(b"\x31")
|
||||
|
||||
self.send_command(b"\x45")
|
||||
self.send_bytes(b"\x00")
|
||||
self.send_bytes(b"\x00")
|
||||
self.send_bytes(b"\x2B")
|
||||
self.send_bytes(b"\x01")
|
||||
|
||||
self.send_command(b"\x4E")
|
||||
self.send_bytes(b"\x00")
|
||||
|
||||
self.send_command(b"\x4F")
|
||||
self.send_bytes(b"\x00")
|
||||
self.send_bytes(b"\x00")
|
||||
self.wait_until_ready()
|
||||
|
||||
# Clear display
|
||||
self.send_command(b"\x24")
|
||||
for j in range(_EPD_HEIGHT):
|
||||
self.send_bytes(b"\xff" * _BWIDTH)
|
||||
|
||||
self.send_command(b"\x26")
|
||||
for j in range(_EPD_HEIGHT):
|
||||
self.send_bytes(b"\xff" * _BWIDTH)
|
||||
|
||||
self.display_on()
|
||||
|
||||
def wait_until_ready(self):
|
||||
while(self.ready()):
|
||||
time.sleep_ms(100)
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 0 == busy. Comment in official code is wrong. Code is correct.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self.busy_pin() == 0)) # 0 == busy
|
||||
|
||||
def _line(self, n, buf=bytearray(_BWIDTH)):
|
||||
img = self.mvb
|
||||
s = n * _BWIDTH
|
||||
for x, b in enumerate(img[s : s + _BWIDTH]):
|
||||
buf[x] = b ^ 0xFF
|
||||
self.send_bytes(buf)
|
||||
|
||||
async def _as_show(self):
|
||||
self.send_command(b"\x24")
|
||||
for j in range(_EPD_HEIGHT): # Loop would take ~300ms
|
||||
self._line(j)
|
||||
await asyncio.sleep_ms(0)
|
||||
self.wait_until_ready()
|
||||
self._updated.set()
|
||||
self._updated.clear()
|
||||
self._as_busy = False
|
||||
|
||||
def show(self):
|
||||
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
|
||||
self.send_command(b"\x24")
|
||||
for j in range(_EPD_HEIGHT):
|
||||
self._line(j)
|
||||
self.display_on()
|
||||
self.wait_until_ready()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(b"\x10") # deep sleep
|
||||
self.send_bytes(b"\x01")
|
||||
326
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper7in5b.py
Normal file
326
lib/Pico_ePaper_Code/pythonNanoGui/drivers/ePaper7in5b.py
Normal file
@@ -0,0 +1,326 @@
|
||||
# ePaper7in5b.py nanogui driver for Pico-ePpaper-7.5-B
|
||||
# Tested with RPi Pico
|
||||
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
|
||||
# Optimisations to reduce allocations and RAM use.
|
||||
|
||||
# Released under the MIT license see LICENSE
|
||||
# Thanks to @Peter for a great micropython-nano-gui: https://github.com/peterhinch/micropython-nano-gui
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# * | File : ePaper7in5b.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-10-10
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import framebuf
|
||||
import uasyncio as asyncio
|
||||
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
|
||||
|
||||
class EPD(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._asyn = asyn
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
self._updated = asyncio.Event()
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 480 if landscape else 800
|
||||
self.height = 800 if landscape else 480
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
self.init()
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
def init(self):
|
||||
# Hardware reset
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
self._rst(0)
|
||||
sleep_ms(5)
|
||||
self._rst(1)
|
||||
sleep_ms(20)
|
||||
# Initialisation
|
||||
|
||||
self._command(b'\x06') # btst
|
||||
self._data(b'\x17')
|
||||
self._data(b'\x17')
|
||||
self._data(b'\x28') # If an exception is displayed, try using 0x38
|
||||
self._data(b'\x17')
|
||||
|
||||
self._command(b'\x04') # POWER ON
|
||||
sleep_ms(100)
|
||||
self.wait_until_ready()
|
||||
|
||||
self._command(b'\x00') # PANNEL SETTING
|
||||
self._data(b'\x0F') # KW-3f KWR-2F BWROTP 0f BWOTP 1f
|
||||
|
||||
self._command(b'\x61') # tres
|
||||
self._data(b'\x03') # source 800
|
||||
self._data(b'\x20')
|
||||
self._data(b'\x01') # gate 480
|
||||
self._data(b'\xE0')
|
||||
|
||||
self._command(b'\x15')
|
||||
self._data(b'\x00')
|
||||
|
||||
self._command(b'\x50') # VCOM AND DATA INTERVAL SETTING
|
||||
self._data(b'\x11')
|
||||
self._data(b'\x07')
|
||||
|
||||
self._command(b'\x60') # TCON SETTING
|
||||
self._data(b'\x22')
|
||||
|
||||
self._command(b'\x65') # RESOLUTION SETTING
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
self._data(b'\x00')
|
||||
|
||||
print('Init Done.')
|
||||
|
||||
def wait_until_ready(self):
|
||||
sleep_ms(50)
|
||||
t = ticks_ms()
|
||||
while not self.ready():
|
||||
sleep_ms(100)
|
||||
dt = ticks_diff(ticks_ms(), t)
|
||||
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
|
||||
|
||||
async def wait(self):
|
||||
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
|
||||
while not self.ready():
|
||||
await asyncio.sleep_ms(100)
|
||||
|
||||
# Pause until framebuf has been copied to device.
|
||||
async def updated(self):
|
||||
await self._updated.wait()
|
||||
|
||||
# For polling in asynchronous code. Just checks pin state.
|
||||
# 0 == busy.
|
||||
def ready(self):
|
||||
return not(self._as_busy or (self._busy() == 0)) # 0 == busy
|
||||
|
||||
async def _as_show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x10')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
t = ticks_ms()
|
||||
if self._lsc: # Landscape mode
|
||||
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)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
else:
|
||||
for i, b in enumerate(mvb):
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
|
||||
await asyncio.sleep_ms(0)
|
||||
t = ticks_ms()
|
||||
|
||||
self._updated.set() # framebuf has now been copied to the device
|
||||
self._updated.clear()
|
||||
|
||||
print('async full refresh')
|
||||
cmd(b'\x12') # DISPLAY_REFRESH
|
||||
|
||||
await asyncio.sleep(1)
|
||||
while self._busy() == 0:
|
||||
await asyncio.sleep_ms(200) # Don't release lock until update is complete
|
||||
self._as_busy = False
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
if self._asyn:
|
||||
if self._as_busy:
|
||||
raise RuntimeError('Cannot refresh: display is busy.')
|
||||
self._as_busy = True
|
||||
asyncio.create_task(self._as_show())
|
||||
return
|
||||
t = ticks_us()
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x10')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = ~mvb[idx] # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = ~b # INVERSION HACK ~data
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
|
||||
print('sync full refresh')
|
||||
cmd(b'\x12') # DISPLAY_REFRESH
|
||||
|
||||
te = ticks_us()
|
||||
print('show time', ticks_diff(te, t)//1000, 'ms')
|
||||
if not self.demo_mode:
|
||||
# Immediate return to avoid blocking the whole application.
|
||||
# User should wait for ready before calling refresh()
|
||||
return
|
||||
self.wait_until_ready()
|
||||
sleep_ms(2000) # Give time for user to see result
|
||||
|
||||
|
||||
# to wake call init()
|
||||
def sleep(self):
|
||||
self._as_busy = False
|
||||
self._command(b'\x02')
|
||||
self.wait_until_ready()
|
||||
self._command(b'\x07')
|
||||
self._data(b'\xA5')
|
||||
self._rst(0) # According to schematic this turns off the power
|
||||
|
||||
|
||||
class EPDred(framebuf.FrameBuffer):
|
||||
# A monochrome approach should be used for coding this. The rgb method ensures
|
||||
# nothing breaks if users specify colors.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return int((r > 127) or (g > 127) or (b > 127))
|
||||
|
||||
def __init__(self, spi, cs, dc, rst, busy, landscape=False):
|
||||
self._spi = spi
|
||||
self._cs = cs # Pins
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self._busy = busy
|
||||
self._lsc = landscape
|
||||
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
|
||||
# Public bound variables required by nanogui.
|
||||
self.width = 480 if landscape else 800
|
||||
self.height = 800 if landscape else 480
|
||||
self.demo_mode = False # Special mode enables demos to run
|
||||
self._buffer = bytearray(self.height * self.width // 8)
|
||||
self._mvb = memoryview(self._buffer)
|
||||
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
|
||||
super().__init__(self._buffer, self.width, self.height, mode)
|
||||
|
||||
def _command(self, command, data=None):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
if data is not None:
|
||||
self._data(data)
|
||||
|
||||
def _data(self, data, buf1=bytearray(1)):
|
||||
self._dc(1)
|
||||
for b in data:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
self._spi.write(buf1)
|
||||
self._cs(1)
|
||||
|
||||
# draw the current frame memory. Blocking time ~180ms
|
||||
def show(self, buf1=bytearray(1)):
|
||||
mvb = self._mvb
|
||||
send = self._spi.write
|
||||
cmd = self._command
|
||||
|
||||
cmd(b'\x13')
|
||||
|
||||
self._dc(1)
|
||||
# Necessary to deassert CS after each byte otherwise display does not
|
||||
# clear down correctly
|
||||
if self._lsc: # Landscape mode
|
||||
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 _ in range(len(mvb)):
|
||||
self._cs(0)
|
||||
buf1[0] = mvb[idx]
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
idx -= self.width
|
||||
vbc += 1
|
||||
vbc %= tbc
|
||||
if not vbc:
|
||||
hpc += 1
|
||||
idx = iidx + hpc
|
||||
else:
|
||||
for b in mvb:
|
||||
self._cs(0)
|
||||
buf1[0] = b
|
||||
send(buf1)
|
||||
self._cs(1)
|
||||
Reference in New Issue
Block a user