micropython-nano-gui/drivers/epaper/epd29_ssd1680.py

200 wiersze
6.7 KiB
Python

# Nanogui driver for WeAct Studio 2.9" Black and White ePaper display.
# This driver can be used with the 2.13" EPD from WeAct Studio or with
# other displays with the same driver however, on my tests changing the resolution
# may rise exception, but I manage to successfully use this driver with the smaller
# display without changing the resolution.
# [Display](https://github.com/WeActStudio/WeActStudio.EpaperModule)
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
# Released under the MIT license see LICENSE
# Based on the following sources:
# https://github.com/peterhinch/micropython-nano-gui/blob/master/drivers/epaper/epd29.py
# https://github.com/hfwang132/ssd1680-micropython-drivers
# You can run a demo for this driver by executing the demo script "epd29_sync.py"
import framebuf
import uasyncio as asyncio
from micropython import const
from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff
from gui.drivers.boolpalette import BoolPalette
from machine import lightsleep, Pin
class TimeoutError(Exception):
def __init__(self, msg):
super().__init__(msg)
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))
# Discard asyn: autodetect
def __init__(self, spi, cs, dc, rst_pin, busy, landscape=True):
self._spi = spi
self._cs = cs # Pins
self._dc = dc
self._rst = Pin(rst_pin, Pin.OUT, value=1)
self._rst_pin = rst_pin
self._busy = busy # Pin High if Busy
self._lsc = landscape
# ._as_busy is set immediately on start of task. Cleared
# when busy pin is physically 0.
self._as_busy = False
self.updated = asyncio.Event()
self.complete = asyncio.Event()
# Public bound variables required by nanogui.
# Dimensions in pixels as seen by nanogui (landscape mode).
self.width = 296 if landscape else 128
self.height = 128 if landscape else 296
# Other public bound variable.
# Special mode enables demos written for generic displays to run.
self.demo_mode = False
self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
self.palette = BoolPalette(mode)
super().__init__(self._buffer, self.width, self.height, mode)
self.init()
def _command(self, command, data=None):
self._dc(0)
self._spi.write(command)
self._dc(1)
if data is not None:
self._data(data)
def _data(self, data, buf1=bytearray(1)):
for b in data:
buf1[0] = b
self._spi.write(buf1)
def hw_reset(self):
self._rst(1)
sleep_ms(200)
self._rst(0)
sleep_ms(200)
self._rst(1)
self.wait_until_ready()
# print("hardware reset successful")
def init(self):
# Hardware reset
self.hw_reset()
self._dc(1)
self._cs(0)
# Initialisation
cmd = self._command
# Software Reset
# print("software resetting...")
# cmd(b'\x12')
# self.wait_until_ready()
# print("software reset successful")
# deriver output control
cmd(b'\x01', b'\x27\x01\x01')
# data entry mode
cmd(b'\x11', b'\x01')
# set ram-x addr start/end pos
cmd(b'\x44', b'\x00\x0F')
# set ram-y addr start/end pos
cmd(b'\x45', b'\x27\x01\x00\x00')
# border waveform
cmd(b'\x3c', b'\x05')
# display update control
cmd(b'\x21', b'\x00\x80')
# set ram-x addr cnt to 0
cmd(b'\x4e', b'\x00')
# set ram-y addr cnt to 0x127
cmd(b'\x4F', b'\x27\x01')
# set to use internal temperature sensor
cmd(b'\x18', b'\x80')
'''
# read from internal temperature sensor
self._dc(0)
self._spi.write(b'\x1B')
print(self._spi.read(2))
self._dc(1)
print(self._spi.read(2))
'''
self.wait_until_ready()
# print('Init Done.')
# For use in synchronous code: blocking wait on ready state.
def wait_until_ready(self):
sleep_ms(50)
while not self.ready():
sleep_ms(100)
# Return immediate status. Pin state: 1 == busy.
def ready(self):
return not (self._as_busy or (self._busy() == 1))
# draw the current frame memory.
def show(self, buf1=bytearray(1),
fast_refresh=False, # USELESS for this driver, doesn't work well at all
deepsleep_after_refresh=False,
lightsleep_while_waiting_for_refresh=False):
if not self.ready():
# Hardware reset to exit deep sleep mode
self.hw_reset()
mvb = self._mvb
cmd = self._command
dat = self._data
cmd(b'\x24')
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)):
buf1[0] = ~mvb[idx]
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
else:
for b in mvb:
buf1[0] = ~b
dat(buf1)
if fast_refresh:
cmd(b'\x22', b'\xFF')
else:
cmd(b'\x22', b'\xF7')
sleep_us(20)
cmd(b'\x20') # DISPLAY_REFRESH
if lightsleep_while_waiting_for_refresh:
# set Pin hold=True is needed before entering lightsleep and after you must revert it back to hold=False
# without this, entering lightsleep results in a low state on the reset pin, and this resets the driver
self._rst = Pin(self._rst_pin, Pin.OUT, value=1, hold=True)
lightsleep(3000) # can be used to lowering consumption on ESP32
self._rst = Pin(self._rst_pin, Pin.OUT, value=1, hold=False)
self.wait_until_ready()
if deepsleep_after_refresh:
cmd(b'\x10', b'\x01')
else:
cmd(b'\x10', b'\x00')