From e510739daf6821d50c9a3ae785b66d97f6f8c29c Mon Sep 17 00:00:00 2001 From: peterhinch Date: Thu, 13 Apr 2023 13:34:37 +0100 Subject: [PATCH] Add limited ePaper support. --- README.md | 41 +++- drivers/epaper/pico_epaper_42.py | 287 ++++++++++++++++++++++++++ gui/demos/epaper.py | 183 ++++++++++++++++ hardware_setup.py | 29 +-- setup_examples/pico_epaper_42_pico.py | 59 ++++++ 5 files changed, 582 insertions(+), 17 deletions(-) create mode 100644 drivers/epaper/pico_epaper_42.py create mode 100644 gui/demos/epaper.py create mode 100644 setup_examples/pico_epaper_42_pico.py diff --git a/README.md b/README.md index 0b7eb2a..6e21837 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ target and a C device driver (unless you can acquire a suitable binary). # Project status +April 2023: Add limited ePaper support, grid widget, calendar and epaper demos. + July 2022: Add ESP32 touch pad support. June 2022: Add [QRMap](./README.md#620-qrmap-widget) and @@ -147,6 +149,7 @@ development so check for updates. 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. 9. [Realtime applications](./README.md#9-realtime-applications) Accommodating tasks requiring fast RT performance. + 9.1 [ePaper refresh](./README.md#91-epaper-refresh) Using these techniques to provide a full refresh. [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. @@ -501,8 +504,12 @@ some builds. Supported displays are as per [the nano-gui list](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#12-description). -In practice usage with ePaper displays is questionable because of their slow -refresh times. I haven't tested these, or the Sharp displays. +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 +[Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm). This +supports partial updates which work remarkably well with minimal ghosting. Note +that it can be used with hosts other than the Pico via the supplied cable. See +[ePaper refresh](./README.md#91-epaper-refresh). Display drivers are documented [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md). @@ -565,6 +572,11 @@ minimal and aim to demonstrate a single technique. * `adjust_vec.py` A pair of `Adjuster`s vary a vector. * `bitmap.py` Demo of the `BitMap` widget showing a changing image. * `qrcode.py` Display a QR code. Requires the uQR module. + * `calendar.py` Demo of grid widget. + * `epaper.py` Warts-and-all demo for an ePaper display. Currently the only + supported display is the + [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm) + with Pico or other host. ### 1.11.2 Test scripts @@ -784,8 +796,8 @@ display such as an OLED. On a Sharp display it indicates reflection. There is an issue regarding ePaper displays discussed [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#312-monochrome-displays). -I don't consider ePaper displays suitable for I/O because of their slow refresh -time. +The driver for the [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm) +renders colored objects as black on white. ###### [Contents](./README.md#0-contents) @@ -3030,6 +3042,27 @@ another from occurring. ``` The demo `gui/demos/audio.py` provides example usage. +## 9.1 ePaper refresh + +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 +copied or adapted from `setup_examples/pico_epaper_42_pico.py`. After an +initial refresh the driver is put into partial mode to provide reasonably +quick and visually satisfactory response to button events. However ghosting may +accumulate after long periods of running, and an application may occasionally +need to perform a full refresh. This requires the "done" interlock described +above. + +```python +async def full_refresh(): + Screen.rfsh_done.clear() # Enable completion flag + await Screen.rfsh_done.wait() # Wait for a refresh to end + ssd.set_full() + Screen.rfsh_done.clear() # Enable completion flag + await Screen.rfsh_done.wait() # Wait for a single full refresh to end + ssd.set_partial() +``` + ###### [Contents](./README.md#0-contents) # Appendix 1 Application design diff --git a/drivers/epaper/pico_epaper_42.py b/drivers/epaper/pico_epaper_42.py new file mode 100644 index 0000000..08d160b --- /dev/null +++ b/drivers/epaper/pico_epaper_42.py @@ -0,0 +1,287 @@ +# pico_epaper_42.py A 1-bit monochrome display driver for the Waveshare Pico +# ePaper 4.2" display. This version fixes bugs and supports partial updates. +# https://github.com/peterhinch/micropython-nano-gui/blob/master/drivers/epaper/pico_epaper_42.py + +# Adapted from the Waveshare driver by Peter Hinch Sept 2022-March 2023. +# https://www.waveshare.com/pico-epaper-4.2.htm +# UC8176 manual https://www.waveshare.com/w/upload/8/88/UC8176.pdf +# Waveshare's copy of this driver. +# https://github.com/waveshare/Pico_ePaper_Code/blob/main/pythonNanoGui/drivers/ePaper4in2.py + +# ***************************************************************************** +# * | 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. + +# If .set_partial() is called, subsequent updates will be partial. To restore normal +# updates, issue .set_full() + +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" + +# ******************************partial screen update LUT********************************* # + +EPD_partial_lut_vcom1 = 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" + +EPD_partial_lut_ww1 = 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" + +EPD_partial_lut_bw1 =b"\x80\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" + +EPD_partial_lut_wb1 = b"\x40\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" + + +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" + +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, 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.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.set_full() +# 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 set_full(self): # Normal full updates + 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) + + def set_partial(self): # Partial updates + self.send_command(b"\x20") + self.send_bytes(EPD_partial_lut_vcom1) + + self.send_command(b"\x21") + self.send_bytes(EPD_partial_lut_ww1) + + self.send_command(b"\x22") + self.send_bytes(EPD_partial_lut_bw1) + + self.send_command(b"\x23") + self.send_bytes(EPD_partial_lut_wb1) + + self.send_command(b"\x24") + self.send_bytes(EPD_partial_lut_bb1) + + def wait_until_ready(self): + while not self.ready(): + time.sleep_ms(100) + + 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): + self._updated.clear() + await self._updated.wait() + self._updated.clear() + + # 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._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 block ~300ms + self._line(j) + await asyncio.sleep_ms(0) + self._updated.set() + self.send_command(b"\x12") # Async .display_on() + while not self.busy_pin(): + await asyncio.sleep_ms(10) # About 1.7s + self._busy = False + + async def do_refresh(self, split): # For micro-gui + await self._as_show() + + def show(self): + if self._busy: + raise RuntimeError('Cannot refresh: display is busy.') + self._busy = True # Immediate busy flag. Pin goes low much later. + if self._asyn: + asyncio.create_task(self._as_show()) + return + self.send_command(b"\x13") + for j in range(_EPD_HEIGHT): + self._line(j) + self._busy = False + 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/gui/demos/epaper.py b/gui/demos/epaper.py new file mode 100644 index 0000000..d4ebf77 --- /dev/null +++ b/gui/demos/epaper.py @@ -0,0 +1,183 @@ +# epaper.py micro-gui demo of multiple controls on an ePaper display type +# https://www.waveshare.com/pico-epaper-4.2.htm +# Use with setup_examples/pico_epaper_42_pico.py + +# Released under the MIT License (MIT). See LICENSE. +# Copyright (c) 2023 Peter Hinch + +# Initialise hardware and framebuf before importing modules. +# Create SSD instance. Must be done first because of RAM use. +import hardware_setup + +from gui.core.ugui import Screen, ssd +from gui.core.writer import CWriter +import gui.fonts.arial10 as arial10 # Font for CWriter +from gui.core.colors import * +# Widgets +from gui.widgets.label import Label +from gui.widgets.dial import Dial, Pointer +from gui.widgets.meter import Meter +from gui.widgets.scale import Scale +from gui.widgets.buttons import Button, ButtonList, RadioButtons, CloseButton +from gui.widgets.checkbox import Checkbox +from gui.widgets.led import LED + +import cmath +import uasyncio as asyncio +import utime +import gc + +async def full_refresh(): + Screen.rfsh_done.clear() # Enable completion flag + await Screen.rfsh_done.wait() # Wait for a refresh to end + ssd.set_full() + Screen.rfsh_done.clear() # Enable completion flag + await Screen.rfsh_done.wait() # Wait for a single full refresh to end + ssd.set_partial() + + +class FooScreen(Screen): + def __init__(self): + buttons = [] + + # A ButtonList with two entries + table_buttonset = ( + {'fgcolor' : RED, 'text' : 'Disable', 'args' : (buttons, True)}, + {'fgcolor' : GREEN, 'text' : 'Enable', 'args' : (buttons, False)}, + ) + + table_radiobuttons = ( + {'text' : '1', 'args' : ('1',)}, + {'text' : '2', 'args' : ('2',)}, + {'text' : '3', 'args' : ('3',)}, + {'text' : '4', 'args' : ('4',)}, + ) + + def tickcb(f, c): + if f > 0.8: + return RED + if f < -0.8: + return BLUE + return c + + def bcb(b): + print('Button pressed', b) + + super().__init__() + self.rb0 = None + self.bs0 = None + wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) + lbltim = Label(wri, 65, 100, 'this is a test', bdcolor=RED) + + m0 = Meter(wri, 10, 240, divisions = 4, ptcolor=YELLOW, height=80, width=15, + label='Meter example', style=Meter.BAR, legends=('0.0', '0.5', '1.0')) + # Instantiate displayable objects. bgcolor forces complete redraw. + dial = Dial(wri, 2, 2, height = 75, ticks = 12, bgcolor=BLACK, bdcolor=None, label=120) # Border in fg color + scale = Scale(wri, 2, 100, width = 124, tickcb = tickcb, + pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN) + + row = 105 + col = 2 + Label(wri, row, col, 'Normal buttons') + # Four Button instances + row = 120 + ht = 30 + for i, s in enumerate(('a', 'b', 'c', 'd')): + col= 2 + i * (ht + 5) + buttons.append(Button(wri, row, col, height=ht, callback=bcb, text=s, litcolor=RED, shape=CIRCLE, bgcolor=DARKGREEN)) + + # ButtonList + self.bs = ButtonList(self.callback) + self.bs0 = None + col+= 50 + Label(wri, row - 15, col, 'ButtonList') + for t in table_buttonset: # Buttons overlay each other at same location + button = self.bs.add_button(wri, row, col, shape=RECTANGLE, textcolor=BLUE, height=30, **t) + if self.bs0 is None: # Save for reset button callback + self.bs0 = button + + # Reset button + col+= 60 + btn = Button(wri, row, col, height=30, callback=self.rstcb, text='reset', litcolor=RED, fgcolor=GREEN, bgcolor=DARKGREEN) + + col = 2 + row = 170 + Label(wri, row, col, 'Radio buttons') + # Radio buttons + row = 185 + self.rb = RadioButtons(BLUE, self.rbcb) # color of selected button + self.rb0 = None + for t in table_radiobuttons: + button = self.rb.add_button(wri, row, col, textcolor = WHITE, + fgcolor = BLUE, bgcolor = DARKBLUE, shape=CIRCLE, height = 30, **t) + if self.rb0 is None: # Save for reset button callback + self.rb0 = button + col+= 35 + # Checkbox + col+= 35 + Label(wri, row - 15, col, 'Checkbox and LED') + Checkbox(wri, row, col, callback=self.cbcb) + col+= 40 + self.led = LED(wri, row, col, color=YELLOW, bdcolor=GREEN) + CloseButton(wri) + asyncio.create_task(run(dial, lbltim, m0, scale)) + + + def callback(self, button, buttons, val): + buttons[2].greyed_out(val) + + def rbcb(self, button, val): + print('RadioButtons callback', val) + + def rstcb(self, button): + print('Reset button: init ButtonList and RadioButtons, do full refresh.') + self.bs.value(self.bs0) + self.rb.value(self.rb0) + asyncio.create_task(full_refresh()) + + def cbcb(self, cb): + self.led.value(cb.value()) + gc.collect() + print('Free RAM:', gc.mem_free()) + +async def run(dial, lbltim, m0, scale): + days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', + 'Sunday') + months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', + 'Aug', 'Sept', 'Oct', 'Nov', 'Dec') + uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi + pi = cmath.pi + hrs = Pointer(dial) + mins = Pointer(dial) + secs = Pointer(dial) + + hstart = 0 + 0.7j # Pointer lengths and position at top + mstart = 0 + 0.92j + sstart = 0 + 0.92j + + cv = -1.0 # Scale + dv = 0.005 + while True: + t = utime.localtime() + hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360), YELLOW) + mins.value(mstart * uv(-t[4] * pi/30), YELLOW) + secs.value(sstart * uv(-t[5] * pi/30), RED) + lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5])) + dial.text('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0])) + m0.value(t[5]/60) + scale.value(cv) + await asyncio.sleep_ms(200) + cv += dv + if abs(cv) > 1.0: + dv = -dv + cv += dv + + +def test(): + if ssd.height < 240 or ssd.width < 320: + print(' This test requires a display of at least 320x240 pixels.') + else: + print('Testing micro-gui...') + Screen.change(FooScreen) + +test() diff --git a/hardware_setup.py b/hardware_setup.py index 7bc0f1b..0aa5a3c 100644 --- a/hardware_setup.py +++ b/hardware_setup.py @@ -1,15 +1,12 @@ -# ili9341_pico.py Customise for your hardware config +# pico_epaper_42.py on my PCB (non-standard connection) # Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2021 Peter Hinch +# Copyright (c) 2023 Peter Hinch -# As written, supports: -# ili9341 240x320 displays on Pi Pico -# 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. +# This Hardware_setup.py is for # https://www.waveshare.com/pico-epaper-4.2.htm +# wired to the Pico using the ribbon cable rather than the default socket. +# This was to enable testing using my ILI9341 PCB and its pushbuttons. +# Use commented-out code below if using the built-in Pico socket. # WIRING # Pico Display @@ -33,15 +30,19 @@ from machine import Pin, SPI, freq import gc -from drivers.ili93xx.ili9341 import ILI9341 as SSD +from drivers.epaper.pico_epaper_42 import EPD as SSD freq(250_000_000) # RP2 overclock # Create and export an SSD instance prst = Pin(9, Pin.OUT, value=1) pcs = Pin(10, Pin.OUT, value=1) pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins -spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=30_000_000) +busy = Pin(15, Pin.IN) +spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=4_000_000) gc.collect() # Precaution before instantiating framebuf -ssd = SSD(spi, pcs, pdc, prst, usd=True) + +# Using normal socket connection default args apply +# ssd = SSD() +ssd = SSD(spi, pcs, pdc, prst, busy) gc.collect() from gui.core.ugui import Display, quiet # quiet() @@ -53,4 +54,6 @@ prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value # display = Display(ssd, nxt, sel, prev) # 3-button mode -display = Display(ssd, nxt, sel, prev, increase, decrease, 4) # Encoder mode +display = Display(ssd, nxt, sel, prev, increase, decrease) # 5-button mode +ssd.wait_until_ready() # Blocking wait +ssd.set_partial() # Subsequent refreshes are partial diff --git a/setup_examples/pico_epaper_42_pico.py b/setup_examples/pico_epaper_42_pico.py new file mode 100644 index 0000000..0aa5a3c --- /dev/null +++ b/setup_examples/pico_epaper_42_pico.py @@ -0,0 +1,59 @@ +# pico_epaper_42.py on my PCB (non-standard connection) + +# Released under the MIT License (MIT). See LICENSE. +# Copyright (c) 2023 Peter Hinch + +# This Hardware_setup.py is for # https://www.waveshare.com/pico-epaper-4.2.htm +# wired to the Pico using the ribbon cable rather than the default socket. +# This was to enable testing using my ILI9341 PCB and its pushbuttons. +# Use commented-out code below if using the built-in Pico socket. + +# WIRING +# Pico Display +# GPIO Pin +# 3v3 36 Vin +# IO6 9 CLK Hardware SPI0 +# IO7 10 DATA (AKA SI MOSI) +# IO8 11 DC +# IO9 12 Rst +# Gnd 13 Gnd +# IO10 14 CS + +# Pushbuttons are wired between the pin and Gnd +# Pico pin Meaning +# 16 Operate current control +# 17 Decrease value of current control +# 18 Select previous control +# 19 Select next control +# 20 Increase value of current control + +from machine import Pin, SPI, freq +import gc + +from drivers.epaper.pico_epaper_42 import EPD as SSD +freq(250_000_000) # RP2 overclock +# Create and export an SSD instance +prst = Pin(9, Pin.OUT, value=1) +pcs = Pin(10, Pin.OUT, value=1) +pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins +busy = Pin(15, Pin.IN) +spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=4_000_000) +gc.collect() # Precaution before instantiating framebuf + +# Using normal socket connection default args apply +# ssd = SSD() +ssd = SSD(spi, pcs, pdc, prst, busy) +gc.collect() +from gui.core.ugui import Display, quiet +# quiet() +# Create and export a Display instance +# Define control buttons +nxt = Pin(19, Pin.IN, Pin.PULL_UP) # Move to next control +sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control +prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control +increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value +decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value +# display = Display(ssd, nxt, sel, prev) # 3-button mode +display = Display(ssd, nxt, sel, prev, increase, decrease) # 5-button mode +ssd.wait_until_ready() # Blocking wait +ssd.set_partial() # Subsequent refreshes are partial