Improved display driver pico_epaper_42.py.

pull/9/merge
peterhinch 2023-03-20 11:53:06 +00:00
rodzic e89075a569
commit c019f35ab4
4 zmienionych plików z 105 dodań i 326 usunięć

Wyświetl plik

@ -1218,6 +1218,8 @@ Pins 26-40 unused and omitted.
## 5.3 Waveshare 400x300 Pi Pico display ## 5.3 Waveshare 400x300 Pi Pico display
The driver for this display now supports partial updates.
This 4.2" display supports a Pi Pico or Pico W plugged into the rear of the This 4.2" display supports a Pi Pico or Pico W plugged into the rear of the
unit. Alternatively it can be connected to any other host using the supplied unit. Alternatively it can be connected to any other host using the supplied
cable. With a Pico variant the `color_setup` file is very simple: cable. With a Pico variant the `color_setup` file is very simple:
@ -1251,24 +1253,24 @@ following constructor args:
* `ready` No args. After issuing a `refresh` the device will become busy for * `ready` No args. After issuing a `refresh` the device will become busy for
a period: `ready` status should be checked before issuing `refresh`. a period: `ready` status should be checked before issuing `refresh`.
* `wait_until_ready` No args. Pause until the device is ready. * `wait_until_ready` No args. Pause until the device is ready.
* `set_partial()` Enable partial updates.
* `set_full()` Restore normal update operation.
##### Asynchronous methods After issuing `set_partial()`, subsequent updates will be partial. Normal
updates are restored by issuing `set_full()`. These methods should not be
* `updated` Asynchronous. No args. Pause until the framebuffer has been copied issued while an update is in progress.
to the display.
* `wait` Asynchronous. No args. Pause until the display refresh is complete.
An alternative driver supporting partial updates is `pico_epaper_42_part.py`.
Usage is as above, but the driver supports two methods:
* `set_partial()`
* `set_full()`
After issuing `set_partial()`, subsequent updates will be partial. Normal
updates are restored by issuing `set_full()`. These should not be issued while
an update is in progress.
Partial updates are fast and visually unobtrusive but they are prone to Partial updates are fast and visually unobtrusive but they are prone to
ghosting. ghosting.
##### Asynchronous methods
* `wait` No args. If an update is in progress, pause until the display refresh
is complete, otherwise return is immediate.
* `updated` No args. Pause until the framebuffer has been copied to the
display. It is now safe to modify the framebuf, but display update may still
be in progress.
###### [Contents](./DRIVERS.md#contents) ###### [Contents](./DRIVERS.md#contents)
# 6. EPD Asynchronous support # 6. EPD Asynchronous support

Wyświetl plik

@ -61,6 +61,7 @@ display.
3.1 [Application Initialisation](./README.md#31-application-initialisation) Initial setup and refresh method. 3.1 [Application Initialisation](./README.md#31-application-initialisation) Initial setup and refresh method.
     3.1.1 [User defined colors](./README.md#311-user-defined-colors)      3.1.1 [User defined colors](./README.md#311-user-defined-colors)
     3.1.2 [Monochrome displays](./README.md#312-monochrome-displays) A slight "gotcha" with ePaper.      3.1.2 [Monochrome displays](./README.md#312-monochrome-displays) A slight "gotcha" with ePaper.
     3.1.3 [Display update mechanism](./README.md#313-display-update-mechanism) How updates are managed.
3.2 [Label class](./README.md#32-label-class) Dynamic text at any screen location. 3.2 [Label class](./README.md#32-label-class) Dynamic text at any screen location.
3.3 [Meter class](./README.md#33-meter-class) A vertical panel meter. 3.3 [Meter class](./README.md#33-meter-class) A vertical panel meter.
3.4 [LED class](./README.md#34-led-class) Virtual LED of any color. 3.4 [LED class](./README.md#34-led-class) Virtual LED of any color.
@ -113,6 +114,7 @@ my GUI's employ the American spelling of `color`.
## 1.1 Change log ## 1.1 Change log
15 Mar 2023 Driver update to 4.2 inch Waveshare ePpaper display.
12 Feb 2023 Add support for sh1106 driver. Fix color compatibility of SSD1306. 12 Feb 2023 Add support for sh1106 driver. Fix color compatibility of SSD1306.
5 Sep 2022 Add support for additional Pico displays. 5 Sep 2022 Add support for additional Pico displays.
8 Aug 2022 Typo and grammar fixes from @bfiics. 8 Aug 2022 Typo and grammar fixes from @bfiics.
@ -492,6 +494,16 @@ color may come as a surprise.
In general the solution is to leave color settings at default. In general the solution is to leave color settings at default.
### 3.1.3 Display update mechanism
A typical application comprises various widgets displaying user data. When a
widget's `value` method is called, the framebuffer's contents are updated to
reflect the widget's current state. The framebuffer is transferred to the
physical hardware when `refresh(device)` is called. This allows multiple
widgets to be refreshed at the same time. It also minimises processor overhead:
`.value` is generally fast, while `refresh` is slow because of the time taken
to transfer an entire buffer over SPI.
###### [Contents](./README.md#contents) ###### [Contents](./README.md#contents)
## 3.2 Label class ## 3.2 Label class

Wyświetl plik

@ -1,8 +1,11 @@
# pico_epaper_42.py A 1-bit monochrome display driver for the Waveshare Pico # pico_epaper_42.py A 1-bit monochrome display driver for the Waveshare Pico
# ePaper 4.2" display.
# https://www.waveshare.com/pico-epaper-4.2.htm
# Adapted from the Waveshare driver by Peter Hinch Sept 2022. # ePaper 4.2" display. Version supports partial updates.
# Adapted from the Waveshare driver by Peter Hinch Sept 2022-March 2023.
# https://www.waveshare.com/pico-epaper-4.2.htm
# UC8176 manual https://www.waveshare.com/w/upload/8/88/UC8176.pdf
# Note that Waveshare's version of this driver may be out of date
# https://github.com/waveshare/Pico_ePaper_Code/blob/main/pythonNanoGui/drivers/ePaper4in2.py
# ***************************************************************************** # *****************************************************************************
# * | File : Pico_ePaper-3.7.py # * | File : Pico_ePaper-3.7.py
@ -31,7 +34,9 @@
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 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 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
#
# If .set_partial() is called, subsequent updates will be partial. To restore normal
# updates, issue .set_full()
from machine import Pin, SPI from machine import Pin, SPI
import framebuf import framebuf
@ -65,6 +70,29 @@ EPD_lut_wb = b"\xA0\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\x50\x08\x08\x00\
EPD_lut_bb = b"\x20\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\x10\x08\x08\x00\x00\x02\ 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" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# ******************************partial screen update LUT********************************* #
EPD_partial_lut_vcom1 = b"\x00\x19\x01\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"
EPD_partial_lut_ww1 = b"\x00\x19\x01\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"
EPD_partial_lut_bw1 =b"\x80\x19\x01\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"
EPD_partial_lut_wb1 = b"\x40\x19\x01\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"
EPD_partial_lut_bb1 = b"\x00\x19\x01\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"
class EPD(framebuf.FrameBuffer): class EPD(framebuf.FrameBuffer):
# A monochrome approach should be used for coding this. The rgb method ensures # A monochrome approach should be used for coding this. The rgb method ensures
# nothing breaks if users specify colors. # nothing breaks if users specify colors.
@ -77,10 +105,10 @@ class EPD(framebuf.FrameBuffer):
self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) if busy is None else busy 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.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.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 = SPI(1, sck = Pin(10), mosi=Pin(11), miso=Pin(28)) if spi is None else spi
self.spi.init(baudrate=4_000_000) self.spi.init(baudrate=4_000_000)
self._asyn = asyn self._asyn = asyn
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1). self._busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
self._updated = asyncio.Event() self._updated = asyncio.Event()
self.width = _EPD_WIDTH self.width = _EPD_WIDTH
@ -97,7 +125,7 @@ class EPD(framebuf.FrameBuffer):
def reset(self): def reset(self):
for v in (1, 0, 1): for v in (1, 0, 1):
self.reset_pin(v) self.reset_pin(v)
time.sleep_ms(20) time.sleep_ms(20)
def send_command(self, command): def send_command(self, command):
self.dc_pin(0) self.dc_pin(0)
@ -150,6 +178,22 @@ class EPD(framebuf.FrameBuffer):
self.send_command(b"\x50") # VCOM AND DATA INTERVAL SETTING 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_bytes(b"\x97") # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7
self.set_full()
# 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 set_full(self): # Normal full updates
self.send_command(b"\x20") self.send_command(b"\x20")
self.send_bytes(EPD_lut_vcom0) self.send_bytes(EPD_lut_vcom0)
@ -164,38 +208,41 @@ class EPD(framebuf.FrameBuffer):
self.send_command(b"\x24") self.send_command(b"\x24")
self.send_bytes(EPD_lut_bb) 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") def set_partial(self): # Partial updates
time.sleep_ms(10) self.send_command(b"\x20")
self.display_on() self.send_bytes(EPD_partial_lut_vcom1)
self.send_command(b"\x21")
self.send_bytes(EPD_partial_lut_ww1)
self.send_command(b"\x22")
self.send_bytes(EPD_partial_lut_bw1)
self.send_command(b"\x23")
self.send_bytes(EPD_partial_lut_wb1)
self.send_command(b"\x24")
self.send_bytes(EPD_partial_lut_bb1)
def wait_until_ready(self): def wait_until_ready(self):
while(not self.ready()): while not self.ready():
self.send_command(b"\x71")
time.sleep_ms(100) time.sleep_ms(100)
async def wait(self): async def wait(self):
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
while not self.ready(): while not self.ready():
self.send_command(b"\x71")
await asyncio.sleep_ms(100) await asyncio.sleep_ms(100)
# Pause until framebuf has been copied to device. # Pause until framebuf has been copied to device.
async def updated(self): async def updated(self):
self._updated.clear()
await self._updated.wait() await self._updated.wait()
self._updated.clear()
# For polling in asynchronous code. Just checks pin state. # For polling in asynchronous code. Just checks pin state.
# 0 == busy. Comment in official code is wrong. Code is correct. # 0 == busy. Comment in official code is wrong. Code is correct.
def ready(self): def ready(self):
return not(self._as_busy or (self.busy_pin() == 0)) # 0 == busy return not (self._busy or (self.busy_pin() == 0)) # 0 == busy
def _line(self, n, buf=bytearray(_BWIDTH)): def _line(self, n, buf=bytearray(_BWIDTH)):
img = self.mvb img = self.mvb
@ -206,27 +253,26 @@ class EPD(framebuf.FrameBuffer):
async def _as_show(self): async def _as_show(self):
self.send_command(b"\x13") self.send_command(b"\x13")
for j in range(_EPD_HEIGHT): # Loop blocks ~300ms for j in range(_EPD_HEIGHT): # Loop would block ~300ms
self._line(j) self._line(j)
# For some reason the following did not work await asyncio.sleep_ms(0)
#await asyncio.sleep_ms(0) self._updated.set()
self.send_command(b"\x12") # Async .display_on() self.send_command(b"\x12") # Async .display_on()
while not self.busy_pin(): while not self.busy_pin():
await asyncio.sleep_ms(10) # About 1.7s await asyncio.sleep_ms(10) # About 1.7s
self._updated.set() self._busy = False
self._updated.clear()
self._as_busy = False
def show(self): def show(self):
if self._busy:
raise RuntimeError('Cannot refresh: display is busy.')
self._busy = True # Immediate busy flag. Pin goes low much later.
if self._asyn: 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()) asyncio.create_task(self._as_show())
return return
self.send_command(b"\x13") self.send_command(b"\x13")
for j in range(_EPD_HEIGHT): for j in range(_EPD_HEIGHT):
self._line(j) self._line(j)
self._busy = False
self.display_on() self.display_on()
self.wait_until_ready() self.wait_until_ready()

Wyświetl plik

@ -1,281 +0,0 @@
# pico_epaper_42_part.py A 1-bit monochrome display driver for the Waveshare Pico
# ePaper 4.2" display. Version supports partial updates.
# Adapted from the Waveshare driver by Peter Hinch Sept 2022.
# https://www.waveshare.com/pico-epaper-4.2.htm
# UC8176 manual https://www.waveshare.com/w/upload/8/88/UC8176.pdf
# Note that Waveshare's version of this driver may be out of data
# https://github.com/waveshare/Pico_ePaper_Code/blob/main/pythonNanoGui/drivers/ePaper4in2.py
# *****************************************************************************
# * | File : Pico_ePaper-3.7.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2021-06-01
# # | 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.
# If .set_partial() is called, subsequent updates will be partial. To restore normal
# updates, issue .set_full()
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"
# ******************************partial screen update LUT********************************* #
EPD_partial_lut_vcom1 = b"\x00\x19\x01\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"
EPD_partial_lut_ww1 = b"\x00\x19\x01\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"
EPD_partial_lut_bw1 =b"\x80\x19\x01\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"
EPD_partial_lut_wb1 = b"\x40\x19\x01\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"
EPD_partial_lut_bb1 = b"\x00\x19\x01\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"
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, 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.set_full()
# 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 set_full(self): # Normal full updates
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)
def set_partial(self): # Partial updates
self.send_command(b"\x20")
self.send_bytes(EPD_partial_lut_vcom1)
self.send_command(b"\x21")
self.send_bytes(EPD_partial_lut_ww1)
self.send_command(b"\x22")
self.send_bytes(EPD_partial_lut_bw1)
self.send_command(b"\x23")
self.send_bytes(EPD_partial_lut_wb1)
self.send_command(b"\x24")
self.send_bytes(EPD_partial_lut_bb1)
def wait_until_ready(self):
while not self.ready():
time.sleep_ms(100)
async def wait(self):
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_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 block ~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")