From c019f35ab4dce7cd7ffa7e8fac2ff949bc003d8f Mon Sep 17 00:00:00 2001 From: peterhinch Date: Mon, 20 Mar 2023 11:53:06 +0000 Subject: [PATCH] Improved display driver pico_epaper_42.py. --- DRIVERS.md | 28 +-- README.md | 12 ++ drivers/epaper/pico_epaper_42.py | 110 +++++++--- drivers/epaper/pico_epaper_42_part.py | 281 -------------------------- 4 files changed, 105 insertions(+), 326 deletions(-) delete mode 100644 drivers/epaper/pico_epaper_42_part.py diff --git a/DRIVERS.md b/DRIVERS.md index 92d9a22..19d34a1 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -1218,6 +1218,8 @@ Pins 26-40 unused and omitted. ## 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 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: @@ -1251,24 +1253,24 @@ following constructor args: * `ready` No args. After issuing a `refresh` the device will become busy for a period: `ready` status should be checked before issuing `refresh`. * `wait_until_ready` No args. Pause until the device is ready. + * `set_partial()` Enable partial updates. + * `set_full()` Restore normal update operation. -##### Asynchronous methods - - * `updated` Asynchronous. No args. Pause until the framebuffer has been copied - 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. + After issuing `set_partial()`, subsequent updates will be partial. Normal +updates are restored by issuing `set_full()`. These methods should not be +issued while an update is in progress. Partial updates are fast and visually unobtrusive but they are prone to 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) # 6. EPD Asynchronous support diff --git a/README.md b/README.md index ec9f1d4..fd192ef 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ display. 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.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.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. @@ -113,6 +114,7 @@ my GUI's employ the American spelling of `color`. ## 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. 5 Sep 2022 Add support for additional Pico displays. 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. +### 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) ## 3.2 Label class diff --git a/drivers/epaper/pico_epaper_42.py b/drivers/epaper/pico_epaper_42.py index cb55700..884804e 100644 --- a/drivers/epaper/pico_epaper_42.py +++ b/drivers/epaper/pico_epaper_42.py @@ -1,8 +1,11 @@ # 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 @@ -31,7 +34,9 @@ # 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 @@ -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\ \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. @@ -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.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 = 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._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.width = _EPD_WIDTH @@ -97,7 +125,7 @@ class EPD(framebuf.FrameBuffer): def reset(self): for v in (1, 0, 1): self.reset_pin(v) - time.sleep_ms(20) + time.sleep_ms(20) def send_command(self, command): self.dc_pin(0) @@ -150,6 +178,22 @@ class EPD(framebuf.FrameBuffer): 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) @@ -164,38 +208,41 @@ class EPD(framebuf.FrameBuffer): 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 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()): - self.send_command(b"\x71") + while not self.ready(): 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): + self._updated.clear() await self._updated.wait() + self._updated.clear() # 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 + return not (self._busy or (self.busy_pin() == 0)) # 0 == busy def _line(self, n, buf=bytearray(_BWIDTH)): img = self.mvb @@ -206,27 +253,26 @@ class EPD(framebuf.FrameBuffer): async def _as_show(self): 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) - # 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() while not self.busy_pin(): await asyncio.sleep_ms(10) # About 1.7s - self._updated.set() - self._updated.clear() - self._as_busy = False + self._busy = False 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._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._busy = False self.display_on() self.wait_until_ready() diff --git a/drivers/epaper/pico_epaper_42_part.py b/drivers/epaper/pico_epaper_42_part.py deleted file mode 100644 index 1627d63..0000000 --- a/drivers/epaper/pico_epaper_42_part.py +++ /dev/null @@ -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")