Minor changes to driver pico_epaper_42_v2.py.

pull/80/head
Peter Hinch 2024-07-19 13:56:20 +01:00
rodzic 6f7a8112c8
commit b9506de37a
2 zmienionych plików z 92 dodań i 70 usunięć

Wyświetl plik

@ -1379,9 +1379,9 @@ before issuing another refresh.
## 5.3 Waveshare 400x300 Pi Pico display
This display has excellent support for partial updates which are fast, visually
unobtrusive updates. They have the drawback of "ghosting" where the remnants of
the previous image is visible. At any time a full update may be performed which
This display has excellent support for partial updates which are fast and
visually unobtrusive. They have the drawback of "ghosting" where remnants of the
previous image are visible. At any time a full update may be performed which
removes all trace of ghosting. This model of display has low levels of ghosting
and thus is supported by micro-gui. The model supports hosts other than the Pico
via a supplied cable.
@ -1404,11 +1404,11 @@ All drivers have identical args and methods.
The 4.2" displays support 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:
cable. With a Pico variant plugged in the `color_setup` file is very simple:
```python
import machine
import gc
from drivers.epaper.pico_epaper_42 import EPD as SSD
from drivers.epaper.pico_epaper_42_v2 import EPD as SSD # V2 driver
gc.collect() # Precaution before instantiating framebuf.
ssd = SSD() # Create a display instance based on a Pico in socket.
@ -1424,33 +1424,39 @@ following constructor args:
* `rst=None` A `Pin` instance defined as `Pin.OUT`.
* `busy=None` A `Pin` instance defined as `Pin.IN, Pin.PULL_UP`.
The `asyn` arg has been removed: the driver now detects asynchronous use.
### 5.3.2 Public methods
All methods are synchronous.
All methods are synchronous. Common API (nanogui and microgui):
* `init` No args. Issues a hardware reset and initialises the hardware. This
is called by the constructor. It needs to explicitly be called to exit from a
deep sleep.
* `sleep` No args. Puts the display into deep sleep. `sleep` should be called
before a power down to avoid leaving the display in an abnormal state. See note
on current consumption.
* `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 (does nothing on greyscale driver).
* `set_full()` Restore normal update operation (null on greyscale driver).
On the 1-bit driver, 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.
On the 1-bit driver, 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. In the case of synchronous
applications, issue `.wait_until_ready`. Asynchronous and microgui applications
should wait on the `rfsh_done` event.
Nanogui API:
* `sleep` No args. Applications should call this before power down to ensure
the display is put into the correct state.
* `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. This should be
run before issuing `refresh` or `sleep`.
* `init` No args. Issues a hardware reset and initialises the hardware. This
is called by the constructor. It may be used to recover from a `sleep` state
but this is not recommended for V2 displays (see note on current consumption).
### 5.3.3 Events
These provide synchronisation in asynchronous applications. They are only
needed in more advanced asynchronous applications and their use is discussed in
[EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).
[EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). They are
necessary in microgui applications to synchronise changes between partial and
full refrresh modes. See
[this demo](https://github.com/peterhinch/micropython-micro-gui/blob/main/gui/demos/epaper.py).
* `updated` Set when framebuf has been copied to device. It is now safe to
modify widgets without risk of display corruption.
* `complete` Set when display update is complete. It is now safe to call
@ -1465,9 +1471,13 @@ needed in more advanced asynchronous applications and their use is discussed in
seconds to enable viewing. This enables generic nanogui demos to be run on an
EPD.
Class variable:
* `MAXBLOCK = 25` Defines the maximum period (in ms) that an asynchronous
The following are intended for use in micro-gui applications:
* `maxblock=25` Defines the maximum period (in ms) that the asynchronous
refresh can block before yielding to the scheduler.
* `blank_on_exit=True` On application shutdown by default the display is
cleared. Setting this `False` overrides this, leaving the display contents in
place.
Note that in synchronous applications with `demo_mode=False`, `refresh` returns
while the display is updating. Applications should issue `wait_until_ready`
@ -1499,14 +1509,17 @@ Color values of 0 (white) to 3 (black) can explicitly be specified.
### 5.3.6 Current consumption
This was measured on a V2 display.
This was measured on a V2 display. The Waveshare driver has a `sleep` method
which claims to put the device into a deep sleep mode. Their docs indicate a
sleep current of 0.01μA. This was not borne out by measurement:
* ~5mA while doing a full update.
* ~1.2mA while running the micro-gui epaper.py demo. This performs continuous
partial updates.
* 92μA while inactive.
* 92μA after running `.sleep`.
Conclusion: there is no reason to call `.sleep` other than in preparation for a
shutdown.
shutdown, consequently the method is not provided. I believe the discrepancy is
caused by the supply current of the level translator.
###### [Contents](./DRIVERS.md#contents)

Wyświetl plik

@ -1,3 +1,5 @@
# pico_epaper_42_v2.py
# Materials used for discovery can be found here
# https://www.waveshare.com/wiki/4.2inch_e-Paper_Module_Manual#Introduction
# Note, at the time of writing this, none of the source materials have working
@ -28,7 +30,11 @@
# 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.
#
# Waveshare URLs
# Main page: https://www.waveshare.com/pico-epaper-4.2.htm
# Wiki: https://www.waveshare.com/wiki/Pico-ePaper-4.2
# Code: https://github.com/waveshareteam/Pico_ePaper_Code/blob/main/python/Pico-ePaper-4.2_V2.py
from machine import Pin, SPI
import framebuf
@ -69,7 +75,6 @@ def _linv(dest: ptr32, source: ptr32, length: int):
class EPD(framebuf.FrameBuffer):
MAXBLOCK = 25 # Max async blocking time in ms
# A monochrome approach should be used for coding this. The rgb method ensures
# nothing breaks if users specify colors.
@staticmethod
@ -81,15 +86,14 @@ class EPD(framebuf.FrameBuffer):
self._busy_pin = Pin(_BUSY_PIN, Pin.IN, Pin.PULL_UP) if busy is None else busy
self._cs = Pin(_CS_PIN, Pin.OUT) if cs is None else cs
self._dc = Pin(_DC_PIN, Pin.OUT) if dc is None else dc
self._spi = (
SPI(1, sck=Pin(10), mosi=Pin(11), miso=Pin(28)) 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)
# Busy flag: set immediately on .show(). Cleared when busy pin is logically false.
self._busy = False
# Async API
self.updated = asyncio.Event()
self.complete = asyncio.Event()
self.maxblock = 25
# partial refresh
self._partial = False
@ -100,6 +104,7 @@ class EPD(framebuf.FrameBuffer):
# Other public bound variable.
# Special mode enables demos written for generic displays to run.
self.demo_mode = False
self.blank_on_exit = True
self._buf = bytearray(_EPD_HEIGHT * _BWIDTH)
self._mvb = memoryview(self._buf)
@ -130,7 +135,7 @@ class EPD(framebuf.FrameBuffer):
self._spi.write(data)
self._cs(1)
def display_on(self):
def _display_on(self):
if self._partial:
self._command(b"\x22")
self._data(b"\xFF")
@ -140,6 +145,7 @@ class EPD(framebuf.FrameBuffer):
self._data(b"\xF7")
self._command(b"\x20")
# Called by constructor. Application use is deprecated.
def init(self):
self.reset() # hardware reset
@ -147,8 +153,9 @@ class EPD(framebuf.FrameBuffer):
self.wait_until_ready()
self.set_full()
self.display_on()
self._display_on()
# Common API
def set_full(self):
self._partial = False
@ -194,10 +201,15 @@ class EPD(framebuf.FrameBuffer):
self._spi.write(buf)
self._cs(1)
# Send the frame buffer. If running asyncio, return whenever MAXBLOCK ms elapses
# so that caller can yield to the scheduler.
# Returns no. of bytes outstanding.
def _send_bytes(self):
fbidx = 0 # Index into framebuf
nbytes = len(self._ibuf) # Bytes to send
nleft = len(self._buf) # Size of framebuf
asyn = asyncio_running()
def inner():
nonlocal fbidx
nonlocal nbytes
@ -208,20 +220,30 @@ class EPD(framebuf.FrameBuffer):
fbidx += nbytes # Adjust for bytes already sent
nleft -= nbytes
nbytes = min(nbytes, nleft)
if time.ticks_diff(time.ticks_ms(), ts) > EPD.MAXBLOCK:
return nbytes # Probably not all done; quit and call again
if asyn and time.ticks_diff(time.ticks_ms(), ts) > self.maxblock:
return nbytes # Probably not all done; quit. Caller yields, calls again
return 0 # All done
return inner
# Specific method for micro-gui. Unsuitable EPD's lack this method. Micro-gui
# does not test for asyncio as this is guaranteed to be up.
async def do_refresh(self, split):
# micro-gui API; asyncio is running.
async def do_refresh(self, split): # split = 5
assert not self._busy, "Refresh while busy"
if self._partial:
await self._as_show_partial() # split=5
await self._as_show_partial()
else:
await self._as_show_full() # split=5
await self._as_show_full()
def shutdown(self, clear=False):
time.sleep(1) # Empirically necessary (ugh)
self.fill(0)
self.set_full()
if clear or self.blank_on_exit:
self.show()
self.wait_until_ready()
self.sleep()
# nanogui API
def show(self):
if self._busy:
raise RuntimeError("Cannot refresh: display is busy.")
@ -229,6 +251,12 @@ class EPD(framebuf.FrameBuffer):
self._show_partial()
else:
self._show_full()
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()
time.sleep_ms(2000) # Demo mode: give time for user to see result
def _show_full(self):
self._busy = True # Immediate busy flag. Pin goes low much later.
@ -238,39 +266,29 @@ class EPD(framebuf.FrameBuffer):
asyncio.create_task(self._as_show_full())
return
# asyncio is not running, hence sb() will not time out.
self._command(b"\x24")
sb = self._send_bytes() # Instantiate closure
while sb():
pass
sb() # Run to completion
self._command(b"\x26")
sb = self._send_bytes() # Instantiate closure
while sb():
pass
sb = self._send_bytes() # Create new instance
sb()
self._busy = False
self.display_on()
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()
time.sleep_ms(2000) # Demo mode: give time for user to see result
self._display_on()
async def _as_show_full(self):
self._command(b"\x24")
sb = self._send_bytes() # Instantiate closure
while sb():
await asyncio.sleep_ms(0)
await asyncio.sleep_ms(0) # Timed out. Yield and continue.
self._command(b"\x26")
sb = self._send_bytes() # Instantiate closure
sb = self._send_bytes() # New closure instance
while sb():
await asyncio.sleep_ms(0)
self.updated.set()
self.display_on()
self._display_on()
while self._busy_pin():
await asyncio.sleep_ms(0)
self._busy = False
@ -286,19 +304,9 @@ class EPD(framebuf.FrameBuffer):
self._command(b"\x24")
sb = self._send_bytes() # Instantiate closure
while sb():
pass
sb()
self._busy = False
self.display_on()
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()
time.sleep_ms(2000) # Demo mode: give time for user to see result
self._display_on()
async def _as_show_partial(self):
self._command(b"\x24")
@ -307,12 +315,13 @@ class EPD(framebuf.FrameBuffer):
await asyncio.sleep_ms(0)
self.updated.set()
self.display_on()
self._display_on()
while self._busy_pin():
await asyncio.sleep_ms(0)
self._busy = False
self.complete.set()
# nanogui API
def wait_until_ready(self):
while not self.ready():
time.sleep_ms(100)