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
|
## 5.3 Waveshare 400x300 Pi Pico display
|
||||||
|
|
||||||
This display has excellent support for partial updates which are fast, visually
|
This display has excellent support for partial updates which are fast and
|
||||||
unobtrusive updates. They have the drawback of "ghosting" where the remnants of
|
visually unobtrusive. They have the drawback of "ghosting" where remnants of the
|
||||||
the previous image is visible. At any time a full update may be performed which
|
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
|
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
|
and thus is supported by micro-gui. The model supports hosts other than the Pico
|
||||||
via a supplied cable.
|
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
|
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
|
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
|
```python
|
||||||
import machine
|
import machine
|
||||||
import gc
|
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.
|
gc.collect() # Precaution before instantiating framebuf.
|
||||||
ssd = SSD() # Create a display instance based on a Pico in socket.
|
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`.
|
* `rst=None` A `Pin` instance defined as `Pin.OUT`.
|
||||||
* `busy=None` A `Pin` instance defined as `Pin.IN, Pin.PULL_UP`.
|
* `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
|
### 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_partial()` Enable partial updates (does nothing on greyscale driver).
|
||||||
* `set_full()` Restore normal update operation (null 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
|
On the 1-bit driver, after issuing `set_partial()`, subsequent updates will be
|
||||||
partial. Normal updates are restored by issuing `set_full()`. These methods
|
partial. Normal updates are restored by issuing `set_full()`. These methods
|
||||||
should not be issued while an update is in progress.
|
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
|
### 5.3.3 Events
|
||||||
|
|
||||||
These provide synchronisation in asynchronous applications. They are only
|
These provide synchronisation in asynchronous applications. They are only
|
||||||
needed in more advanced asynchronous applications and their use is discussed in
|
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
|
* `updated` Set when framebuf has been copied to device. It is now safe to
|
||||||
modify widgets without risk of display corruption.
|
modify widgets without risk of display corruption.
|
||||||
* `complete` Set when display update is complete. It is now safe to call
|
* `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
|
seconds to enable viewing. This enables generic nanogui demos to be run on an
|
||||||
EPD.
|
EPD.
|
||||||
|
|
||||||
Class variable:
|
The following are intended for use in micro-gui applications:
|
||||||
* `MAXBLOCK = 25` Defines the maximum period (in ms) that an asynchronous
|
|
||||||
|
* `maxblock=25` Defines the maximum period (in ms) that the asynchronous
|
||||||
refresh can block before yielding to the scheduler.
|
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
|
Note that in synchronous applications with `demo_mode=False`, `refresh` returns
|
||||||
while the display is updating. Applications should issue `wait_until_ready`
|
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
|
### 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.
|
* ~5mA while doing a full update.
|
||||||
* ~1.2mA while running the micro-gui epaper.py demo. This performs continuous
|
* ~1.2mA while running the micro-gui epaper.py demo. This performs continuous
|
||||||
partial updates.
|
partial updates.
|
||||||
* 92μA while inactive.
|
* 92μA while inactive.
|
||||||
* 92μA after running `.sleep`.
|
* 92μA after running `.sleep`.
|
||||||
Conclusion: there is no reason to call `.sleep` other than in preparation for a
|
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)
|
###### [Contents](./DRIVERS.md#contents)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# pico_epaper_42_v2.py
|
||||||
|
|
||||||
# Materials used for discovery can be found here
|
# Materials used for discovery can be found here
|
||||||
# https://www.waveshare.com/wiki/4.2inch_e-Paper_Module_Manual#Introduction
|
# 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
|
# 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,
|
# 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.
|
||||||
#
|
|
||||||
|
# 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
|
from machine import Pin, SPI
|
||||||
import framebuf
|
import framebuf
|
||||||
|
@ -69,7 +75,6 @@ def _linv(dest: ptr32, source: ptr32, length: int):
|
||||||
|
|
||||||
|
|
||||||
class EPD(framebuf.FrameBuffer):
|
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
|
# 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.
|
||||||
@staticmethod
|
@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._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._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._dc = Pin(_DC_PIN, Pin.OUT) if dc is None else dc
|
||||||
self._spi = (
|
self._spi = SPI(1, sck=Pin(10), mosi=Pin(11), miso=Pin(28)) if spi is None else 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)
|
||||||
# Busy flag: set immediately on .show(). Cleared when busy pin is logically false.
|
# Busy flag: set immediately on .show(). Cleared when busy pin is logically false.
|
||||||
self._busy = False
|
self._busy = False
|
||||||
# Async API
|
# Async API
|
||||||
self.updated = asyncio.Event()
|
self.updated = asyncio.Event()
|
||||||
self.complete = asyncio.Event()
|
self.complete = asyncio.Event()
|
||||||
|
self.maxblock = 25
|
||||||
# partial refresh
|
# partial refresh
|
||||||
self._partial = False
|
self._partial = False
|
||||||
|
|
||||||
|
@ -100,6 +104,7 @@ class EPD(framebuf.FrameBuffer):
|
||||||
# Other public bound variable.
|
# Other public bound variable.
|
||||||
# Special mode enables demos written for generic displays to run.
|
# Special mode enables demos written for generic displays to run.
|
||||||
self.demo_mode = False
|
self.demo_mode = False
|
||||||
|
self.blank_on_exit = True
|
||||||
|
|
||||||
self._buf = bytearray(_EPD_HEIGHT * _BWIDTH)
|
self._buf = bytearray(_EPD_HEIGHT * _BWIDTH)
|
||||||
self._mvb = memoryview(self._buf)
|
self._mvb = memoryview(self._buf)
|
||||||
|
@ -130,7 +135,7 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self._spi.write(data)
|
self._spi.write(data)
|
||||||
self._cs(1)
|
self._cs(1)
|
||||||
|
|
||||||
def display_on(self):
|
def _display_on(self):
|
||||||
if self._partial:
|
if self._partial:
|
||||||
self._command(b"\x22")
|
self._command(b"\x22")
|
||||||
self._data(b"\xFF")
|
self._data(b"\xFF")
|
||||||
|
@ -140,6 +145,7 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self._data(b"\xF7")
|
self._data(b"\xF7")
|
||||||
self._command(b"\x20")
|
self._command(b"\x20")
|
||||||
|
|
||||||
|
# Called by constructor. Application use is deprecated.
|
||||||
def init(self):
|
def init(self):
|
||||||
self.reset() # hardware reset
|
self.reset() # hardware reset
|
||||||
|
|
||||||
|
@ -147,8 +153,9 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self.wait_until_ready()
|
self.wait_until_ready()
|
||||||
|
|
||||||
self.set_full()
|
self.set_full()
|
||||||
self.display_on()
|
self._display_on()
|
||||||
|
|
||||||
|
# Common API
|
||||||
def set_full(self):
|
def set_full(self):
|
||||||
self._partial = False
|
self._partial = False
|
||||||
|
|
||||||
|
@ -194,10 +201,15 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self._spi.write(buf)
|
self._spi.write(buf)
|
||||||
self._cs(1)
|
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):
|
def _send_bytes(self):
|
||||||
fbidx = 0 # Index into framebuf
|
fbidx = 0 # Index into framebuf
|
||||||
nbytes = len(self._ibuf) # Bytes to send
|
nbytes = len(self._ibuf) # Bytes to send
|
||||||
nleft = len(self._buf) # Size of framebuf
|
nleft = len(self._buf) # Size of framebuf
|
||||||
|
asyn = asyncio_running()
|
||||||
|
|
||||||
def inner():
|
def inner():
|
||||||
nonlocal fbidx
|
nonlocal fbidx
|
||||||
nonlocal nbytes
|
nonlocal nbytes
|
||||||
|
@ -208,20 +220,30 @@ class EPD(framebuf.FrameBuffer):
|
||||||
fbidx += nbytes # Adjust for bytes already sent
|
fbidx += nbytes # Adjust for bytes already sent
|
||||||
nleft -= nbytes
|
nleft -= nbytes
|
||||||
nbytes = min(nbytes, nleft)
|
nbytes = min(nbytes, nleft)
|
||||||
if time.ticks_diff(time.ticks_ms(), ts) > EPD.MAXBLOCK:
|
if asyn and time.ticks_diff(time.ticks_ms(), ts) > self.maxblock:
|
||||||
return nbytes # Probably not all done; quit and call again
|
return nbytes # Probably not all done; quit. Caller yields, calls again
|
||||||
return 0 # All done
|
return 0 # All done
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
# Specific method for micro-gui. Unsuitable EPD's lack this method. Micro-gui
|
# micro-gui API; asyncio is running.
|
||||||
# does not test for asyncio as this is guaranteed to be up.
|
async def do_refresh(self, split): # split = 5
|
||||||
async def do_refresh(self, split):
|
|
||||||
assert not self._busy, "Refresh while busy"
|
assert not self._busy, "Refresh while busy"
|
||||||
if self._partial:
|
if self._partial:
|
||||||
await self._as_show_partial() # split=5
|
await self._as_show_partial()
|
||||||
else:
|
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):
|
def show(self):
|
||||||
if self._busy:
|
if self._busy:
|
||||||
raise RuntimeError("Cannot refresh: display is busy.")
|
raise RuntimeError("Cannot refresh: display is busy.")
|
||||||
|
@ -229,6 +251,12 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self._show_partial()
|
self._show_partial()
|
||||||
else:
|
else:
|
||||||
self._show_full()
|
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):
|
def _show_full(self):
|
||||||
self._busy = True # Immediate busy flag. Pin goes low much later.
|
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())
|
asyncio.create_task(self._as_show_full())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# asyncio is not running, hence sb() will not time out.
|
||||||
self._command(b"\x24")
|
self._command(b"\x24")
|
||||||
sb = self._send_bytes() # Instantiate closure
|
sb = self._send_bytes() # Instantiate closure
|
||||||
while sb():
|
sb() # Run to completion
|
||||||
pass
|
|
||||||
self._command(b"\x26")
|
self._command(b"\x26")
|
||||||
sb = self._send_bytes() # Instantiate closure
|
sb = self._send_bytes() # Create new instance
|
||||||
while sb():
|
sb()
|
||||||
pass
|
|
||||||
self._busy = False
|
self._busy = False
|
||||||
|
self._display_on()
|
||||||
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
|
|
||||||
|
|
||||||
async def _as_show_full(self):
|
async def _as_show_full(self):
|
||||||
self._command(b"\x24")
|
self._command(b"\x24")
|
||||||
sb = self._send_bytes() # Instantiate closure
|
sb = self._send_bytes() # Instantiate closure
|
||||||
while sb():
|
while sb():
|
||||||
await asyncio.sleep_ms(0)
|
await asyncio.sleep_ms(0) # Timed out. Yield and continue.
|
||||||
|
|
||||||
self._command(b"\x26")
|
self._command(b"\x26")
|
||||||
sb = self._send_bytes() # Instantiate closure
|
sb = self._send_bytes() # New closure instance
|
||||||
while sb():
|
while sb():
|
||||||
await asyncio.sleep_ms(0)
|
await asyncio.sleep_ms(0)
|
||||||
|
|
||||||
self.updated.set()
|
self.updated.set()
|
||||||
self.display_on()
|
self._display_on()
|
||||||
while self._busy_pin():
|
while self._busy_pin():
|
||||||
await asyncio.sleep_ms(0)
|
await asyncio.sleep_ms(0)
|
||||||
self._busy = False
|
self._busy = False
|
||||||
|
@ -286,19 +304,9 @@ class EPD(framebuf.FrameBuffer):
|
||||||
|
|
||||||
self._command(b"\x24")
|
self._command(b"\x24")
|
||||||
sb = self._send_bytes() # Instantiate closure
|
sb = self._send_bytes() # Instantiate closure
|
||||||
while sb():
|
sb()
|
||||||
pass
|
|
||||||
self._busy = False
|
self._busy = False
|
||||||
|
self._display_on()
|
||||||
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
|
|
||||||
|
|
||||||
async def _as_show_partial(self):
|
async def _as_show_partial(self):
|
||||||
self._command(b"\x24")
|
self._command(b"\x24")
|
||||||
|
@ -307,12 +315,13 @@ class EPD(framebuf.FrameBuffer):
|
||||||
await asyncio.sleep_ms(0)
|
await asyncio.sleep_ms(0)
|
||||||
|
|
||||||
self.updated.set()
|
self.updated.set()
|
||||||
self.display_on()
|
self._display_on()
|
||||||
while self._busy_pin():
|
while self._busy_pin():
|
||||||
await asyncio.sleep_ms(0)
|
await asyncio.sleep_ms(0)
|
||||||
self._busy = False
|
self._busy = False
|
||||||
self.complete.set()
|
self.complete.set()
|
||||||
|
|
||||||
|
# nanogui API
|
||||||
def wait_until_ready(self):
|
def wait_until_ready(self):
|
||||||
while not self.ready():
|
while not self.ready():
|
||||||
time.sleep_ms(100)
|
time.sleep_ms(100)
|
||||||
|
|
Ładowanie…
Reference in New Issue