kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
Minor changes to driver pico_epaper_42_v2.py.
rodzic
6f7a8112c8
commit
b9506de37a
63
DRIVERS.md
63
DRIVERS.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue