From 1424c06bb6f813a09656c06d07c2fd8872003b19 Mon Sep 17 00:00:00 2001 From: peterhinch Date: Thu, 13 Apr 2023 18:37:21 +0100 Subject: [PATCH] ePaper optimisations. --- README.md | 8 +++--- drivers/epaper/pico_epaper_42.py | 40 ++++++++++++++++++--------- gui/demos/epaper.py | 11 ++++---- hardware_setup.py | 4 +-- setup_examples/pico_epaper_42_pico.py | 4 +-- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6e21837..ea19e36 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ development so check for updates. 7.4 [Class TSequence](./README.md#74-class-tsequence) Plotting realtime, time sequential data. 8. [ESP32 touch pads](./README.md#8-esp32-touch-pads) Replacing buttons with touch pads. 9. [Realtime applications](./README.md#9-realtime-applications) Accommodating tasks requiring fast RT performance. - 9.1 [ePaper refresh](./README.md#91-epaper-refresh) Using these techniques to provide a full refresh. +10. [ePaper displays](./README.md#10-epaper-displays) Using these techniques to provide a full refresh. [Appendix 1 Application design](./README.md#appendix-1-application-design) Tab order, button layout, encoder interface, use of graphics primitives [Appendix 2 Freezing bytecode](./README.md#appendix-2-freezing-bytecode) Optional way to save RAM. @@ -509,7 +509,7 @@ of slow and visually intrusive refreshing. However there is an exception: the [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm). This supports partial updates which work remarkably well with minimal ghosting. Note that it can be used with hosts other than the Pico via the supplied cable. See -[ePaper refresh](./README.md#91-epaper-refresh). +[ePaper displays](./README.md#10-epaper-displays). Display drivers are documented [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md). @@ -3042,7 +3042,7 @@ another from occurring. ``` The demo `gui/demos/audio.py` provides example usage. -## 9.1 ePaper refresh +# 10 ePaper displays The [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm) is currently the only fully supported ePaper display, with a hardware_setup.py @@ -3051,7 +3051,7 @@ initial refresh the driver is put into partial mode to provide reasonably quick and visually satisfactory response to button events. However ghosting may accumulate after long periods of running, and an application may occasionally need to perform a full refresh. This requires the "done" interlock described -above. +in section 9. ```python async def full_refresh(): diff --git a/drivers/epaper/pico_epaper_42.py b/drivers/epaper/pico_epaper_42.py index 08d160b..e63cf14 100644 --- a/drivers/epaper/pico_epaper_42.py +++ b/drivers/epaper/pico_epaper_42.py @@ -94,6 +94,12 @@ 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" +@micropython.viper +def _linv(dest:ptr8, source:ptr8, length:int): + for n in range(length): + c = source[n] + dest[n] = c ^ 0xFF + class EPD(framebuf.FrameBuffer): # A monochrome approach should be used for coding this. The rgb method ensures # nothing breaks if users specify colors. @@ -245,33 +251,41 @@ class EPD(framebuf.FrameBuffer): def ready(self): return not (self._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 + @micropython.native + def _line(self, start, buf=bytearray(_BWIDTH)): # Sending 50 bytes 40us at 10MHz, 12ms for 300 lines + _linv(buf, self.mvb[start:], _BWIDTH) # Invert image data for EPD self.send_bytes(buf) - async def _as_show(self): + # Timing @10MHz/250MHz: full refresh 2.1s, partial 740ms + # Blocking with split=5 740/5=150ms + async def _as_show(self, split): self.send_command(b"\x13") - for j in range(_EPD_HEIGHT): # Loop would block ~300ms - self._line(j) + lps = _EPD_HEIGHT // split + idx = 0 + #ttt = time.ticks_ms() + for _ in range(split): # For each segment + for _ in range(lps): + self._line(idx) + idx += _BWIDTH await asyncio.sleep_ms(0) + #print("Time", time.ticks_diff(time.ticks_ms(), ttt)) self._updated.set() - self.send_command(b"\x12") # Async .display_on() + self.send_command(b"\x12") # Nonblocking .display_on() while not self.busy_pin(): - await asyncio.sleep_ms(10) # About 1.7s + await asyncio.sleep_ms(0) # About 1.7s for full refresh self._busy = False + #print("Time", time.ticks_diff(time.ticks_ms(), ttt)) # ~630ms async def do_refresh(self, split): # For micro-gui - await self._as_show() + assert (not self._busy), "Refresh while busy" + await self._as_show(split) # split=5 - def show(self): + def show(self): # nanogui if self._busy: raise RuntimeError('Cannot refresh: display is busy.') self._busy = True # Immediate busy flag. Pin goes low much later. if self._asyn: - asyncio.create_task(self._as_show()) + asyncio.create_task(self._as_show(5)) # split into 5 segments return self.send_command(b"\x13") for j in range(_EPD_HEIGHT): diff --git a/gui/demos/epaper.py b/gui/demos/epaper.py index d4ebf77..5fd1f72 100644 --- a/gui/demos/epaper.py +++ b/gui/demos/epaper.py @@ -119,7 +119,7 @@ class FooScreen(Screen): Checkbox(wri, row, col, callback=self.cbcb) col+= 40 self.led = LED(wri, row, col, color=YELLOW, bdcolor=GREEN) - CloseButton(wri) + CloseButton(wri, bgcolor=BLACK) asyncio.create_task(run(dial, lbltim, m0, scale)) @@ -174,10 +174,9 @@ async def run(dial, lbltim, m0, scale): def test(): - if ssd.height < 240 or ssd.width < 320: - print(' This test requires a display of at least 320x240 pixels.') - else: - print('Testing micro-gui...') - Screen.change(FooScreen) + print('Testing micro-gui...') + Screen.change(FooScreen) + print("End") + ssd.sleep() # Tidy shutdown of EPD test() diff --git a/hardware_setup.py b/hardware_setup.py index 0aa5a3c..b648a5b 100644 --- a/hardware_setup.py +++ b/hardware_setup.py @@ -29,7 +29,6 @@ from machine import Pin, SPI, freq import gc - from drivers.epaper.pico_epaper_42 import EPD as SSD freq(250_000_000) # RP2 overclock # Create and export an SSD instance @@ -37,7 +36,8 @@ prst = Pin(9, Pin.OUT, value=1) pcs = Pin(10, Pin.OUT, value=1) pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins busy = Pin(15, Pin.IN) -spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=4_000_000) +# Datasheet allows 10MHz +spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=10_000_000) gc.collect() # Precaution before instantiating framebuf # Using normal socket connection default args apply diff --git a/setup_examples/pico_epaper_42_pico.py b/setup_examples/pico_epaper_42_pico.py index 0aa5a3c..b648a5b 100644 --- a/setup_examples/pico_epaper_42_pico.py +++ b/setup_examples/pico_epaper_42_pico.py @@ -29,7 +29,6 @@ from machine import Pin, SPI, freq import gc - from drivers.epaper.pico_epaper_42 import EPD as SSD freq(250_000_000) # RP2 overclock # Create and export an SSD instance @@ -37,7 +36,8 @@ prst = Pin(9, Pin.OUT, value=1) pcs = Pin(10, Pin.OUT, value=1) pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins busy = Pin(15, Pin.IN) -spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=4_000_000) +# Datasheet allows 10MHz +spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=10_000_000) gc.collect() # Precaution before instantiating framebuf # Using normal socket connection default args apply