kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
ePaper: Optimise driver, improve docs.
rodzic
1424c06bb6
commit
cbbededc60
37
README.md
37
README.md
|
@ -149,7 +149,7 @@ development so check for updates.
|
||||||
7.4 [Class TSequence](./README.md#74-class-tsequence) Plotting realtime, time sequential data.
|
7.4 [Class TSequence](./README.md#74-class-tsequence) Plotting realtime, time sequential data.
|
||||||
8. [ESP32 touch pads](./README.md#8-esp32-touch-pads) Replacing buttons with touch pads.
|
8. [ESP32 touch pads](./README.md#8-esp32-touch-pads) Replacing buttons with touch pads.
|
||||||
9. [Realtime applications](./README.md#9-realtime-applications) Accommodating tasks requiring fast RT performance.
|
9. [Realtime applications](./README.md#9-realtime-applications) Accommodating tasks requiring fast RT performance.
|
||||||
10. [ePaper displays](./README.md#10-epaper-displays) Using these techniques to provide a full refresh.
|
10. [ePaper displays](./README.md#10-epaper-displays) Guidance on using ePaper displays.
|
||||||
|
|
||||||
[Appendix 1 Application design](./README.md#appendix-1-application-design) Tab order, button layout, encoder interface, use of graphics primitives
|
[Appendix 1 Application design](./README.md#appendix-1-application-design) Tab order, button layout, encoder interface, use of graphics primitives
|
||||||
[Appendix 2 Freezing bytecode](./README.md#appendix-2-freezing-bytecode) Optional way to save RAM.
|
[Appendix 2 Freezing bytecode](./README.md#appendix-2-freezing-bytecode) Optional way to save RAM.
|
||||||
|
@ -506,10 +506,8 @@ Supported displays are as per
|
||||||
[the nano-gui list](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#12-description).
|
[the nano-gui list](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#12-description).
|
||||||
In general ePaper and Sharp displays are unlikely to yield good results because
|
In general ePaper and Sharp displays are unlikely to yield good results because
|
||||||
of slow and visually intrusive refreshing. However there is an exception: the
|
of slow and visually intrusive refreshing. However there is an exception: the
|
||||||
[Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm). This
|
[Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm). See
|
||||||
supports partial updates which work remarkably well with minimal ghosting. Note
|
[10. ePaper displays](./README.md#10-epaper-displays).
|
||||||
that it can be used with hosts other than the Pico via the supplied cable. See
|
|
||||||
[ePaper displays](./README.md#10-epaper-displays).
|
|
||||||
|
|
||||||
Display drivers are documented [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).
|
Display drivers are documented [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).
|
||||||
|
|
||||||
|
@ -3044,14 +3042,29 @@ The demo `gui/demos/audio.py` provides example usage.
|
||||||
|
|
||||||
# 10 ePaper displays
|
# 10 ePaper displays
|
||||||
|
|
||||||
|
In general ePaper displays do not work well with micro-gui because refresh is
|
||||||
|
slow (seconds) and visually intrusive. Some displays support partial refresh
|
||||||
|
which is faster (hundreds of ms) and non-intrusive. The penalty is "ghosting"
|
||||||
|
where pixels which change from black to white do so imperfectly, leaving a grey
|
||||||
|
trace behind. The degree of ghosting varies between display types.
|
||||||
|
|
||||||
The [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm)
|
The [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm)
|
||||||
is currently the only fully supported ePaper display, with a hardware_setup.py
|
has quite a low level of ghosting. A full refresh takes about 2.1s and partial
|
||||||
copied or adapted from `setup_examples/pico_epaper_42_pico.py`. After an
|
about 740ms. In use there is a visible lag between operating a user control and
|
||||||
initial refresh the driver is put into partial mode to provide reasonably
|
a visible response, but it is usable. Currently this is the only fully
|
||||||
quick and visually satisfactory response to button events. However ghosting may
|
supported ePaper display.
|
||||||
accumulate after long periods of running, and an application may occasionally
|
|
||||||
need to perform a full refresh. This requires the "done" interlock described
|
It has a socket for a Pico or Pico W, but also comes with a cable suitable for
|
||||||
in section 9.
|
connecting to any host. The hardware_setup.py should be copied or adapted from
|
||||||
|
`setup_examples/pico_epaper_42_pico.py`. If using the socket, default args may
|
||||||
|
be used (see code comment).
|
||||||
|
|
||||||
|
After an initial refresh to clear the screen the driver is put into partial
|
||||||
|
mode. This provides a reasonably quick and visually satisfactory response to
|
||||||
|
user inputs such as button events. See the
|
||||||
|
[epaper demo](https://github.com/peterhinch/micropython-micro-gui/blob/main/gui/demos/epaper.py).
|
||||||
|
This provides for a full refresh via the `reset` button. Provision of full
|
||||||
|
refresh is application dependent. It should be done as follows:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def full_refresh():
|
async def full_refresh():
|
||||||
|
|
|
@ -94,11 +94,14 @@ EPD_partial_lut_bb1 = b"\x00\x19\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\x00"
|
\x00\x00\x00\x00"
|
||||||
|
|
||||||
|
# Invert: EPD is black on white
|
||||||
|
# 483/241 us for 2000 bytes (125/250MHz)
|
||||||
|
# Can't extend to 32 bits becaue of long ints
|
||||||
@micropython.viper
|
@micropython.viper
|
||||||
def _linv(dest:ptr8, source:ptr8, length:int):
|
def _linv(dest:ptr16, source:ptr16, length:int):
|
||||||
for n in range(length):
|
for n in range(length):
|
||||||
c = source[n]
|
c: uint = source[n]
|
||||||
dest[n] = c ^ 0xFF
|
dest[n] = c ^ 0xFFFF
|
||||||
|
|
||||||
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
|
||||||
|
@ -122,6 +125,7 @@ class EPD(framebuf.FrameBuffer):
|
||||||
self.height = _EPD_HEIGHT
|
self.height = _EPD_HEIGHT
|
||||||
self.buf = bytearray(_EPD_HEIGHT * _BWIDTH)
|
self.buf = bytearray(_EPD_HEIGHT * _BWIDTH)
|
||||||
self.mvb = memoryview(self.buf)
|
self.mvb = memoryview(self.buf)
|
||||||
|
self.ibuf = bytearray(1000) # Buffer for inverted pixels
|
||||||
mode = framebuf.MONO_HLSB
|
mode = framebuf.MONO_HLSB
|
||||||
self.palette = BoolPalette(mode)
|
self.palette = BoolPalette(mode)
|
||||||
super().__init__(self.buf, _EPD_WIDTH, _EPD_HEIGHT, mode)
|
super().__init__(self.buf, _EPD_WIDTH, _EPD_HEIGHT, mode)
|
||||||
|
@ -252,44 +256,55 @@ class EPD(framebuf.FrameBuffer):
|
||||||
return not (self._busy or (self.busy_pin() == 0)) # 0 == busy
|
return not (self._busy or (self.busy_pin() == 0)) # 0 == busy
|
||||||
|
|
||||||
@micropython.native
|
@micropython.native
|
||||||
def _line(self, start, buf=bytearray(_BWIDTH)): # Sending 50 bytes 40us at 10MHz, 12ms for 300 lines
|
def _bsend(self, start, nbytes):
|
||||||
_linv(buf, self.mvb[start:], _BWIDTH) # Invert image data for EPD
|
buf = self.ibuf
|
||||||
|
_linv(buf, self.mvb[start:], nbytes >> 1) # Invert image data for EPD
|
||||||
self.send_bytes(buf)
|
self.send_bytes(buf)
|
||||||
|
|
||||||
# Timing @10MHz/250MHz: full refresh 2.1s, partial 740ms
|
# Time to convert and transmit 1000 bytes ~ 1ms: most of that is tx @ 10MHz
|
||||||
# Blocking with split=5 740/5=150ms
|
# Yield every 16 transfers means blocking is ~16ms
|
||||||
async def _as_show(self, split):
|
# Total convert and transmit time for 15000 bytes is ~15ms.
|
||||||
|
# Timing @10MHz/250MHz: full refresh 2.1s, partial 740ms: the bulk of the time
|
||||||
|
# is spent spinning on the busy pin and is CPU frequency independent.
|
||||||
|
async def _as_show(self):
|
||||||
self.send_command(b"\x13")
|
self.send_command(b"\x13")
|
||||||
lps = _EPD_HEIGHT // split
|
fbidx = 0 # Index into framebuf
|
||||||
idx = 0
|
nbytes = len(self.ibuf) # Bytes to send
|
||||||
#ttt = time.ticks_ms()
|
nleft = len(self.buf) # Size of framebuf
|
||||||
for _ in range(split): # For each segment
|
npass = 0
|
||||||
for _ in range(lps):
|
while nleft > 0:
|
||||||
self._line(idx)
|
self._bsend(fbidx, nbytes) # Invert, buffer and send nbytes
|
||||||
idx += _BWIDTH
|
fbidx += nbytes # Adjust for bytes already sent
|
||||||
await asyncio.sleep_ms(0)
|
nleft -= nbytes
|
||||||
#print("Time", time.ticks_diff(time.ticks_ms(), ttt))
|
nbytes = min(nbytes, nleft)
|
||||||
|
if not ((npass := npass + 1) % 16):
|
||||||
|
await asyncio.sleep_ms(0) # Control blocking time
|
||||||
self._updated.set()
|
self._updated.set()
|
||||||
self.send_command(b"\x12") # Nonblocking .display_on()
|
self.send_command(b"\x12") # Nonblocking .display_on()
|
||||||
while not self.busy_pin():
|
while not self.busy_pin(): # Wait on display hardware
|
||||||
await asyncio.sleep_ms(0) # About 1.7s for full refresh
|
await asyncio.sleep_ms(0)
|
||||||
self._busy = False
|
self._busy = False
|
||||||
#print("Time", time.ticks_diff(time.ticks_ms(), ttt)) # ~630ms
|
|
||||||
|
|
||||||
async def do_refresh(self, split): # For micro-gui
|
async def do_refresh(self, split): # For micro-gui
|
||||||
assert (not self._busy), "Refresh while busy"
|
assert (not self._busy), "Refresh while busy"
|
||||||
await self._as_show(split) # split=5
|
await self._as_show() # split=5
|
||||||
|
|
||||||
def show(self): # nanogui
|
def show(self): # nanogui
|
||||||
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 self._asyn:
|
||||||
asyncio.create_task(self._as_show(5)) # split into 5 segments
|
asyncio.create_task(self._as_show())
|
||||||
return
|
return
|
||||||
self.send_command(b"\x13")
|
self.send_command(b"\x13")
|
||||||
for j in range(_EPD_HEIGHT):
|
fbidx = 0 # Index into framebuf
|
||||||
self._line(j)
|
nbytes = len(self.ibuf) # Bytes to send
|
||||||
|
nleft = len(self.buf) # Size of framebuf
|
||||||
|
while nleft > 0:
|
||||||
|
self._bsend(fbidx, nbytes) # Invert, buffer and send nbytes
|
||||||
|
fbidx += nbytes # Adjust for bytes already sent
|
||||||
|
nleft -= nbytes
|
||||||
|
nbytes = min(nbytes, nleft)
|
||||||
self._busy = False
|
self._busy = False
|
||||||
self.display_on()
|
self.display_on()
|
||||||
self.wait_until_ready()
|
self.wait_until_ready()
|
||||||
|
|
Ładowanie…
Reference in New Issue