diff --git a/DRIVERS.md b/DRIVERS.md index 205164b..8abec9c 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -1039,8 +1039,8 @@ see below. * `rst` An initialised output pin. Initial value should be 1. * `busy` An initialised input pin. * `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 @@ -1058,9 +1058,9 @@ All methods are synchronous. ### 5.1.3 Events -These provide synchronisation in asynchronous applications where `asyn=True`. -They are only needed in more advanced asynchronous applications and their use -is discussed in [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). +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). * `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 @@ -1227,8 +1227,8 @@ Pins 26-40 unused and omitted. * `rst` An initialised output pin. Initial value should be 1. * `busy` An initialised input pin. * `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 @@ -1246,9 +1246,9 @@ All methods are synchronous. ### 5.2.3 Events -These provide synchronisation in asynchronous applications where `asyn=True`. -They are only needed in more advanced asynchronous applications and their use -is discussed in [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). +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). * `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 @@ -1280,8 +1280,7 @@ import gc from drivers.epaper.pico_epaper_42 import EPD as SSD gc.collect() # Precaution before instantiating framebuf. -ssd = SSD() # Create a display instance. For normal applications. -# ssd = SSD(asyn=True) # Alternative for asynchronous applications. +ssd = SSD() # Create a display instance based on a Pico in socket. ``` ### 5.3.1 Constructor args @@ -1293,8 +1292,8 @@ following constructor args: * `dc=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`. - * `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 @@ -1321,9 +1320,9 @@ ghosting. ### 5.3.3 Events -These provide synchronisation in asynchronous applications where `asyn=True`. -They are only needed in more advanced asynchronous applications and their use -is discussed in [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support). +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). * `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 @@ -1349,8 +1348,6 @@ before issuing another refresh. The following applies to nano-gui. Under micro-gui the update mechanism is a background task. Use with micro-gui is covered [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 ```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 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 -modified if, in `color_setup.py`, an `EPD` is instantiated with `asyn=True`. In -this case `refresh` calls the `show` method as before, but `show` creates a -task `._as_show` and returns immediately. The task yields to the scheduler as -necessary to ensure that blocking is limited to around 30ms. If screen updates -take place at a low rate the only precaution necessary is to ensure that -sufficient time elapses between calls to `ssd.refresh()` for the update to -complete. For example the following code fragment illustrates an application -which performs a full EPD refresh once per minute: +This long blocking period is not ideal in asynchronous code. If `refresh` is +called from a task, `refresh` calls the `show` method as before, but `show` +creates a task `._as_show` and returns immediately. The task yields to the +scheduler as necessary to ensure that blocking is limited to around 30ms. If +screen updates take place at a low rate the only precaution necessary is to +ensure that sufficient time elapses between calls to `ssd.refresh()` for the +update to complete. For example the following code fragment illustrates an +application which performs a full EPD refresh once per minute: ```python async def run(): @@ -1382,7 +1378,7 @@ async def run(): ssd.refresh() # Launches background refresh 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 fragment assumes an application with a single task performing refreshes. The diff --git a/drivers/epaper/epaper2in7_fb.py b/drivers/epaper/epaper2in7_fb.py index 8da044d..3d44841 100644 --- a/drivers/epaper/epaper2in7_fb.py +++ b/drivers/epaper/epaper2in7_fb.py @@ -3,7 +3,7 @@ # EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. # 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 # Based on the following sources: @@ -16,6 +16,13 @@ import framebuf import uasyncio as asyncio 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): # A monochrome approach should be used for coding this. The rgb method ensures # nothing breaks if users specify colors. @@ -23,6 +30,7 @@ class EPD(framebuf.FrameBuffer): def rgb(r, g, b): 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): self._spi = spi self._cs = cs # Pins @@ -30,7 +38,6 @@ class EPD(framebuf.FrameBuffer): self._rst = rst self._busy = busy 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.updated = asyncio.Event() self.complete = asyncio.Event() @@ -198,7 +205,7 @@ class EPD(framebuf.FrameBuffer): # draw the current frame memory. Blocking time ~180ms def show(self, buf1=bytearray(1)): - if self._asyn: + if asyncio_running(): if self._as_busy: raise RuntimeError('Cannot refresh: display is busy.') self._as_busy = True diff --git a/drivers/epaper/epd29.py b/drivers/epaper/epd29.py index 9f416ee..9469302 100644 --- a/drivers/epaper/epd29.py +++ b/drivers/epaper/epd29.py @@ -4,7 +4,7 @@ # 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 # Based on the following sources: @@ -22,7 +22,14 @@ import uasyncio as asyncio from micropython import const 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): # 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): 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): self._spi = spi self._cs = cs # Pins @@ -38,7 +46,6 @@ class EPD(framebuf.FrameBuffer): self._rst = rst # Active low. self._busy = busy # Active low on IL0373 self._lsc = landscape - self._asyn = asyn # ._as_busy is set immediately on start of task. Cleared # when busy pin is logically false (physically 1). self._as_busy = False @@ -165,7 +172,7 @@ class EPD(framebuf.FrameBuffer): # draw the current frame memory. def show(self, buf1=bytearray(1)): - if self._asyn: + if asyncio_running(): if self._as_busy: raise RuntimeError('Cannot refresh: display is busy.') self._as_busy = True # Immediate busy flag. Pin goes low much later. diff --git a/drivers/epaper/pico_epaper_42.py b/drivers/epaper/pico_epaper_42.py index dc67895..4ca8521 100644 --- a/drivers/epaper/pico_epaper_42.py +++ b/drivers/epaper/pico_epaper_42.py @@ -45,6 +45,13 @@ import time import uasyncio as asyncio from drivers.boolpalette import BoolPalette +def asyncio_running(): + try: + _ = asyncio.current_task() + except: + return False + return True + # Display resolution _EPD_WIDTH = const(400) _BWIDTH = _EPD_WIDTH // 8 @@ -111,6 +118,7 @@ class EPD(framebuf.FrameBuffer): def rgb(r, g, b): 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): 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 @@ -118,7 +126,6 @@ class EPD(framebuf.FrameBuffer): 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.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.updated = asyncio.Event() self.complete = asyncio.Event() @@ -284,7 +291,9 @@ class EPD(framebuf.FrameBuffer): self._busy = False 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" await self._as_show() # split=5 @@ -292,7 +301,7 @@ class EPD(framebuf.FrameBuffer): if self._busy: raise RuntimeError('Cannot refresh: display is busy.') self._busy = True # Immediate busy flag. Pin goes low much later. - if self._asyn: + if asyncio_running(): self.updated.clear() self.complete.clear() asyncio.create_task(self._as_show()) diff --git a/setup_examples/epd29_pyb_async.py b/setup_examples/epd29_pyb_async.py index 4e1c4f6..b3283d1 100644 --- a/setup_examples/epd29_pyb_async.py +++ b/setup_examples/epd29_pyb_async.py @@ -38,4 +38,4 @@ pbusy = machine.Pin('Y4', machine.Pin.IN) # Datasheet P35 indicates up to 10MHz. spi = machine.SPI(2, baudrate=5_000_000) 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 diff --git a/setup_examples/epd_waveshare_42_pico.py b/setup_examples/epd_waveshare_42_pico.py index d365fa8..79b8f78 100644 --- a/setup_examples/epd_waveshare_42_pico.py +++ b/setup_examples/epd_waveshare_42_pico.py @@ -13,6 +13,6 @@ import gc from drivers.epaper.pico_epaper_42 import EPD as SSD gc.collect() # Precaution before instantiating framebuf -# Set asyn True to run asynchronous code such as epd_async.py -# Set False for normal synchronous code e.g. other demos. -ssd = SSD(asyn=True) # Create a display instance +ssd = SSD() # Create a display instance +# Set this to run demos written for arbitrary displays: +# ssd.demo_mode = True diff --git a/setup_examples/waveshare_pyb.py b/setup_examples/waveshare_pyb.py index b94a6ca..fb8f625 100644 --- a/setup_examples/waveshare_pyb.py +++ b/setup_examples/waveshare_pyb.py @@ -32,5 +32,5 @@ prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 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 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