ePaper drivers test for uasyncio running.

pull/56/head
peterhinch 2023-05-12 18:03:01 +01:00
rodzic 35f8b23a52
commit 7be1073f48
7 zmienionych plików z 63 dodań i 44 usunięć

Wyświetl plik

@ -1039,8 +1039,8 @@ see below.
* `rst` An initialised output pin. Initial value should be 1. * `rst` An initialised output pin. Initial value should be 1.
* `busy` An initialised input pin. * `busy` An initialised input pin.
* `landscape=True` By default the long axis is horizontal. * `landscape=True` By default the long axis is horizontal.
* `asyn=False` Setting this `True` invokes an asynchronous mode. See
[EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). The `asyn` arg has been removed: the driver now detects asynchronous use.
### 5.1.2 Public methods ### 5.1.2 Public methods
@ -1058,9 +1058,9 @@ All methods are synchronous.
### 5.1.3 Events ### 5.1.3 Events
These provide synchronisation in asynchronous applications where `asyn=True`. These provide synchronisation in asynchronous applications. They are only
They are only needed in more advanced asynchronous applications and their use needed in more advanced asynchronous applications and their use is discussed in
is discussed in [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).
* `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
@ -1227,8 +1227,8 @@ Pins 26-40 unused and omitted.
* `rst` An initialised output pin. Initial value should be 1. * `rst` An initialised output pin. Initial value should be 1.
* `busy` An initialised input pin. * `busy` An initialised input pin.
* `landscape=False` By default the long axis is vertical. * `landscape=False` By default the long axis is vertical.
* `asyn=False` Setting this `True` invokes an asynchronous mode. See
[EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). The `asyn` arg has been removed: the driver now detects asynchronous use.
### 5.2.2 EPD public methods ### 5.2.2 EPD public methods
@ -1246,9 +1246,9 @@ All methods are synchronous.
### 5.2.3 Events ### 5.2.3 Events
These provide synchronisation in asynchronous applications where `asyn=True`. These provide synchronisation in asynchronous applications. They are only
They are only needed in more advanced asynchronous applications and their use needed in more advanced asynchronous applications and their use is discussed in
is discussed in [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).
* `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
@ -1280,8 +1280,7 @@ import gc
from drivers.epaper.pico_epaper_42 import EPD as SSD from drivers.epaper.pico_epaper_42 import EPD as SSD
gc.collect() # Precaution before instantiating framebuf. gc.collect() # Precaution before instantiating framebuf.
ssd = SSD() # Create a display instance. For normal applications. ssd = SSD() # Create a display instance based on a Pico in socket.
# ssd = SSD(asyn=True) # Alternative for asynchronous applications.
``` ```
### 5.3.1 Constructor args ### 5.3.1 Constructor args
@ -1293,8 +1292,8 @@ following constructor args:
* `dc=None` A `Pin` instance defined as `Pin.OUT`. * `dc=None` A `Pin` instance defined as `Pin.OUT`.
* `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`.
* `asyn=False` Set `True` for asynchronous applications. Leave `False` for
microgui where the arg has no effect. The `asyn` arg has been removed: the driver now detects asynchronous use.
### 5.3.2 Public methods ### 5.3.2 Public methods
@ -1321,9 +1320,9 @@ ghosting.
### 5.3.3 Events ### 5.3.3 Events
These provide synchronisation in asynchronous applications where `asyn=True`. These provide synchronisation in asynchronous applications. They are only
They are only needed in more advanced asynchronous applications and their use needed in more advanced asynchronous applications and their use is discussed in
is discussed in [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).
* `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
@ -1349,8 +1348,6 @@ before issuing another refresh.
The following applies to nano-gui. Under micro-gui the update mechanism is The following applies to nano-gui. Under micro-gui the update mechanism is
a background task. Use with micro-gui is covered a background task. Use with micro-gui is covered
[here](https://github.com/peterhinch/micropython-micro-gui/blob/main/README.md#10-epaper-displays). [here](https://github.com/peterhinch/micropython-micro-gui/blob/main/README.md#10-epaper-displays).
Further, the comments address the case where the driver is instantiated with
`asyn=True`.
When synchronous code issues When synchronous code issues
```python ```python
@ -1364,15 +1361,14 @@ refresh is complete. If `demo_mode` is set, device drivers block for an
additional 2 seconds to enable demos written for normal displays to work (the additional 2 seconds to enable demos written for normal displays to work (the
2 second pause allows the result of each refresh to be seen). 2 second pause allows the result of each refresh to be seen).
This long blocking period is not ideal in asynchronous code, and the process is This long blocking period is not ideal in asynchronous code. If `refresh` is
modified if, in `color_setup.py`, an `EPD` is instantiated with `asyn=True`. In called from a task, `refresh` calls the `show` method as before, but `show`
this case `refresh` calls the `show` method as before, but `show` creates a creates a task `._as_show` and returns immediately. The task yields to the
task `._as_show` and returns immediately. The task yields to the scheduler as scheduler as necessary to ensure that blocking is limited to around 30ms. If
necessary to ensure that blocking is limited to around 30ms. If screen updates screen updates take place at a low rate the only precaution necessary is to
take place at a low rate the only precaution necessary is to ensure that ensure that sufficient time elapses between calls to `ssd.refresh()` for the
sufficient time elapses between calls to `ssd.refresh()` for the update to update to complete. For example the following code fragment illustrates an
complete. For example the following code fragment illustrates an application application which performs a full EPD refresh once per minute:
which performs a full EPD refresh once per minute:
```python ```python
async def run(): async def run():
@ -1382,7 +1378,7 @@ async def run():
ssd.refresh() # Launches background refresh ssd.refresh() # Launches background refresh
await asyncio.sleep(60) await asyncio.sleep(60)
``` ```
With `asyn=True` other running tasks experience latency measured in tens of ms. Other running tasks experience latency measured in tens of ms.
Finer control is available using the two public bound `Event` instances. This Finer control is available using the two public bound `Event` instances. This
fragment assumes an application with a single task performing refreshes. The fragment assumes an application with a single task performing refreshes. The

Wyświetl plik

@ -3,7 +3,7 @@
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. # EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
# Optimisations to reduce allocations and RAM use. # Optimisations to reduce allocations and RAM use.
# Copyright (c) Peter Hinch 2020 # Copyright (c) Peter Hinch 2020-2023
# Released under the MIT license see LICENSE # Released under the MIT license see LICENSE
# Based on the following sources: # Based on the following sources:
@ -16,6 +16,13 @@ import framebuf
import uasyncio as asyncio import uasyncio as asyncio
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
def asyncio_running():
try:
_ = asyncio.current_task()
except:
return False
return True
class EPD(framebuf.FrameBuffer): class EPD(framebuf.FrameBuffer):
# 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.
@ -23,6 +30,7 @@ class EPD(framebuf.FrameBuffer):
def rgb(r, g, b): def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127)) return int((r > 127) or (g > 127) or (b > 127))
# Discard asyn: autodetect
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False): def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
self._spi = spi self._spi = spi
self._cs = cs # Pins self._cs = cs # Pins
@ -30,7 +38,6 @@ class EPD(framebuf.FrameBuffer):
self._rst = rst self._rst = rst
self._busy = busy self._busy = busy
self._lsc = landscape self._lsc = landscape
self._asyn = asyn
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1). self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
self.updated = asyncio.Event() self.updated = asyncio.Event()
self.complete = asyncio.Event() self.complete = asyncio.Event()
@ -198,7 +205,7 @@ class EPD(framebuf.FrameBuffer):
# draw the current frame memory. Blocking time ~180ms # draw the current frame memory. Blocking time ~180ms
def show(self, buf1=bytearray(1)): def show(self, buf1=bytearray(1)):
if self._asyn: if asyncio_running():
if self._as_busy: if self._as_busy:
raise RuntimeError('Cannot refresh: display is busy.') raise RuntimeError('Cannot refresh: display is busy.')
self._as_busy = True self._as_busy = True

Wyświetl plik

@ -4,7 +4,7 @@
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. # EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
# Copyright (c) Peter Hinch 2020 # Copyright (c) Peter Hinch 2020-2023
# Released under the MIT license see LICENSE # Released under the MIT license see LICENSE
# Based on the following sources: # Based on the following sources:
@ -22,7 +22,14 @@ import uasyncio as asyncio
from micropython import const from micropython import const
from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff
_MAX_BLOCK = const(20) # Maximum blocking time (ms) for asynchronous show. _def asyncio_running():
try:
_ = asyncio.current_task()
except:
return False
return True
MAX_BLOCK = const(20) # Maximum blocking time (ms) for asynchronous show.
class EPD(framebuf.FrameBuffer): class EPD(framebuf.FrameBuffer):
# 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
@ -31,6 +38,7 @@ class EPD(framebuf.FrameBuffer):
def rgb(r, g, b): def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127)) return int((r > 127) or (g > 127) or (b > 127))
# Discard asyn: autodetect
def __init__(self, spi, cs, dc, rst, busy, landscape=True, asyn=False): def __init__(self, spi, cs, dc, rst, busy, landscape=True, asyn=False):
self._spi = spi self._spi = spi
self._cs = cs # Pins self._cs = cs # Pins
@ -38,7 +46,6 @@ class EPD(framebuf.FrameBuffer):
self._rst = rst # Active low. self._rst = rst # Active low.
self._busy = busy # Active low on IL0373 self._busy = busy # Active low on IL0373
self._lsc = landscape self._lsc = landscape
self._asyn = asyn
# ._as_busy is set immediately on start of task. Cleared # ._as_busy is set immediately on start of task. Cleared
# when busy pin is logically false (physically 1). # when busy pin is logically false (physically 1).
self._as_busy = False self._as_busy = False
@ -165,7 +172,7 @@ class EPD(framebuf.FrameBuffer):
# draw the current frame memory. # draw the current frame memory.
def show(self, buf1=bytearray(1)): def show(self, buf1=bytearray(1)):
if self._asyn: if asyncio_running():
if self._as_busy: if self._as_busy:
raise RuntimeError('Cannot refresh: display is busy.') raise RuntimeError('Cannot refresh: display is busy.')
self._as_busy = True # Immediate busy flag. Pin goes low much later. self._as_busy = True # Immediate busy flag. Pin goes low much later.

Wyświetl plik

@ -45,6 +45,13 @@ import time
import uasyncio as asyncio import uasyncio as asyncio
from drivers.boolpalette import BoolPalette from drivers.boolpalette import BoolPalette
def asyncio_running():
try:
_ = asyncio.current_task()
except:
return False
return True
# Display resolution # Display resolution
_EPD_WIDTH = const(400) _EPD_WIDTH = const(400)
_BWIDTH = _EPD_WIDTH // 8 _BWIDTH = _EPD_WIDTH // 8
@ -111,6 +118,7 @@ class EPD(framebuf.FrameBuffer):
def rgb(r, g, b): def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127)) return int((r > 127) or (g > 127) or (b > 127))
# Discard asyn: autodetect
def __init__(self, spi=None, cs=None, dc=None, rst=None, busy=None, asyn=False): 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.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.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) if busy is None else busy
@ -118,7 +126,6 @@ class EPD(framebuf.FrameBuffer):
self.dc_pin = Pin(DC_PIN, Pin.OUT) if dc is None else dc self.dc_pin = 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) self.spi.init(baudrate = 4_000_000)
self._asyn = asyn
self._busy = False # Set immediately on .show(). Cleared when busy pin is logically false (physically 1). self._busy = False # Set immediately on .show(). Cleared when busy pin is logically false (physically 1).
self.updated = asyncio.Event() self.updated = asyncio.Event()
self.complete = asyncio.Event() self.complete = asyncio.Event()
@ -284,7 +291,9 @@ class EPD(framebuf.FrameBuffer):
self._busy = False self._busy = False
self.complete.set() self.complete.set()
async def do_refresh(self, split): # For micro-gui # 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):
assert (not self._busy), "Refresh while busy" assert (not self._busy), "Refresh while busy"
await self._as_show() # split=5 await self._as_show() # split=5
@ -292,7 +301,7 @@ class EPD(framebuf.FrameBuffer):
if self._busy: if self._busy:
raise RuntimeError('Cannot refresh: display is busy.') raise RuntimeError('Cannot refresh: display is busy.')
self._busy = True # Immediate busy flag. Pin goes low much later. self._busy = True # Immediate busy flag. Pin goes low much later.
if self._asyn: if asyncio_running():
self.updated.clear() self.updated.clear()
self.complete.clear() self.complete.clear()
asyncio.create_task(self._as_show()) asyncio.create_task(self._as_show())

Wyświetl plik

@ -38,4 +38,4 @@ pbusy = machine.Pin('Y4', machine.Pin.IN)
# Datasheet P35 indicates up to 10MHz. # Datasheet P35 indicates up to 10MHz.
spi = machine.SPI(2, baudrate=5_000_000) spi = machine.SPI(2, baudrate=5_000_000)
gc.collect() # Precaution before instantiating framebuf gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, pbusy, asyn=True) # Create a display instance ssd = SSD(spi, pcs, pdc, prst, pbusy) # Create a display instance

Wyświetl plik

@ -13,6 +13,6 @@ import gc
from drivers.epaper.pico_epaper_42 import EPD as SSD from drivers.epaper.pico_epaper_42 import EPD as SSD
gc.collect() # Precaution before instantiating framebuf gc.collect() # Precaution before instantiating framebuf
# Set asyn True to run asynchronous code such as epd_async.py ssd = SSD() # Create a display instance
# Set False for normal synchronous code e.g. other demos. # Set this to run demos written for arbitrary displays:
ssd = SSD(asyn=True) # Create a display instance # ssd.demo_mode = True

Wyświetl plik

@ -32,5 +32,5 @@ prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
pbusy = machine.Pin('Y4', machine.Pin.IN) pbusy = machine.Pin('Y4', machine.Pin.IN)
spi = machine.SPI(2, baudrate=4_000_000) # From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py spi = machine.SPI(2, baudrate=4_000_000) # From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py
gc.collect() # Precaution before instantiating framebuf gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, pbusy, landscape=False, asyn=True) # Create a display instance ssd = SSD(spi, pcs, pdc, prst, pbusy, landscape=False) # Create a display instance
#ssd.demo_mode = True #ssd.demo_mode = True