diff --git a/DISPLAYS.md b/DISPLAYS.md index de5600e..3825ee1 100644 --- a/DISPLAYS.md +++ b/DISPLAYS.md @@ -21,6 +21,7 @@ Width and height are pixels. | 1.44C | 128 | 128 | TFT | [ST7735R][4d] | [Adafruit 2088][5m] | | | 1.5C | 160 | 128 | TFT | [ST7735R][4d] | [Adafruit 358][6m] | | | 1.3C | 240 | 240 | TFT | [ST7789][5d] | [Adafruit 4313][7m] | | +| 2.0C | 320 | 240 | TFT | [ST7789][5d] | [Waveshare Pico LCD 2][18m]| For Pi Pico | | 1.54C | 240 | 240 | TFT | [ST7789][5d] | [Adafruit 3787][8m] | | | 1.14C | 240 | 135 | TFT | [ST7789][5d] | [T-Display][9m] | ESP32 with attached display | | 2.8C | 320 | 240 | TFT | [ST7789][5d] | [Waveshare pico 2.8][10m] | Display for Pi Pico | @@ -28,6 +29,7 @@ Width and height are pixels. | 3.2C | 320 | 240 | TFT | [ILI9341][6d] | [Adafruit 1743][12m] | Big display. eBay equivalents work here. | | 2.9M | 296 | 128 | eInk | [UC8151D][7d] | [Adafruit 4262][13m] | Flexible ePaper display | | 2.9M | 296 | 128 | eInk | [UC8151D][7d] | [Adafruit 4777][15m] | FeatherWing ePaper display | +| 4.2M | 400 | 300 | eInk | [WS][10d] | [Waveshare pico 4.2][19m] | Pico, Pico W plug in. Other hosts via cable | | 2.7M | 274 | 176 | eInk | [HAT][8d] | [Waveshare HAT][14m] | HAT designed for Raspberry Pi, repurposed. | | 2.7M | 400 | 240 | Sharp | [Sharp][9d] | [Adafruit 4694][16m] | Micropower monochrome display. | | 1.3M | 168 | 144 | Sharp | [Sharp][9d] | [Adafruit 3502][17m] | Ditto | @@ -83,6 +85,7 @@ simple. See [this doc](./DRIVERS.md#7-writing-device-drivers) for details. [7d]: https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md#51-adafruit-monochrome-eink-displays [8d]: https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md#52-waveshare-eink-display-hat [9d]: https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md#4-drivers-for-sharp-displays +[10d]: https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md#53-waveshare-400x300-pi-pico-display [1m]: https://www.adafruit.com/product/684 [2m]: https://www.adafruit.com/product/1673 @@ -101,4 +104,5 @@ simple. See [this doc](./DRIVERS.md#7-writing-device-drivers) for details. [15m]: https://www.adafruit.com/product/4777 [16m]: https://www.adafruit.com/product/4694 [17m]: https://www.adafruit.com/product/3502 - +[18m]: https://www.waveshare.com/wiki/Pico-LCD-2 +[19m]: https://thepihut.com/collections/epaper-displays-for-raspberry-pi/products/4-2-e-paper-display-module-for-raspberry-pi-pico-black-white-400x300 diff --git a/DRIVERS.md b/DRIVERS.md index 4e1d9ee..a60ccf7 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -37,7 +37,8 @@ access via the `Writer` and `CWriter` classes is documented 3.3 [Drivers for ST7789](./DRIVERS.md#33-drivers-for-st7789) Small high density TFTs      3.3.1 [TTGO T Display](./DRIVERS.md#331-ttgo-t-display) Low cost ESP32 with integrated display      3.3.2 [Waveshare Pico Res Touch](./DRIVERS.md#332-waveshare-pico-res-touch) -      3.3.3 [Troubleshooting](./DRIVERS.md#333-troubleshooting) +      3.3.3 [Waveshare Pico LCD 2](./DRIVERS.md#333-waveshare-pico-lcd-2) +      3.3.4 [Troubleshooting](./DRIVERS.md#334-troubleshooting) 4. [Drivers for sharp displays](./DRIVERS.md#4-drivers-for-sharp-displays) Large low power monochrome displays 4.1 [Display characteristics](./DRIVERS.md#41-display-characteristics)      4.1.1 [The VCOM bit](./DRIVERS.md#411-the-vcom-bit) @@ -60,9 +61,9 @@ access via the `Writer` and `CWriter` classes is documented      5.2.1 [EPD constructor args](./DRIVERS.md#521-epd-constructor-args)      5.2.2 [EPD public methods](./DRIVERS.md#522-epd-public-methods)      5.2.3 [EPD public bound variables](./DRIVERS.md#523-epd-public-bound-variables) - 6. [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support) - 7. [Writing device drivers](./DRIVERS.md#7-writing-device-drivers) - 8. [Links](./DRIVERS.md#8-links) + 6. [EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support) + 7. [Writing device drivers](./DRIVERS.md#7-writing-device-drivers) + 8. [Links](./DRIVERS.md#8-links) The [Micropower use](./DRIVERS.md#515-micropower-use) section is applicable to EPD's in general but makes specific reference to the 2.9" micropower demo. @@ -565,7 +566,34 @@ driver which may be found in the MicroPython source tree in `drivers/sdcard/sdcard.py`. I am not an expert on SD cards. Mine worked fine at 31.25MHz but this may or may not be universally true. -### 3.3.3 Troubleshooting +### 3.3.3 Waveshare Pico LCD 2 + +Support for this display resulted from a collaboration with Mike Wilson +(@MikeTheGent). + +This is a "plug and play" 2" color TFT for `nano-gui` and the Pi Pico. Users of +`micro-gui` will need to find a way to connect pushbuttons, using stacking +headers on the Pico or soldering wires to its pads. The `color_setup.py` file +is as follows. +```python +from machine import Pin, SPI +import gc + +from drivers.st7789.st7789_4bit import * +SSD = ST7789 + +gc.collect() # Precaution before instantiating framebuf +# Conservative low baudrate. Can go to 62.5MHz. +spi = SPI(1, 30_000_000, sck=Pin(10), mosi=Pin(11), miso=None) +pcs = Pin(9, Pin.OUT, value=1) +prst = Pin(12, Pin.OUT, value=1) +pbl = Pin(13, Pin.OUT, value=1) +pdc = Pin(8, Pin.OUT, value=0) + +ssd = SSD(spi, height=240, width=320, dc=pdc, cs=pcs, rst=prst, disp_mode=LANDSCAPE, display=PI_PICO_LCD_2) +``` + +### 3.3.4 Troubleshooting If your display shows garbage, check the following (I have seen both): * SPI baudrate too high for your physical layout. @@ -1023,6 +1051,30 @@ Pins 26-40 unused and omitted. seconds to enable viewing. This enables generic nanogui demos to be run on an EPD. +## 5.3 Waveshare 400x300 Pi Pico display + +This 4.2" display supports 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 +cable. With a Pico variant the `color_setup` file is very simple: +```python +import machine +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. +``` +For other hosts the pins need to be specified in `color_setup.py` via the +following constructor args: + + * `spi=None` An SPI bus instance defined with default args. + * `cs=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`. + * `busy=None` A `Pin` instance defined as `Pin.IN, Pin.PULL_UP`. + * `asyn=False` Set `True` for asynchronous applications. + ###### [Contents](./DRIVERS.md#contents) # 6. EPD Asynchronous support @@ -1054,6 +1106,7 @@ The following illustrates the kind of approach which may be used: ```python while True: # Before refresh, ensure that a previous refresh is complete + # Not strictly necessary if .updated() used after refresh. await ssd.wait() refresh(ssd) # Immediate return. Creates a task to copy content to EPD. # Wait until the framebuf content has been passed to EPD. @@ -1062,7 +1115,6 @@ The following illustrates the kind of approach which may be used: # framebuffer in background evt.set() evt.clear() - # The 2.9 inch display should not be updated too frequently await asyncio.sleep(180) ``` @@ -1082,21 +1134,19 @@ driver chips support graphics primitives in hardware; drivers using these capabilities will be faster than those provided here and may often be found using a forum search. +## 7.1 The basics + For a driver to support `nanogui` it must be subclassed from `framebuf.FrameBuffer` and provide `height` and `width` bound variables being the display size in pixels. This, and a `show` method, are all that is required -for monochrome drivers. Generality can be extended by providing this static -method: -```python - @staticmethod - def rgb(r, g, b): - return int(((r | g | b) & 0x80) > 0) -``` -This ensures compatibility with code written for color displays by converting -RGB values to a single bit. +for monochrome drivers. -For color display drivers some boilerplate code is required for rendering -monochrome objects such as glyphs: +## 7.2 Color and color compatible drivers + +Some additional boilerplate code is required for color drivers to enable them +to render monochrome object such as glyphs. To enable a monochrome driver to +run code written for color displays it too should incorporate this code. +Otherise color code will fail with an "Incompatible device driver" exception. ```python from drivers.boolpalette import BoolPalette # In the constructor: @@ -1104,16 +1154,45 @@ from drivers.boolpalette import BoolPalette self.palette = BoolPalette(mode) super().__init__(buf, self.width, self.height, mode) ``` +The GUI achieves hardware independence by using 24 bit color. The driver must +convert this, typically to a format used by the hardware. This is done by a +static `rgb` method. In the case of a monochrome display, any color with high +brightness is mapped to white with: +```python + @staticmethod + def rgb(r, g, b): + return int(((r | g | b) & 0x80) > 0) +``` +A typical color display with 8-bit `rrrgggbb` hardware will use: +```python + @staticmethod + def rgb(r, g, b): + return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6) +``` +See the discussion below for other color mappings including 16-bit and 4-bit +variants. + +## 7.3 Show + Refresh must be handled by a `show` method taking no arguments; when called, the contents of the buffer underlying the `FrameBuffer` must be copied to the hardware. -For color drivers, to conserve RAM it is suggested that 8-bit color is used -for the `FrameBuffer`. If the hardware does not support this, conversion to the -supported color space needs to be done "on the fly" as per the SSD1351 driver. -This uses `framebuf.GS8` to stand in for 8 bit color in `rrrgggbb` format. To -maximise update speed consider using native, viper or assembler for the -conversion, typically to RGB565 format. +## 7.4 Minimising RAM usage + +In the simplest case the `FrameBuffer` mode is chosen to match a mode used by +the hardware. The `rgb` static method converts colors to that format and +`.show` writes it out. + +In some cases this can result in a need for a large `FrameBuffer`, either +because the hardware can only accept 16 bit color values or because the +display has a large number of pixels. In these cases the `FrameBuffer` uses +a mode for 8 bit or 4 bit color with mapping taking place on the fly in the +`.show` method. To maximise update speed consider using native, viper or +assembler for this mapping. + +An example of hardware that does not support 8 bit color is the SSD1351 driver. +This uses `framebuf.GS8` to stand in for 8 bit color in `rrrgggbb` format. An alternative is to design for 4-bit color which halves the size of the framebuffer. This means using `GS4_HMSB` mode. The class must include the class @@ -1127,41 +1206,21 @@ acceptable to the hardware. The "on the fly" converter unpacks the values in the frame buffer and uses them as indices into the `lut` bytearray. See the various supplied 4-bit drivers. -Color drivers should have a static method converting rgb(255, 255, 255) to a -form acceptable to the driver. For 8-bit rrrgggbb this can be: -```python - @staticmethod - def rgb(r, g, b): - return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6) -``` -This should be amended if the hardware uses a different 8-bit format. If the -hardware expects a 16 bit value, the "on the fly" converter will map the 8-bit -value to 16 bits. In the case of 4-bit drivers the LUT is 16 bits in size, and -`.rgb` is called only when populating the LUT. In this case `.rgb` returns a 16 -bit value in a format compatible with the hardware and the byte order of the -"on the fly" conversion code. +The color driver static method should be amended if the hardware uses a +different 8-bit format. If the hardware expects a 16 bit value, the "on the +fly" converter will map the 8-bit value to 16 bits. In a 4-bit driver the LUT +is 16 bits in size, and `.rgb` is called only when populating the LUT. In this +case `.rgb` returns a 16 bit value in a format compatible with the hardware and +the byte order of the "on the fly" conversion code. The convention I use is that the LS byte from `.rgb()` is transmitted first. So long as `.rgb()` and the "on the fly" converter match, this is arbitrary. -The `Writer` (monochrome) or `CWriter` (color) classes and the `nanogui` module -should then work automatically. +## 7.5 Debugging -For color displays the following provides a substantial performance boost when -rendering text. -```python -from drivers.boolpalette import BoolPalette -``` -and, in `__init__.py`: -```python - mode = framebuf.GS4_HMSB # The format to be used by the driver - self.palette = BoolPalette(mode) - gc.collect() - buf = bytearray(self.height * self.width // 2) # Computation must match the format - super().__init__(buf, self.width, self.height, mode) -``` -Assuming firmware dated after 26th Aug 2021, a fast C blit will be used to -render glyphs instead of Python code. +If the above guidelines are followed the `Writer` (monochrome) or `CWriter` +(color) classes, `nanogui` and `micro-gui`modules should then work +automatically. The following script is useful for testing color display drivers after configuring `color_setup.py`. It draws squares at the extreme corners of the @@ -1185,6 +1244,42 @@ If this produces correct output the GUI's can be expected to work. Authors of device drivers are encouraged to raise an issue or PR so that the library can be extended. +## 7.6 Reducing blocking time in show + +This is available for `micro-gui` only. The blocking time of the `.show` method +is reduced by splitting it into segments and yielding to the scheduler after +each segment. It is handled transparently by the GUI. It consists of providing +an asynchronous `do_refresh` method taking an integer `split` arg. See +[the ili9341 driver](https://github.com/peterhinch/micropython-nano-gui/blob/master/drivers/ili93xx/ili9341.py) +for an example. The GUI will pass a `split` value which is a divisor of the +number of lines in the display. The `do_refresh` method calculates the number +of lines in each segment. For each segment it outputs those lines and yields +to the scheduler. + +## 7.7 ePaper drivers + +These are not supported by `micro-gui` owing to their very slow refresh time. +They are supported by `Witer`, `CWriter` and `nano-gui`. + +Owing to the long refresh periods some synchronisation is necessary. This +comprises `ready` and `wait_until_ready` methods. The `ready` method +immediately returns a `bool` indicating if the hardware can accept data. The +`wait_until_ready` method blocks until the device is ready to accept data. This +is all that is required for synchronous applications. The `.show.` method calls +`wait_until_ready` at the end, removing the need for explicit synchronisation +in the application. The cost is that display refresh blocks for a long period. + +For applications using asynchronous code this blocking is usually unacceptable. +It can be restricted to a single task, with others, able to continue running by +adding two asynchronous methods, `.wait` and `.updated`. The `.wait` method is +an asynchronous version of `.wait_until_ready`. The `.updated` method is issued +after a `refresh` has been issued and pauses until the physical refresh is +complete. After a `refresh` applications should avoid changing the frame buffer +contents until `.updated` has returned. They should wait on `.wait` before +issuing `.refresh`. + +###### [Contents](./DRIVERS.md#contents) + # 8. Links #### [Device driver document.](./DRIVERS.md) diff --git a/drivers/epaper/pico_epaper_42.py b/drivers/epaper/pico_epaper_42.py new file mode 100644 index 0000000..719b91d --- /dev/null +++ b/drivers/epaper/pico_epaper_42.py @@ -0,0 +1,234 @@ +# pico_epaper_42.py A 1-bit monochrome display driver for the Waveshare Pico +# ePaper 4.2" display. +# Adapted from the Waveshare driver by Peter Hinch Sept 2022. + +# ***************************************************************************** +# * | File : Pico_ePaper-3.7.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2021-06-01 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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 +# THE SOFTWARE. +# + +from machine import Pin, SPI +import framebuf +import time +import uasyncio as asyncio +from drivers.boolpalette import BoolPalette + +# Display resolution +_EPD_WIDTH = const(400) +_BWIDTH = _EPD_WIDTH // 8 +_EPD_HEIGHT = const(300) + +RST_PIN = 12 +DC_PIN = 8 +CS_PIN = 9 +BUSY_PIN = 13 + +EPD_lut_vcom0 = b"\x00\x08\x08\x00\x00\x02\x00\x0F\x0F\x00\x00\x01\x00\x08\x08\x00\ +\x00\x02\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" + +EPD_lut_ww = b"\x50\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\xA0\x08\x08\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +EPD_lut_bw = b"\x50\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\xA0\x08\x08\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +EPD_lut_wb = b"\xA0\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\x50\x08\x08\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +EPD_lut_bb = b"\x20\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\x10\x08\x08\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +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=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 + self.cs_pin = Pin(CS_PIN, Pin.OUT) if cs is None else cs + self.dc_pin = Pin(DC_PIN, Pin.OUT) if dc is None else dc + self.spi = SPI(1) if spi is None else spi + self.spi.init(baudrate=4_000_000) + 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.width = _EPD_WIDTH + self.height = _EPD_HEIGHT + self.buf = bytearray(_EPD_HEIGHT * _BWIDTH) + self.mvb = memoryview(self.buf) + mode = framebuf.MONO_HLSB + self.palette = BoolPalette(mode) + super().__init__(self.buf, _EPD_WIDTH, _EPD_HEIGHT, mode) + self.init() + time.sleep_ms(500) + + # Hardware reset + def reset(self): + for v in (1, 0, 1): + self.reset_pin(v) + time.sleep_ms(20) + + def send_command(self, command): + self.dc_pin(0) + self.cs_pin(0) + self.spi.write(command) + self.cs_pin(1) + + def send_bytes(self, data): + self.dc_pin(1) + self.cs_pin(0) + self.spi.write(data) + self.cs_pin(1) + + def display_on(self): + self.send_command(b"\x12") + time.sleep_ms(100) + self.wait_until_ready() + + def init(self): + self.reset() + self.send_command(b"\x01") # POWER SETTING + self.send_bytes(b"\x03") + self.send_bytes(b"\x00") + self.send_bytes(b"\x2b") + self.send_bytes(b"\x2b") + + self.send_command(b"\x06") # boost soft start + self.send_bytes(b"\x17") # A + self.send_bytes(b"\x17") # B + self.send_bytes(b"\x17") # C + + self.send_command(b"\x04") # POWER_ON + self.wait_until_ready() + + self.send_command(b"\x00") # panel setting + self.send_bytes(b"\xbf") # KW-BF KWR-AF BWROTP 0f BWOTP 1f + self.send_bytes(b"\x0d") + + self.send_command(b"\x30") # PLL setting + self.send_bytes(b"\x3C") # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(b"\x61") # resolution setting + self.send_bytes(b"\x01") + self.send_bytes(b"\x90") # 128 + self.send_bytes(b"\x01") + self.send_bytes(b"\x2c") + + self.send_command(b"\x82") # vcom_DC setting + self.send_bytes(b"\x28") + + self.send_command(b"\x50") # VCOM AND DATA INTERVAL SETTING + self.send_bytes(b"\x97") # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + self.send_command(b"\x20") + self.send_bytes(EPD_lut_vcom0) + + self.send_command(b"\x21") + self.send_bytes(EPD_lut_ww) + + self.send_command(b"\x22") + self.send_bytes(EPD_lut_bw) + + self.send_command(b"\x23") + self.send_bytes(EPD_lut_wb) + + self.send_command(b"\x24") + self.send_bytes(EPD_lut_bb) +# Clear display + self.send_command(b"\x10") + for j in range(_EPD_HEIGHT): + self.send_bytes(b"\xff" * _BWIDTH) + + self.send_command(b"\x13") + for j in range(_EPD_HEIGHT): + self.send_bytes(b"\xff" * _BWIDTH) + + self.send_command(b"\x12") + time.sleep_ms(10) + self.display_on() + + def wait_until_ready(self): + while(not self.ready()): + self.send_command(b"\x71") + time.sleep_ms(100) + + async def wait(self): + await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready + while not self.ready(): + self.send_command(b"\x71") + 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_pin() == 0)) # 0 == busy + + def _line(self, n, buf=bytearray(_BWIDTH)): + img = self.mvb + s = n * _BWIDTH + for x, b in enumerate(img[s : s + _BWIDTH]): + buf[x] = b ^ 0xFF + self.send_bytes(buf) + + async def _as_show(self): + self.send_command(b"\x13") + for j in range(_EPD_HEIGHT): # Loop would take ~300ms + self._line(j) + await asyncio.sleep_ms(0) + self.send_command(b"\x12") # Async .display_on() + while not self.busy_pin(): + await asyncio.sleep_ms(10) # About 1.7s + self._updated.set() + self._updated.clear() + self._as_busy = False + + def show(self): + if self._asyn: + if self._as_busy: + raise RuntimeError('Cannot refresh: display is busy.') + self._as_busy = True # Immediate busy flag. Pin goes low much later. + asyncio.create_task(self._as_show()) + return + self.send_command(b"\x13") + for j in range(_EPD_HEIGHT): + self._line(j) + self.display_on() + self.wait_until_ready() + + def sleep(self): +# self.send_command(b"\x02") # power off +# self.wait_until_ready() + self.send_command(b"\x07") # deep sleep + self.send_bytes(b"\xA5") diff --git a/drivers/st7789/st7789_4bit.py b/drivers/st7789/st7789_4bit.py index 51d1562..bde0ad9 100644 --- a/drivers/st7789/st7789_4bit.py +++ b/drivers/st7789/st7789_4bit.py @@ -31,7 +31,7 @@ PORTRAIT = 4 # Display types GENERIC = (0, 0, 0) TDISPLAY = (52, 40, 1) - +PI_PICO_LCD_2 = (0, 0, 1) # Waveshare Pico LCD 2 determined by Mike Wilson. @micropython.viper def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int): @@ -61,8 +61,8 @@ class ST7789(framebuf.FrameBuffer): disp_mode=LANDSCAPE, init_spi=False, display=GENERIC): if not 0 <= disp_mode <= 7: raise ValueError('Invalid display mode:', disp_mode) - if not display in (GENERIC, TDISPLAY): - raise ValueError('Invalid display type.') + if not display in (GENERIC, TDISPLAY, PI_PICO_LCD_2): + print("WARNING: unsupported display parameter value.") self._spi = spi # Clock cycle time for write 16ns 62.5MHz max (read is 150ns) self._rst = rst # Pins self._dc = dc diff --git a/gui/demos/waveshare_test.py b/gui/demos/epd_async.py similarity index 95% rename from gui/demos/waveshare_test.py rename to gui/demos/epd_async.py index 287598a..537a2ee 100644 --- a/gui/demos/waveshare_test.py +++ b/gui/demos/epd_async.py @@ -1,8 +1,10 @@ -# waveshare_test.py Demo program for nano_gui on an Waveshare ePaper screen +# epd_async.py Demo of nano_gui asynchronous code. +# Needs a large screen e.g. # https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT +# or 4.2" Waveshare Pico ePaper display. # Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2020 Peter Hinch +# Copyright (c) 2020-2022 Peter Hinch # color_setup must set landcsape False, asyn True and must not set demo_mode import uasyncio as asyncio diff --git a/setup_examples/epd_waveshare_42_pico.py b/setup_examples/epd_waveshare_42_pico.py new file mode 100644 index 0000000..d365fa8 --- /dev/null +++ b/setup_examples/epd_waveshare_42_pico.py @@ -0,0 +1,18 @@ +# color_setup.py Customise for your hardware config + +# Released under the MIT License (MIT). See LICENSE. +# Copyright (c) 2022 Peter Hinch + +# Supports Waveshare 4.2" 400x300 ePaper display with Raspberry Pico +# https://thepihut.com/collections/epaper-displays-for-raspberry-pi/products/4-2-e-paper-display-module-for-raspberry-pi-pico-black-white-400x300 +# Waveshare code +# https://github.com/waveshare/Pico_ePaper_Code/blob/a6b26ac714d56f958010ddfca3b7fef8410c59c2/python/Pico-ePaper-4.2.py +import machine +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 diff --git a/setup_examples/ssd1351_pyb.py b/setup_examples/ssd1351_pyb.py new file mode 100644 index 0000000..a950d22 --- /dev/null +++ b/setup_examples/ssd1351_pyb.py @@ -0,0 +1,43 @@ +# color_setup.py Customise for your hardware config + +# Released under the MIT License (MIT). See LICENSE. +# Copyright (c) 2020 Peter Hinch + +# As written, supports: +# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 +# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 +# Edit the driver import for other displays. + +# 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 pin nos and names). +# Pyb SSD +# 3v3 Vin (10) +# Gnd Gnd (11) +# Y1 DC (3 DC) +# Y2 CS (5 OC OLEDCS) +# Y3 Rst (4 R RESET) +# Y6 CLK (2 CL SCK) +# Y8 DATA (1 SI MOSI) + +import machine +import gc + +# *** Choose your color display driver here *** +# Driver supporting non-STM platforms +# from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD + +# STM specific driver +from drivers.ssd1351.ssd1351 import SSD1351 as SSD + +height = 96 # 1.27 inch 96*128 (rows*cols) display +# height = 128 # 1.5 inch 128*128 display + +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) +spi = machine.SPI(2) +gc.collect() # Precaution before instantiating framebuf +ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance