Prior to merge.

pull/8/head
Peter Hinch 2021-01-15 16:54:29 +00:00
rodzic f4c6631c91
commit bfba0b71a6
11 zmienionych plików z 758 dodań i 67 usunięć

Wyświetl plik

@ -1,7 +1,7 @@
# Display drivers for nano-gui
The nano-gui project currently supports three display technologies: OLED (color
and monochrome), color TFT, and monochrome Sharp displays.
The nano-gui project currently supports four display technologies: OLED (color
and monochrome), color TFT, monochrome Sharp displays and EPD (ePaper/eInk).
# Contents
@ -23,10 +23,14 @@ and monochrome), color TFT, and monochrome Sharp displays.
     6.4.1 [Micropower applications](./DRIVERS.md#641-micropower-applications)
6.5 [Resources](./DRIVERS.md#65-resources)
7. [ePaper displays](./DRIVERS.md#7-epaper-displays)
7.1 [Waveshare eInk Display HAT](./DRIVERS.md#71-waveshare-eink-display-hat)
7.1 [Adafruit flexible eInk Display](./DRIVERS.md#71-adafruit-flexible-eink-display)
     7.1.1 [EPD constructor args](./DRIVERS.md#711-epd-constructor-args)
     7.1.2 [EPD public methods](./DRIVERS.md#712-epd-public-methods)
8. [Writing device drivers](./DRIVERS.md#8-writing-device-drivers)
7.2 [Waveshare eInk Display HAT](./DRIVERS.md#71-waveshare-eink-display-hat)
     7.2.1 [EPD constructor args](./DRIVERS.md#711-epd-constructor-args)
     7.2.2 [EPD public methods](./DRIVERS.md#712-epd-public-methods)
8. [EPD Asynchronous support](./DRIVERS.md#8-epd-asynchronous-support)
9. [Writing device drivers](./DRIVERS.md#8-writing-device-drivers)
###### [Main README](./README.md)
@ -505,11 +509,91 @@ device retaining the image indefinitely. Some devices such as the Waveshare
units perform the refresh internally. Earlier devices required the driver to
perform this, tying up the CPU for the duration.
## 7.1 Waveshare eInk Display HAT
The drivers are compatible with `uasyncio`. One approach is to use synchronous
methods only and the standard demos (some of which use `uasyncio`) may be run.
However copying the framebuffer to the device blocks for some time - 250ms or
more - which may be problematic for applications which need to respond to
external events. A specific asynchronous mode provides support for reducing
blocking time. See [EPD Asynchronous support](./DRIVERS.md#8-epd-asynchronous-support).
This 2.7" 176*274 portrait mode display is designed for the Raspberry Pi.
Details [here](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT). The driver
is cross-platform.
## 7.1 Adafruit flexible eInk Display
The driver assumes an Adafruit 2.9 inch 296*128 pixel flexible
[display](https://www.adafruit.com/product/4262) interfaced via their
[interface breakout](https://www.adafruit.com/product/4224).
This is currently my preferred ePaper setup, not least because the breakout
enables the display to be completely powered down. This facilitates micropower
applications: the host shuts down the display before going into deep sleep.
The driver is cross platform and supports landscape or portrait mode. To keep
the buffer size down (to 4736 bytes) there is no greyscale support.
##### Wiring
The following assumes a Pyboard host. Pyboard pin numbers are based on hardware
SPI 2 and my arbitrary choice of GPIO. All may be changed and soft SPI may be
used.
| Pyb | Breakout |
|:---:|:---------:|
| Vin | Vin (1) |
| Gnd | Gnd (3) |
| Y8 | MOSI (6) |
| Y6 | SCK (4) |
| Y4 | BUSY (11) | (Low = Busy)
| Y3 | RST (10) |
| Y2 | CS (7) |
| Y1 | DC (8) |
In normal use the `ENA` pin (12) may be left unconnected. For micropower use,
see below.
### 7.1.1 EPD constructor args
* `spi` An initialised SPI bus instance. The device can support clock rates of
upto 10MHz.
* `cs` An initialised output pin. Initial value should be 1.
* `dc` An initialised output pin. Initial value should be 0.
* `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#8-epd-asynchronous-support).
### 7.1.2 EPD public methods
##### Synchronous methods
* `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. If called while a refresh
is in progress it will block until the refresh is complete. `sleep` should be
called before a power down to avoid leaving the display in an abnormal 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.
##### Asynchronous methods
* `updated` Asynchronous. No args. Pause until the framebuffer has been copied
to the display.
* `wait` Asynchronous. No args. Pause until the display refresh is complete.
** POWER DOWN HARDWARE **
## 7.2 Waveshare eInk Display HAT
This 2.7" 176*274 display is designed for the Raspberry Pi and is detailed
[here](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT).
I bought two of these units from different sources. Both have hardware issues
discussed [here](https://forum.micropython.org/viewtopic.php?f=2&t=9564). I
have failed to achieve consistent behaviour. Units behave perfectly one day and
fail the next. I published this driver on the assumption that I was sold
dubious Chinese clones and that genuine ones would be reliable.
The driver is cross-platform.
##### Wiring
@ -520,7 +604,6 @@ connector is shown, with connections to a Pyboard to match `waveshare_setup.py`.
Connections may be adapted for other MicroPython targets. The board may be
powered from 5V or 3.3V: there is a regulator on board.
|:---:|:----:|:--:|:--:|:----:|:---:|
| Pyb | | L | R | | Pyb |
|:---:|:----:|:--:|:--:|:----:|:---:|
| Vin | VIN | 2 | 1 | | |
@ -538,24 +621,69 @@ powered from 5V or 3.3V: there is a regulator on board.
Pins 26-40 unused and omitted.
### 7.1.1 EPD constructor args
### 7.2.1 EPD constructor args
* `spi` An initialised SPI bus instance. The device can support clock rates of
upto 2MHz.
* `cs` An initialised output pin. Initial value should be 1.
* `dc` An initialised output pin. Initial value should be 0.
* `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`
### 7.1.2 EPD public methods
### 7.2.2 EPD public methods
##### Synchronous methods
* `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` No args. Puts the display into deep sleep. If called while a refresh
is in progress it will block until the refresh is complete. `sleep` should be
called before a power down to avoid leaving the display in an abnormal 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.
# 8. Writing device drivers
##### Asynchronous methods
* `updated` Asynchronous. No args. Pause until the framebuffer has been copied
to the display.
* `wait` Asynchronous. No args. Pause until the display refresh is complete.
# 8. EPD Asynchronous support
Normally when GUI code issues
```python
refresh(ssd)
```
display data is copied to the device and a physical refresh is initiated. The
code blocks - typically for 250ms or more - before returning, with physical
refresh being performed by the display hardware and taking several seconds.
This blocking period, which may be longer on non-Pyboard hosts, is too long for
many `uasyncio` applications.
If an `EPD` is instantiated with `asyn=True` the process of copying the data to
the device is performed by a task which periodically yields to the scheduler.
By default blocking is limited to around 30ms.
An `updated` method allows user code to pause after issuing `refresh` before
modifying the content of the framebuf - at which time the old data has been
entirely copied to the hardware. The `wait` method will pause until any
physical update is complete.
```python
while True:
# Normal procedure before refresh, but 10s sleep should mean it always returns immediately
await ssd.wait()
refresh(ssd) # Launches ._as_show()
await ssd.updated()
# Content has now been shifted out so coros can update
# framebuffer in background
evt.set()
evt.clear()
await asyncio.sleep(20) # Allow for slow refresh
```
# 9. Writing device drivers
Device drivers capable of supporting `nanogui` can be extremely simple: see
`drivers/sharp/sharp.py` for a minimal example. It should be noted that the

Wyświetl plik

@ -516,7 +516,7 @@ Keyword only args:
11. `label=None` A text string will cause a `Label` to be drawn below the
meter. An integer will create a `Label` of that width for later use.
12. `style=Meter.LINE` The pointer is a horizontal line. `Meter.BAR` causes a
vertical bar to be displayed.
vertical bar to be displayed. Much easier to read on monochrome displays.
13. `legends=None` If a tuple of strings is passed, `Label` instances will be
displayed to the right hand side of the meter, starting at the bottom. E.G.
`('0.0', '0.5', '1.0')`

Wyświetl plik

@ -0,0 +1,41 @@
# epd96_asyn.py Config for asynchronous applications on 2.9" ePaper.
# Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# Supports Adafruit 2.9" monochrome EPD with interface board connected to Pyboard.
# Interface breakout: https://www.adafruit.com/product/4224
# Display: https://www.adafruit.com/product/4262
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING. Adafruit schematic linked on the product web pagerefers to a different
# device. These are the pins on the physical board.
# Pyb Breakout
# Vin Vin (1)
# Gnd Gnd (3)
# Y8 MOSI (6)
# Y6 SCK (4)
# Y4 BUSY (11) (Low = Busy)
# Y3 RST (10)
# Y2 CS (7)
# Y1 DC (8)
import machine
import gc
from drivers.epaper.epd29 import EPD as SSD
pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
pbusy = machine.Pin('Y4', machine.Pin.IN)
# Baudrate. Adafruit use 1MHz at
# https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2
# 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

Wyświetl plik

@ -1,9 +1,12 @@
# epd96_demo.py Allow standard demos to run on ePaper. Customise for your hardware config
# epd96_demo.py Allow standard demos to run on ePaper.
# Customise for your hardware config.
# Beware of running demos for long as they refresh the display more frequently
# than is advised by Adafruit.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# As written, supports Adafruit 2.9" monochrome EPD with interface board.
# Supports Adafruit 2.9" monochrome EPD with interface board connected to Pyboard.
# Interface breakout: https://www.adafruit.com/product/4224
# Display: https://www.adafruit.com/product/4262
@ -11,8 +14,8 @@
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING. Adafruit schematic is incorrect in that it references a nonexistent
# SD card so that interface has an extra pin.
# WIRING. Adafruit schematic linked on the product web pagerefers to a different
# device. These are the pins on the physical board.
# Pyb Breakout
# Vin Vin (1)
# Gnd Gnd (3)
@ -32,9 +35,10 @@ pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
pbusy = machine.Pin('Y4', machine.Pin.IN)
# Baudrate from https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2
# Baudrate. Adafruit use 1MHz at
# https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2
# Datasheet P35 indicates up to 10MHz.
spi = machine.SPI(2, baudrate=1_000_000)
spi = machine.SPI(2, baudrate=5_000_000)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, pbusy) # Create a display instance
ssd.demo_mode = True

Wyświetl plik

@ -0,0 +1,36 @@
# waveshare_demo.py Allow standard demos to run on ePaper. Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# As written, supports:
# Waveshare 2.7" 264h*176w monochrome ePaper display:
# https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING Pin numbers refer to RPI connector.
# Pyb ePaper
# Vin Vcc (2)
# Gnd Gnd (9)
# Y8 DIN MOSI (19)
# Y6 CLK SCK (23)
# Y4 BUSY (18) (Low = Busy)
# Y3 RST (11)
# Y2 CS (24)
# Y1 DC (22)
import machine
import gc
from drivers.epaper.epaper2in7_fb import EPD as SSD
pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
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=True) # Create a display instance
ssd.demo_mode = True

Wyświetl plik

@ -0,0 +1,36 @@
# waveshare_setup.py Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# As written, supports:
# Waveshare 2.7" 264h*176w monochrome ePaper display:
# https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING Pin numbers refer to RPI connector.
# Pyb ePaper
# Vin Vcc (2)
# Gnd Gnd (9)
# Y8 DIN MOSI (19)
# Y6 CLK SCK (23)
# Y4 BUSY (18) (Low = Busy)
# Y3 RST (11)
# Y2 CS (24)
# Y1 DC (22)
import machine
import gc
from drivers.epaper.epaper2in7_fb import EPD as SSD
pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
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.demo_mode = True

Wyświetl plik

@ -0,0 +1,302 @@
# epaper2in7_fb.py nanogui driver for ePpaper 2.7" display
# Tested with Pyboard linked to Raspberry Pi 2.7" E-Ink Display HAT
# 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
# Released under the MIT license see LICENSE
# Based on the following sources:
# https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT
# MicroPython Waveshare 2.7" Black/White GDEW027W3 e-paper display driver
# https://github.com/mcauser/micropython-waveshare-epaper referred to as "mcauser"
# https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py ("official")
import framebuf
import uasyncio as asyncio
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
class EPD(framebuf.FrameBuffer):
# A monochrome approach should be used for coding this. The rgb method ensures
# nothing breaks if users specify colors.
@staticmethod
def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
self._spi = spi
self._cs = cs # Pins
self._dc = dc
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()
# Dimensions in pixels. Waveshare code is portrait mode.
# Public bound variables required by nanogui.
self.width = 264 if landscape else 176
self.height = 176 if landscape else 264
self.demo_mode = False # Special mode enables demos to run
self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
super().__init__(self._buffer, self.width, self.height, mode)
self.init()
def _command(self, command, data=None):
self._dc(0)
self._cs(0)
self._spi.write(command)
self._cs(1)
if data is not None:
self._dc(1)
self._cs(0)
self._spi.write(data)
self._cs(1)
def init(self):
# Hardware reset
self._rst(1)
sleep_ms(200)
self._rst(0)
sleep_ms(200) # 5ms in Waveshare code
self._rst(1)
sleep_ms(200)
# Initialisation
cmd = self._command
cmd(b'\x01', b'\x03\x00\x2B\x2B\x09') # POWER_SETTING: VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR
cmd(b'\x06', b'\x07\x07\x17') # BOOSTER_SOFT_START
cmd(b'\xf8', b'\x60\xA5') # POWER_OPTIMIZATION
cmd(b'\xf8', b'\x89\xA5')
cmd(b'\xf8', b'\x90\x00')
cmd(b'\xf8', b'\x93\x2A')
cmd(b'\xf8', b'\xA0\xA5')
cmd(b'\xf8', b'\xA1\x00')
cmd(b'\xf8', b'\x73\x41')
cmd(b'\x16', b'\x00') # PARTIAL_DISPLAY_REFRESH
cmd(b'\x04') # POWER_ON
self.wait_until_ready()
cmd(b'\x00', b'\xAF') # PANEL_SETTING: KW-BF, KWR-AF, BWROTP 0f
cmd(b'\x30', b'\x3A') # PLL_CONTROL: 3A 100HZ, 29 150Hz, 39 200HZ 31 171HZ
cmd(b'\x50', b'\x57') # Vcom and data interval setting (PGH)
cmd(b'\x82', b'\x12') # VCM_DC_SETTING_REGISTER
sleep_ms(2) # No delay in official code
# Set LUT. Local bytes objects reduce RAM usage.
# Values used by mcauser
#lut_vcom_dc =\
#b'\x00\x00\x00\x0F\x0F\x00\x00\x05\x00\x32\x32\x00\x00\x02\x00'\
#b'\x0F\x0F\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
#b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#lut_ww =\
#b'\x50\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\xA0\x0F\x0F'\
#b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
#b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R21H
#lut_bb =\
#b'\xA0\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\x50\x0F\x0F'\
#b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
#b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R24H b
# Values from official code:
lut_vcom_dc =\
b'\x00\x00\x00\x08\x00\x00\x00\x02\x60\x28\x28\x00\x00\x01\x00'\
b'\x14\x00\x00\x00\x01\x00\x12\x12\x00\x00\x01\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
lut_ww =\
b'\x40\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x40\x14\x00'\
b'\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
lut_bb =\
b'\x80\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x80\x14\x00'\
b'\x00\x00\x01\x50\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# Both agree on this:
lut_bw = lut_ww # R22H r
lut_wb = lut_bb # R23H w
cmd(b'\x20', lut_vcom_dc) # LUT_FOR_VCOM vcom
cmd(b'\x21', lut_ww) # LUT_WHITE_TO_WHITE ww --
cmd(b'\x22', lut_bw) # LUT_BLACK_TO_WHITE bw r
cmd(b'\x23', lut_bb) # LUT_WHITE_TO_BLACK wb w
cmd(b'\x24', lut_wb) # LUT_BLACK_TO_BLACK bb b
print('Init Done.')
def wait_until_ready(self):
sleep_ms(50)
t = ticks_ms()
while not self.ready():
sleep_ms(100)
dt = ticks_diff(ticks_ms(), t)
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
async def wait(self):
while not self.ready():
await asyncio.sleep_ms(100)
# Pause until framebuf has been copied to device.
async def updated(self):
await self._updated.wait()
# For polling in asynchronous code. Just checks pin state.
# 0 == busy. Comment in official code is wrong. Code is correct.
def ready(self):
return not(self._as_busy or (self._busy() == 0)) # 0 == busy
async def _as_show(self, buf1=bytearray(1)):
mvb = self._mvb
send = self._spi.write
cmd = self._command
cmd(b'\x10') # DATA_START_TRANSMISSION_1
self._dc(1) # For some reason don't need to deassert CS here
buf1[0] = 0xff
t = ticks_ms()
for i in range(len(mvb)):
self._cs(0) # but do when copying the framebuf
send(buf1)
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
await asyncio.sleep_ms(0)
t = ticks_ms()
self._cs(1)
cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet
self._dc(1)
# Necessary to deassert CS after each byte otherwise display does not
# clear down correctly
t = ticks_ms()
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for i in range(len(mvb)):
self._cs(0)
buf1[0] = mvb[idx] # INVERSION HACK ~data
send(buf1)
self._cs(1)
idx -= self.width
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
await asyncio.sleep_ms(0)
t = ticks_ms()
else:
for i, b in enumerate(mvb):
self._cs(0)
buf1[0] = b # INVERSION HACK ~data
send(buf1)
self._cs(1)
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
await asyncio.sleep_ms(0)
t = ticks_ms()
self._updated.set() # framebuf has now been copied to the device
self._updated.clear()
cmd(b'\x12') # DISPLAY_REFRESH
await asyncio.sleep(1)
while self._busy() == 0:
await asyncio.sleep_ms(200) # Don't release lock until update is complete
self._as_busy = False
# draw the current frame memory. Blocking time ~180ms
def show(self, buf1=bytearray(1)):
if self._asyn:
if self._as_busy:
raise RuntimeError('Cannot refresh: display is busy.')
self._as_busy = True
asyncio.create_task(self._as_show())
return
t = ticks_us()
mvb = self._mvb
send = self._spi.write
cmd = self._command
cmd(b'\x10') # DATA_START_TRANSMISSION_1
self._dc(1) # For some reason don't need to deassert CS here
buf1[0] = 0xff
for i in range(len(mvb)):
self._cs(0) # but do when copying the framebuf
send(buf1)
self._cs(1)
cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet
self._dc(1)
# Necessary to deassert CS after each byte otherwise display does not
# clear down correctly
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for _ in range(len(mvb)):
self._cs(0)
buf1[0] = mvb[idx] # INVERSION HACK ~data
send(buf1)
self._cs(1)
idx -= self.width
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
else:
for b in mvb:
self._cs(0)
buf1[0] = b # INVERSION HACK ~data
send(buf1)
self._cs(1)
cmd(b'\x12') # DISPLAY_REFRESH
te = ticks_us()
print('show time', ticks_diff(te, t)//1000, 'ms')
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()
sleep_ms(2000) # Give time for user to see result
# to wake call init()
def sleep(self):
self._as_busy = False
self.wait_until_ready()
cmd = self._command
cmd(b'\x50', b'\xf7') # From Waveshare code
cmd(b'\x02') # POWER_OFF
cmd(b'\x07', b'\xA5') # DEEP_SLEEP (Waveshare and mcauser)
self._rst(0) # According to schematic this turns off the power
# Testing connections by toggling pins connected to 40-way connector and checking volts on small connector
# All OK except rst: a 1 level produced only about 1.6V as against 3.3V for all other I/O.
# Further the level on the 40-way connector read 2.9V as agains 3.3V for others. Suspect hardware problem,
# ordered a second unit from Amazon.
#import machine
#import gc
#pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
#pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
#prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
#pbusy = machine.Pin('Y4', machine.Pin.IN)
## baudrate
## From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py 2MHz
## From https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py 4MHz
#spi = machine.SPI(2, baudrate=2_000_000)
#gc.collect() # Precaution before instantiating framebuf
#epd = EPD(spi, pcs, pdc, prst, pbusy) # Create a display instance
#sleep_ms(100)
#epd.init()
#print('Initialised')
#epd.fill(1) # 1 seems to be white
#epd.show()
#sleep_ms(1000)
#epd.fill(0)
#epd.show()
#epd._rst(0)
#epd._dc(0) # Turn off power according to RPI code

Wyświetl plik

@ -31,12 +31,13 @@ class EPD(framebuf.FrameBuffer):
def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, spi, cs, dc, rst, busy, asyn=False):
def __init__(self, spi, cs, dc, rst, busy, landscape=True, asyn=False):
self._spi = spi
self._cs = cs # Pins
self._dc = dc
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).
@ -44,15 +45,15 @@ class EPD(framebuf.FrameBuffer):
self._updated = asyncio.Event()
# Public bound variables required by nanogui.
# Dimensions in pixels as seen by nanogui (landscape mode).
self.width = 296
self.height = 128
self.width = 296 if landscape else 128
self.height = 128 if landscape else 296
# Other public bound variable.
# Special mode enables demos written for generic displays to run.
self.demo_mode = False
self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
super().__init__(self._buffer, self.width, self.height, mode)
self.init()
@ -101,9 +102,9 @@ class EPD(framebuf.FrameBuffer):
cmd(b'\x30', b'\x29')
# Resolution 128w * 296h as required by IL0373
cmd(b'\x61', b'\x80\x01\x28') # Note hex(296) == 0x128
# Set VCM_DC. 0 is datasheet default. I think Adafruit send 0x50 (-2.6V) rather than 0x12 (-1.0V)
# Set VCM_DC. Now clarified with Adafruit.
# https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/17
cmd(b'\x82', b'\x12') # Set Vcom to -1.0V is my guess at Adafruit's intention.
cmd(b'\x82', b'\x12') # Set Vcom to -1.0V
sleep_ms(50)
print('Init Done.')
@ -136,24 +137,33 @@ class EPD(framebuf.FrameBuffer):
dat = self._data
cmd(b'\x13')
t = ticks_ms()
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for i in range(len(mvb)):
buf1[0] = mvb[idx] ^ 0xff
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK):
await asyncio.sleep_ms(0)
t = ticks_ms()
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for i in range(len(mvb)):
buf1[0] = ~mvb[idx]
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK):
await asyncio.sleep_ms(0)
t = ticks_ms()
else:
for i, b in enumerate(mvb):
buf1[0] = ~b
dat(buf1)
if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK):
await asyncio.sleep_ms(0)
t = ticks_ms()
cmd(b'\x11') # Data stop
self._updated.set()
self._updated.clear()
@ -174,7 +184,6 @@ class EPD(framebuf.FrameBuffer):
asyncio.create_task(self._as_show())
return
# t = ticks_us()
mvb = self._mvb
cmd = self._command
dat = self._data
@ -182,28 +191,32 @@ class EPD(framebuf.FrameBuffer):
# busy pin low (True) and that it stays logically True until
# refresh is complete. In my testing this doesn't happen.
cmd(b'\x13')
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for _ in range(len(mvb)):
buf1[0] = mvb[idx] ^ 0xff
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for _ in range(len(mvb)):
buf1[0] = ~mvb[idx]
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
else:
for b in mvb:
buf1[0] = ~b
dat(buf1)
cmd(b'\x11') # Data stop
sleep_us(20) # Allow for data coming back: currently ignore this
cmd(b'\x12') # DISPLAY_REFRESH
# 258ms to get here on Pyboard D
# Checking with scope, busy goes low now. For 4.9s.
# te = ticks_us()
# print('show time', ticks_diff(te, t)//1000, 'ms')
if not self.demo_mode:
# Immediate return to avoid blocking the whole application.
# User should wait for ready before calling refresh()
@ -213,6 +226,8 @@ class EPD(framebuf.FrameBuffer):
# to wake call init()
def sleep(self):
self._as_busy = False
self.wait_until_ready()
cmd = self._command
# CDI: not sure about value or why we set this here. Copying Adafruit.
cmd(b'\x50', b'\x17')

Wyświetl plik

@ -3,7 +3,7 @@
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# color_setup must set landcsape False, asyn True and must not set demo_mode
# color_setup must set landcsape True, asyn True and must not set demo_mode
import uasyncio as asyncio
from color_setup import ssd
from gui.core.writer import Writer
@ -70,9 +70,13 @@ async def meter(evt):
wri = Writer(ssd, arial10, verbose=False)
row = 10
col = 150
m0 = Meter(wri, row, col, height = 80, width = 15, divisions = 4, legends=('0.0', '0.5', '1.0'))
m1 = Meter(wri, row, col + 50, height = 80, width = 15, divisions = 4, legends=('-1', '0', '+1'))
m2 = Meter(wri, row, col + 100, height = 80, width = 15, divisions = 4, legends=('-1', '0', '+1'))
args = {'height' : 80,
'width' : 15,
'divisions' : 4,
'style' : Meter.BAR}
m0 = Meter(wri, row, col, legends=('0.0', '0.5', '1.0'), **args)
m1 = Meter(wri, row, col + 50, legends=('-1', '0', '+1'), **args)
m2 = Meter(wri, row, col + 100, legends=('-1', '0', '+1'), **args)
random = xorshift64star(2**24 - 1)
while True:
steps = 10
@ -114,6 +118,6 @@ try:
except KeyboardInterrupt:
# Defensive code: avoid leaving EPD hardware in an undefined state.
print('Waiting for display to become idle')
ssd.wait_until_ready() # Synchronous code
ssd.sleep() # Synchronous code. May block for 5s if display is updating.
finally:
_ = asyncio.new_event_loop()

Wyświetl plik

@ -0,0 +1,125 @@
# waveshare_test.py Demo program for nano_gui on an Waveshare ePaper screen
# https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# color_setup must set landcsape False, asyn True and must not set demo_mode
import uasyncio as asyncio
from color_setup import ssd
from gui.core.writer import Writer
from gui.core.nanogui import refresh
from gui.widgets.meter import Meter
from gui.widgets.label import Label
# Fonts
import gui.fonts.arial10 as arial10
import gui.fonts.courier20 as fixed
import gui.fonts.font6 as small
# Some ports don't support uos.urandom.
# See https://github.com/peterhinch/micropython-samples/tree/master/random
def xorshift64star(modulo, seed = 0xf9ac6ba4):
x = seed
def func():
nonlocal x
x ^= x >> 12
x ^= ((x << 25) & 0xffffffffffffffff) # modulo 2**64
x ^= x >> 27
return (x * 0x2545F4914F6CDD1D) % modulo
return func
async def fields(evt):
wri = Writer(ssd, fixed, verbose=False)
wri.set_clip(False, False, False)
textfield = Label(wri, 0, 2, wri.stringlen('longer'))
numfield = Label(wri, 25, 2, wri.stringlen('99.990'), bdcolor=None)
countfield = Label(wri, 0, 90, wri.stringlen('1'))
n = 1
random = xorshift64star(65535)
while True:
for s in ('short', 'longer', '1', ''):
textfield.value(s)
numfield.value('{:5.2f}'.format(random() /1000))
countfield.value('{:1d}'.format(n))
n += 1
await evt.wait()
async def multi_fields(evt):
wri = Writer(ssd, small, verbose=False)
wri.set_clip(False, False, False)
nfields = []
dy = small.height() + 10
y = 80
col = 20
width = wri.stringlen('99.990')
for txt in ('X:', 'Y:', 'Z:'):
Label(wri, y, 0, txt)
nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border
y += dy
random = xorshift64star(2**24 - 1)
while True:
for _ in range(10):
for field in nfields:
value = random() / 167772
field.value('{:5.2f}'.format(value))
await evt.wait()
async def meter(evt):
wri = Writer(ssd, arial10, verbose=False)
args = {'height' : 80,
'width' : 15,
'divisions' : 4,
'style' : Meter.BAR}
m0 = Meter(wri, 165, 2, legends=('0.0', '0.5', '1.0'), **args)
m1 = Meter(wri, 165, 62, legends=('-1', '0', '+1'), **args)
m2 = Meter(wri, 165, 122, legends=('-1', '0', '+1'), **args)
random = xorshift64star(2**24 - 1)
while True:
steps = 10
for n in range(steps + 1):
m0.value(random() / 16777216)
m1.value(n/steps)
m2.value(1 - n/steps)
await evt.wait()
async def main():
# ssd.fill(1)
# ssd.show()
# await ssd.wait()
refresh(ssd, True) # Clear display
await ssd.wait()
print('Ready')
evt = asyncio.Event()
asyncio.create_task(meter(evt))
asyncio.create_task(multi_fields(evt))
asyncio.create_task(fields(evt))
while True:
# Normal procedure before refresh, but 10s sleep should mean it always returns immediately
await ssd.wait()
refresh(ssd) # Launches ._as_show()
await ssd.updated()
# Content has now been shifted out so coros can update
# framebuffer in background
evt.set()
evt.clear()
await asyncio.sleep(9) # Allow for slow refresh
tstr = '''Runs the following tests, updates every 10s
fields() Label test with dynamic data.
multi_fields() More Labels.
meter() Demo of Meter object.
'''
print(tstr)
try:
asyncio.run(main())
except KeyboardInterrupt:
print('Waiting for display to become idle')
ssd.wait_until_ready() # Synchronous code
finally:
_ = asyncio.new_event_loop()

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 10 KiB