From d92470cb4577600a4f8c22ba5689d36aa6390138 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sun, 17 Jan 2021 16:08:08 +0000 Subject: [PATCH] Add ePaper support. Monochrome setup is now consistent with color. --- ASYNC.md | 11 +- DRIVERS.md | 128 +++++++++++++++------ README.md | 110 +++++++----------- color_setup/sharp_setup.py | 31 +++++ color_setup/ssd1306_setup.py | 49 ++++++++ color_setup/ssd1331_setup.py | 2 +- drivers/sharp/sharp.py | 5 +- drivers/ssd1306/ssd1306.py | 4 + {drivers/sharp => gui/demos}/clock_batt.py | 11 +- {drivers/sharp => gui/demos}/clocktest.py | 19 +-- gui/demos/epd29_test.py | 2 +- gui/demos/mono_test.py | 37 +++--- {drivers/sharp => gui/demos}/sharptest.py | 6 +- ssd1306_setup.py | 48 -------- 14 files changed, 259 insertions(+), 204 deletions(-) create mode 100644 color_setup/sharp_setup.py create mode 100644 color_setup/ssd1306_setup.py rename {drivers/sharp => gui/demos}/clock_batt.py (90%) rename {drivers/sharp => gui/demos}/clocktest.py (84%) rename {drivers/sharp => gui/demos}/sharptest.py (84%) delete mode 100644 ssd1306_setup.py diff --git a/ASYNC.md b/ASYNC.md index 6f0b8c6..e9908f1 100644 --- a/ASYNC.md +++ b/ASYNC.md @@ -9,17 +9,22 @@ frame buffer on the host, transferring its entire contents to the display hardware, usually via I2C or SPI. Current drivers block for the time taken by this. -In the case of the Pyboard driver for Adafruit 1.5 and 1.27 inch displays, +In the case of the Pyboard driver for Adafruit 1.5 and 1.27 inch OLED displays, running on a Pyboard 1.x, blocking is for 41ms. Blocking periods for monochrome -or smaller colour dislays will be shorter. On hosts which don't support inline +or smaller colour displays will be shorter. On hosts which don't support inline Arm Thumb assembler or the viper emitter it will be very much longer. +For large displays such as ePaper the blocking time is on the order of 250ms on +a Pyboard, longer on hardware such as ESP32. Such drivers have a special `asyn` +constructor arg which causes refresh to be performed by a coroutine; this +periodically yields to the scheduler and limits blocking to around 30ms. + Blocking occurs when the `nanogui.refresh` function is called. In typical applications which might wait for user input from a switch this blocking is not apparent and the response appears immediate. It may have consequences in applications performing fast concurrent input over devices such as UARTs. -# Demo scripts +## Demo scripts These require uasyncio V3. This is incorporated in daily builds and will be available in release builds starting with MicroPython V1.13. The demos assume diff --git a/DRIVERS.md b/DRIVERS.md index 0e09ea4..19b4e2a 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -2,6 +2,15 @@ The nano-gui project currently supports four display technologies: OLED (color and monochrome), color TFT, monochrome Sharp displays and EPD (ePaper/eInk). +All drivers provide a display class subclassed from the built-in +`framebuf.FrameBuffer` class. This provides three increasing levels of support: + * Graphics via the `FrameBuffer` graphics primitives. + * Text rendering in arbitrary fonts via `Writer` and `Cwriter` classes (see + [font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git)). + * Use with nano-gui. + +It should be noted that in the interests of conserving RAM these drivers offer +a bare minimum of functionality required to support the above. # Contents @@ -26,9 +35,11 @@ and monochrome), color TFT, monochrome Sharp displays and EPD (ePaper/eInk). 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) - 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) +      7.1.3 [EPD public bound variables](./DRIVERS.md#713-epd-public-bound-variables) + 7.2 [Waveshare eInk Display HAT](./DRIVERS.md#72-waveshare-eink-display-hat) +      7.2.1 [EPD constructor args](./DRIVERS.md#721-epd-constructor-args) +      7.2.2 [EPD public methods](./DRIVERS.md#722-epd-public-methods) +      7.2.3 [EPD public bound variables](./DRIVERS.md#723-epd-public-bound-variables) 8. [EPD Asynchronous support](./DRIVERS.md#8-epd-asynchronous-support) 9. [Writing device drivers](./DRIVERS.md#8-writing-device-drivers) @@ -36,13 +47,8 @@ and monochrome), color TFT, monochrome Sharp displays and EPD (ePaper/eInk). # 1. Introduction -With the exception of the Sharp displays use of these drivers is very simple: -the main reason to consult this doc is to select the right driver for your -display, platform and application. - -An application specifies a driver by means of `color_setup.py` or -`ssd1306_setup.py` located in the root directory of the target. This typically -contains code along these lines: +An application specifies a driver by means of `color_setup.py` located in the +root directory of the target. This typically contains code along these lines: ```python import machine import gc @@ -51,12 +57,15 @@ 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, baudrate=10_000_000) # baudrate depends on display chip -gc.collect() # Precaution before instantiating framebuf +gc.collect() +# Precaution before instantiating framebuf. The next line creates the buffer. ssd = SSD(spi, pcs, pdc, prst, 96) # Create a display instance ``` -In the interests of conserving RAM, supplied drivers support only the -functionality required by the GUI. More fully featured drivers may better suit -other applications. +The directory `color_setup` contains example files for various displays. These +may be adapted and copied to `color_setup.py` on the target's root. The entry +in this doc for the specific display should be consulted for SSD constructor +arguments and SPI baudrate. The more exotic displays (Sharp and ePaper) have +additional features and requirements detailed below. ## 1.1 Color handling @@ -438,7 +447,8 @@ Positional args: with value 0 (unusually the hardware CS line is active high). 3. `height=240` Dimensions in pixels. Defaults are for 2.7" display. 4. `width=400` - 5. `vcom=False` Accept the default unless using `pyb.standby`. See 3.2. + 5. `vcom=False` Accept the default unless using `pyb.standby`. See + [6.3.2](./DRIVERS.md#632-the-vcom-arg). ### 6.3.1 Device driver methods @@ -502,12 +512,13 @@ the demo. # 7. ePaper displays -These tend to be monochrome or to support no more than three colors. They also -have very long refresh times (many seconds). The benefit is zero current -between refreshes: it is possible to switch off power completely with the -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. +Known as ePaper or eInk, electrophoretic (EPD) displays are usually monochrome. +Some support a few levels of grey or a very small range of colors. They have +long refresh times (many seconds). The principal benefit that they consume zero +current except while being refreshed: it is possible to switch off power +completely with the device retaining the image indefinitely. Present day EPD +units perform the slow refresh autonomously. It makes no demands on the CPU +enabling user code to continue to run. 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. @@ -527,7 +538,11 @@ 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. +the buffer size down (to 4736 bytes) there is no greyscale support. It should +be noted that the Adafruit site cautions against refreshing these displays more +frequently than every 180s. It is +[unclear](https://forums.adafruit.com/viewtopic.php?f=19&t=174091) if this is +an absolute limit or an average rate. ##### Wiring @@ -578,9 +593,24 @@ see below. to the display. * `wait` Asynchronous. No args. Pause until the display refresh is complete. +### 7.1.3 EPD public bound variables + * `height` Integer. Height in pixels. Treat as read-only. + * `width` Integer. Width in pixels. Treat as read-only. + * `demo_mode=False` Boolean. If set `True` after instantiating, `refresh()` + will block until display update is complete, and then for a further two + seconds to enable viewing. This enables generic nanogui demos to be run on an + EPD. -** POWER DOWN HARDWARE ** +##### Micropower use + +To power down the display the `ENA` pin must be pulled to 0v. Some +microcontrollers can ensure that a GPIO pin is able to sink current when the +chip goes into deep sleep. In other cases the pin becomes high impedance. The +following ensures that a high impedance pin will cause `ENA` to be pulled low. +The N channel MOSFET must have a low threshold voltage. + +![Image](images/epd_enable.png) ## 7.2 Waveshare eInk Display HAT @@ -649,38 +679,54 @@ Pins 26-40 unused and omitted. to the display. * `wait` Asynchronous. No args. Pause until the display refresh is complete. +### 7.2.3 EPD public bound variables + + * `height` Integer. Height in pixels. Treat as read-only. + * `width` Integer. Width in pixels. Treat as read-only. + * `demo_mode=False` Boolean. If set `True` after instantiating, `refresh()` + will block until display update is complete, and then for a further two + seconds to enable viewing. This enables generic nanogui demos to be run on an + EPD. + # 8. EPD Asynchronous support Normally when GUI code issues ```python -refresh(ssd) +refresh(ssd) # 250ms or longer depending on platform ``` 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. +code blocks while copying data to the display before returning. Subsequent +physical refresh is performed by the display hardware taking several seconds. +While physical refresh is nonblocking, the initial blocking period 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. +A `.updated()` method lets user code pause after issuing `refresh()`. The pause +lasts until the framebuf has been entirely copied to the hardware. The +application is then free to alter the framebuf contents. +It is invalid to issue `.refresh()` until the physical display refresh is +complete; if this is attempted a `RuntimeError` will occur. The most efficient +way to ensure that this cannot occur is to await the `.wait()` prior to any +refresh. This method will pause until any physical update is complete. + +The following illustrates the kind of approach which may be used ```python while True: - # Normal procedure before refresh, but 10s sleep should mean it always returns immediately + # Before refresh, ensure that a previous refresh is complete await ssd.wait() - refresh(ssd) # Launches ._as_show() + refresh(ssd) # Immediate return. Creates a task to copy content to EPD. + # Wait until the framebuf content has been passed to EPD. await ssd.updated() - # Content has now been shifted out so coros can update + # Trigger an event which allows other tasks to update the # framebuffer in background evt.set() evt.clear() - await asyncio.sleep(20) # Allow for slow refresh + # The 2.9 inch display should not be updated too frequently + await asyncio.sleep(180) ``` # 9. Writing device drivers @@ -700,7 +746,15 @@ using a forum search. 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. +for monochrome drivers. Generality can be extended by providing this static +method: +```python + @staticmethod + def rgb(r, g, b): + return int((r > 127) or (g > 127) or (b > 127)) +``` +This ensures compatibility with code written for color displays by converting +RGB values to a single bit. 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 diff --git a/README.md b/README.md index 6aabc17..efa8f7e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ A lightweight and minimal MicroPython GUI library for display drivers based on the `FrameBuffer` class. Various display technologies are supported, including -small color and monochrome OLED's and color TFT's. The GUI is cross-platform. +small color and monochrome OLED's, color TFT's, ePaper and Sharp units. The GUI +is cross-platform. These images, most from OLED displays, fail to reproduce the quality of these displays. OLEDs are visually impressive displays with bright colors, wide @@ -44,10 +45,9 @@ wiring details, pin names and hardware issues.      2.1.1 [Core files](./README.md#211-core-files)      2.1.2 [Demo Scripts](./README.md#212-demo-scripts)      2.1.3 [Fonts](./README.md#213-fonts) -      2.1.4 [Color setup examples](./README.md#214-color-setup-examples) +      2.1.4 [Hardware setup examples](./README.md#214-hardware-setup-examples) 2.2 [Dependencies](./README.md#21-dependencies) -      2.2.1 [Monochrome use](./README.md#211-monochrome-use) -      2.2.2 [Color use](./README.md#222-color-use) + 2.3 [Verifying hardware configuration](./README.md#23-verifying-hardware-configuration) 3. [The nanogui module](./README.md#3-the-nanogui-module) 3.1 [Application Initialisation](./README.md#31-application-initialisation) Initial setup and refresh method.      3.1.1 [User defined colors](./README.md#311-user-defined-colors) @@ -60,8 +60,6 @@ wiring details, pin names and hardware issues. 3.7 [Class Textbox](./README.md#37-class-textbox) Scrolling text display. 4. [ESP8266](./README.md#4-esp8266) This can work. Contains information on minimising the RAM and flash footprints of the GUI. - 5. [Hardware configuration](./README.md#5-hardware-configuration) How to write - color_setup.py #### [Device driver document.](./DRIVERS.md) @@ -96,6 +94,8 @@ my GUI's employ the American spelling of `color`. ## 1.1 Change log +17 Jan 2021 Add ePaper drivers. Ensure monochrome and color setup requirements +are identical. Substantial update to docs. 16 Dec 2020 Add ILI9341 driver, 4-bit drivers and SPI bus sharing improvements. These mean that `color_setup.py` should now set SPI baudrate. 29 Nov 2020 Add ST7735R TFT drivers. @@ -134,6 +134,10 @@ Compatible and tested display drivers include: [1.44 inch](https://www.adafruit.com/product/2088) documented [here](./DRIVERS.md#4-drivers-for-st7735r). * Drivers for ILI9341 such as [Adafruit 3.2 inch](https://www.adafruit.com/product/1743) documented [here](./DRIVERS.md#5-drivers-for-ili9341). + * [Adafruit 2.9 inch ePaper display](https://www.adafruit.com/product/4262) + documented [here](./DRIVERS.md#71-adafruit-flexible-eink-display). + * [Waveshare 2.7 inch ePaper HAT](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT) + documented [here with a warning](./DRIVERS.md#72-waveshare-eink-display-hat). Widgets are intended for the display of data from physical devices such as sensors. They are drawn using graphics primitives rather than icons to minimise @@ -187,25 +191,26 @@ Filesystem space may be conserved by copying only the required driver from for SSD1351 displays only the following are actually required: `drivers/ssd1351/ssd1351.py`, `drivers/ssd1351/__init__.py`. +The small `color_setup.py` file contains all hardware definitions (for color or +monochrome displays). This is the only file which will require editing to match +the display and its wiring. For information on how to do this, see +[the drivers document](./DRIVERS.md#1-introduction). + ## 2.1 Files ### 2.1.1 Core files -The root directory contains two example setup files, for monochrome and color -displays respectively. Other examples may be found in the `color_setup` -directory. These are templates for adaptation: only one file is copied to the -target. On the target a color file should be named `color_setup.py`. The -monochrome `ssd1306_setup.py` retains its own name. +The root directory contains an example setup file `color_setup.py` for a color +OLED display. Other examples may be found in the `color_setup` directory. These +are templates for adaptation: only one file is copied to the target. On the +target the file should be named `color_setup.py` and put in the root of the +filesystem. The chosen template will need to be edited to match the display in use, the MicroPython target and the electrical connections between display and target. Electrical connections are detailed in the driver source. - * `color_setup.py` Setup for color displays. As written supports an SSD1351 - display connected to a Pyboard. - * `ssd1306_setup.py` Setup file for monochrome displays using the official - driver. Supports hard or soft SPI or I2C connections, as does the test script - `mono_test.py`. On non Pyboard targets this will require adaptation to match - the hardware connections. + * `color_setup.py` Hardware setup for the display. As written supports an + SSD1351 display connected to a Pyboard. The `gui/core` directory contains the GUI core and its principal dependencies: @@ -225,6 +230,7 @@ The `gui/core` directory contains the GUI core and its principal dependencies: The `gui/demos` directory contains test/demo scripts. +Demos for small displays: * `mono_test.py` Tests/demos using the official SSD1306 driver for a monochrome 128*64 OLED display. * `color96.py` Tests/demos for the Adafruit 0.96 inch color OLED. @@ -240,23 +246,20 @@ Demos for larger displays. * `tbox.py` Demo `Textbox` class. Cross-platform. * `scale_ili.py` A special demo of the asychronous mode of the ILI9341 driver. -Demos for ePaper displays: +Demos for ePaper displays: * `waveshare_test.py` For the Waveshare eInk Display HAT 2.7" 176*274 portrait mode display. + * `epd29_test.py` Demo for Adafruit 2.9" eInk display. + +Demos for Sharp displays: + * `sharptest.py` Basic functionality check. + * `clocktest.py` Digital and analog clock demo. + * `clock_batt.py` Low power demo of battery operated clock. Usage with `uasyncio` is discussed [here](./ASYNC.md). In summary the blocking which occurs during transfer of the framebuffer to the display may affect more demanding `uasyncio` applications. More generally the GUI works well with it. -Demo scripts for Sharp displays are in `drivers/sharp`. Check source code for -wiring details. See [the docs](./DRIVERS.md#6-drivers-for-sharp-displays). They -may be run as follows: -```python -import drivers.sharp.sharptest -# or -import drivers.sharp.clocktest -``` - ###### [Contents](./README.md#contents) ### 2.1.3 Fonts @@ -280,12 +283,14 @@ is required `-f` is also required. Supplied examples are: * `font10.py` * `freesans20.py` -### 2.1.4 Color setup examples +### 2.1.4 Hardware setup examples The `color_setup` directory contains example setup files for various hardware. These are templates which may be adapted to suit the hardware in use, then -copied to the hardware root as `color_setup.py`. +copied to the hardware root as `color_setup.py`. Example files: + * `ssd1306_setup.py` Setup file for monochrome displays using the official + driver. Supports hard or soft SPI or I2C connections. * `esp32_setup.py` As written supports an ESP32 connected to a 128x128 SSD1351 display. After editing to match the display and wiring, it should be copied to the target as `/pyboard/color_setup.py`. @@ -297,6 +302,7 @@ copied to the hardware root as `color_setup.py`. [Adafruit 1.44 inch TFT display](https://www.adafruit.com/product/2088). * `ili9341_setup.py` A 240*320 ILI9341 display on ESP32. * `waveshare_setup.py` 176*274 portrait mode ePaper display. + * `epd96_asyn.py` Adafruit 2.9 inch ePaper display, optimised for `uasyncio`. ## 2.2 Dependencies @@ -310,10 +316,6 @@ Optional feature: * An STM32 implementation of [this optimisation](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#224-a-performance-boost). -###### [Contents](./README.md#contents) - -### 2.2.1 Monochrome use - A copy of the official driver for OLED displays using the SSD1306 chip is provided. The official file is here: * [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). @@ -325,11 +327,13 @@ in this repo but may be found here: The Sharp display is supported in `drivers/sharp`. See [README](./DRIVERS.md#6-drivers-for-sharp-displays) and demos. -### 2.2.2 Color use +###### [Contents](./README.md#contents) + +## 2.3 Verifying hardware configuration This script performs a basic check that the `color_setup.py` file matches the -hardware, that all three primary colors can be displayed and that pixels up to -the edges of the display can be accessed. +hardware, that (on color units) all three primary colors can be displayed and +that pixels up to the edges of the display can be accessed. ```python from color_setup import ssd # Create a display instance from gui.core.nanogui import refresh @@ -362,11 +366,11 @@ Text components of widgets are rendered using the `Writer` (monochrome) or ## 3.1 Application Initialisation -The GUI is initialised for color display by issuing: +The GUI is initialised by issuing: ```python from color_setup import ssd ``` -This defines the hardware as described in [Hardware configuration](./README.md#5-hardware-configuration). +This defines the hardware as described in [the drivers document](./DRIVERS.md#1-introduction). A typical application then imports `nanogui` modules and clears the display: ```python @@ -846,33 +850,3 @@ With the 4 bit driver `scale.py` reported 18112 bytes. In conclusion I think that applications of moderate complexity should be feasible. ###### [Contents](./README.md#contents) - -# 5. Hardware configuration - -The file `color_setup.py` contains the hardware dependent code. It works as -described below, with the aim of allocating the `FrameBuffer` before importing -other modules. This is intended to reduce the risk of memory failures. - -This example is for SSD1351 devices where a single driver supports displays of -two different heights. In general [the driver doc](./DRIVERS.md) should be -consulted for correct SSD constructor arguments. - -Firstly the file sets the display height and imports the driver: -```python -height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch -import machine -import gc -from drivers.ssd1351.ssd1351 import SSD1351 as SSD # Import the display driver -``` -It then sets up the bus (SPI or I2C) and instantiates the display. At this -point the framebuffer is created: -```python -pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) -pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) -prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) -spi = machine.SPI(1) -gc.collect() # Precaution before instantiating framebuf -ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance -``` - -###### [Contents](./README.md#contents) diff --git a/color_setup/sharp_setup.py b/color_setup/sharp_setup.py new file mode 100644 index 0000000..146ec21 --- /dev/null +++ b/color_setup/sharp_setup.py @@ -0,0 +1,31 @@ +# sharp_setup.py Customise for your hardware config + +# Released under the MIT License (MIT). See LICENSE. +# Copyright (c) 2020 Peter Hinch + +# As written, supports Adafruit 2.7 inch 400*240 Sharp display +# https://www.adafruit.com/product/4694 + +# 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. + +# SSD1331 drivers are cross-platform. +# WIRING (Adafruit pin nos and names with Pyboard pins). +# Pyb SSD +# Vin Vin Pyboard: Vin is a 5V output when powered by USB +# Gnd Gnd +# Y8 DI +# Y6 CLK +# Y5 CS + +import machine +import gc + +from drivers.sharp.sharp import SHARP as SSD + +pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high +# Baudrate ref. https://learn.adafruit.com/adafruit-sharp-memory-display-breakout/circuitpython-displayio-usage +spi = machine.SPI(2, baudrate=2_000_000) +gc.collect() +ssd = SSD(spi, pcs) diff --git a/color_setup/ssd1306_setup.py b/color_setup/ssd1306_setup.py new file mode 100644 index 0000000..3d54def --- /dev/null +++ b/color_setup/ssd1306_setup.py @@ -0,0 +1,49 @@ +# ssd1306_setup.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display. +# ssd1306_setup.py Device initialisation. Copy to color_setup.py on host. + +# Released under the MIT License (MIT). See LICENSE. +# Copyright (c) 2018-2021 Peter Hinch + + +# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display +# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html + +import machine +from drivers.ssd1306.ssd1306 import SSD1306_SPI, SSD1306_I2C + +WIDTH = const(128) +HEIGHT = const(64) +use_spi = False # I2C or SPI +soft = True # Soft or hard I2C/SPI + +# Export an initialised ssd display object. +if use_spi: + # Pyb SSD + # 3v3 Vin + # Gnd Gnd + # X1 DC + # X2 CS + # X3 Rst + # X6 CLK + # X8 DATA + pdc = machine.Pin('Y1', machine.Pin.OUT_PP) + pcs = machine.Pin('Y2', machine.Pin.OUT_PP) + prst = machine.Pin('Y3', machine.Pin.OUT_PP) + if soft: + spi = machine.SPI(sck=machine.Pin('Y6'), mosi=machine.Pin('Y8'), miso=machine.Pin('Y7')) + else: + spi = machine.SPI(2) + ssd = SSD1306_SPI(WIDTH, HEIGHT, spi, pdc, prst, pcs) +else: # I2C + # Pyb SSD + # 3v3 Vin + # Gnd Gnd + # Y9 CLK + # Y10 DATA + if soft: + pscl = machine.Pin('Y9', machine.Pin.OPEN_DRAIN) + psda = machine.Pin('Y10', machine.Pin.OPEN_DRAIN) + i2c = machine.SoftI2C(scl=pscl, sda=psda) + else: + i2c = machine.I2C(2) + ssd = SSD1306_I2C(WIDTH, HEIGHT, i2c) diff --git a/color_setup/ssd1331_setup.py b/color_setup/ssd1331_setup.py index af955e6..f0e71e6 100644 --- a/color_setup/ssd1331_setup.py +++ b/color_setup/ssd1331_setup.py @@ -1,4 +1,4 @@ -# color_setup.py Customise for your hardware config +# ssd1331_setup.py Customise for your hardware config # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2020 Peter Hinch diff --git a/drivers/sharp/sharp.py b/drivers/sharp/sharp.py index e80d039..33ada6b 100644 --- a/drivers/sharp/sharp.py +++ b/drivers/sharp/sharp.py @@ -6,7 +6,7 @@ # https://www.adafruit.com/product/3502 1.3 inch 144x168 # https://www.adafruit.com/product/1393 1.3 inch 96x96 Monochrome -# Copyright (c) Peter Hinch 2020 +# Copyright (c) Peter Hinch 2020-2021 # Released under the MIT license see LICENSE # Code checked against https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay @@ -23,6 +23,9 @@ _VCOM = const(2) class SHARP(framebuf.FrameBuffer): + @staticmethod + def rgb(r, g, b): + return int((r > 127) or (g > 127) or (b > 127)) def __init__(self, spi, pincs, height=240, width=400, vcom=False): spi.init(baudrate=2_000_000, firstbit=machine.SPI.LSB) # Data sheet: should support 2MHz diff --git a/drivers/ssd1306/ssd1306.py b/drivers/ssd1306/ssd1306.py index 6359c85..1364bd3 100644 --- a/drivers/ssd1306/ssd1306.py +++ b/drivers/ssd1306/ssd1306.py @@ -26,6 +26,10 @@ SET_CHARGE_PUMP = const(0x8D) # Subclassing FrameBuffer provides support for graphics primitives # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): + @staticmethod + def rgb(r, g, b): + return int((r > 127) or (g > 127) or (b > 127)) + def __init__(self, width, height, external_vcc): self.width = width self.height = height diff --git a/drivers/sharp/clock_batt.py b/gui/demos/clock_batt.py similarity index 90% rename from drivers/sharp/clock_batt.py rename to gui/demos/clock_batt.py index 00a3d3e..6e77e3e 100644 --- a/drivers/sharp/clock_batt.py +++ b/gui/demos/clock_batt.py @@ -18,17 +18,8 @@ # when instantiating the frame buffer. The aim is to do this as early as # possible before importing other modules. -import machine -import gc -from drivers.sharp.sharp import SHARP as SSD +from color_setup import ssd # Create a display instance -# Initialise hardware -pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high -spi = machine.SPI(2) -gc.collect() # Precaution before instantiating framebuf -ssd = SSD(spi, pcs) - -# Now import other modules import upower from gui.core.nanogui import refresh from gui.widgets.label import Label diff --git a/drivers/sharp/clocktest.py b/gui/demos/clocktest.py similarity index 84% rename from drivers/sharp/clocktest.py rename to gui/demos/clocktest.py index ad50062..64b285f 100644 --- a/drivers/sharp/clocktest.py +++ b/gui/demos/clocktest.py @@ -16,17 +16,8 @@ # when instantiating the frame buffer. The aim is to do this as early as # possible before importing other modules. -import machine -import gc -from drivers.sharp.sharp import SHARP as SSD +from color_setup import ssd # Create a display instance -# Initialise hardware -pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high -spi = machine.SPI(2) -gc.collect() # Precaution before instantiating framebuf -ssd = SSD(spi, pcs) - -# Now import other modules from gui.core.nanogui import refresh from gui.widgets.label import Label from gui.widgets.dial import Dial, Pointer @@ -68,11 +59,11 @@ def aclock(): sstart = 0 + 0.92j while True: t = utime.localtime() - hang = -t[4]*pi/6 - t[5]*pi/360 # Angles of hour and minute hands - mang = -t[5] * pi/30 - sang = -t[6] * pi/30 + hang = -t[3]*pi/6 - t[4]*pi/360 # Angles of hour and minute hands + mang = -t[4] * pi/30 + sang = -t[5] * pi/30 if abs(hang - mang) < pi/360: # Avoid overlap of hr and min hands - hang += pi/30 # which is visually confusing. Add slight lag to hts + hang += pi/30 # which is visually confusing. Add slight lag to hrs hrs.value(hstart * uv(hang)) mins.value(mstart * uv(mang)) secs.value(sstart * uv(sang)) diff --git a/gui/demos/epd29_test.py b/gui/demos/epd29_test.py index 50adf84..894fcf6 100644 --- a/gui/demos/epd29_test.py +++ b/gui/demos/epd29_test.py @@ -6,6 +6,7 @@ # color_setup must set landcsape True, asyn True and must not set demo_mode import uasyncio as asyncio from color_setup import ssd +# On a monochrome display Writer is more efficient than CWriter. from gui.core.writer import Writer from gui.core.nanogui import refresh from gui.widgets.meter import Meter @@ -13,7 +14,6 @@ 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. diff --git a/gui/demos/mono_test.py b/gui/demos/mono_test.py index da7886c..3f65e7d 100644 --- a/gui/demos/mono_test.py +++ b/gui/demos/mono_test.py @@ -1,16 +1,19 @@ # mono_test.py Demo program for nano_gui on an SSD1306 OLED display. # Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2018-2020 Peter Hinch +# Copyright (c) 2018-2021 Peter Hinch # https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display # https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html +# V0.33 16th Jan 2021 Hardware configuration is now defined in color_setup to be +# consistent with other displays. # V0.32 5th Nov 2020 Replace uos.urandom for minimal ports import utime # import uos -from ssd1306_setup import setup +from color_setup import ssd +# On a monochrome display Writer is more efficient than CWriter. from gui.core.writer import Writer from gui.core.nanogui import refresh from gui.widgets.meter import Meter @@ -33,8 +36,9 @@ def xorshift64star(modulo, seed = 0xf9ac6ba4): return (x * 0x2545F4914F6CDD1D) % modulo return func -def fields(use_spi=False, soft=True): - ssd = setup(use_spi, soft) # Create a display instance +def fields(): + ssd.fill(0) + refresh(ssd) Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it wri = Writer(ssd, fixed, verbose=False) wri.set_clip(False, False, False) @@ -53,8 +57,9 @@ def fields(use_spi=False, soft=True): textfield.value('Done', True) refresh(ssd) -def multi_fields(use_spi=False, soft=True): - ssd = setup(use_spi, soft) # Create a display instance +def multi_fields(): + ssd.fill(0) + refresh(ssd) Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it wri = Writer(ssd, small, verbose=False) wri.set_clip(False, False, False) @@ -79,11 +84,10 @@ def multi_fields(use_spi=False, soft=True): Label(wri, 0, 64, ' DONE ', True) refresh(ssd) -def meter(use_spi=False, soft=True): - ssd = setup(use_spi, soft) - wri = Writer(ssd, arial10, verbose=False) +def meter(): ssd.fill(0) refresh(ssd) + wri = Writer(ssd, arial10, verbose=False) m0 = Meter(wri, 5, 2, height = 50, divisions = 4, legends=('0.0', '0.5', '1.0')) m1 = Meter(wri, 5, 44, height = 50, divisions = 4, legends=('-1', '0', '+1')) m2 = Meter(wri, 5, 86, height = 50, divisions = 4, legends=('-1', '0', '+1')) @@ -99,14 +103,15 @@ def meter(use_spi=False, soft=True): tstr = '''Test assumes a 128*64 (w*h) display. Edit WIDTH and HEIGHT in ssd1306_setup.py for others. Device pinouts are comments in ssd1306_setup.py. -All tests take two boolean args: -use_spi = False. Set True for SPI connected device -soft=True set False to use hardware I2C/SPI. Hardware I2C option currently fails with official SSD1306 driver. -Available tests: -fields() Label test with dynamic data. -multi_fields() More Labels. -meter() Demo of Meter object. +Test runs to completion. ''' print(tstr) +print('Basic test of fields.') +fields() +print('More fields.') +multi_fields() +print('Meters.') +meter() +print('Done.') diff --git a/drivers/sharp/sharptest.py b/gui/demos/sharptest.py similarity index 84% rename from drivers/sharp/sharptest.py rename to gui/demos/sharptest.py index a551079..d246fdb 100644 --- a/drivers/sharp/sharptest.py +++ b/gui/demos/sharptest.py @@ -13,8 +13,7 @@ # Y6 CLK # Y5 CS -import machine -from drivers.sharp.sharp import SHARP as SSD +from color_setup import ssd # Create a display instance # Fonts for Writer import gui.fonts.freesans20 as freesans20 import gui.fonts.arial_50 as arial_50 @@ -23,9 +22,6 @@ from gui.core.writer import Writer import time def test(): - pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high - spi = machine.SPI(2) - ssd = SSD(spi, pcs) rhs = ssd.width -1 ssd.line(rhs - 80, 0, rhs, 80, 1) square_side = 40 diff --git a/ssd1306_setup.py b/ssd1306_setup.py deleted file mode 100644 index f5a2d08..0000000 --- a/ssd1306_setup.py +++ /dev/null @@ -1,48 +0,0 @@ -# ssd1306_setup.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display. -# Device initialisation - -# Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2018-2020 Peter Hinch - - -# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display -# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html - -import machine -from drivers.ssd1306.ssd1306 import SSD1306_SPI, SSD1306_I2C - -WIDTH = const(128) -HEIGHT = const(64) - -def setup(use_spi=False, soft=True): - if use_spi: - # Pyb SSD - # 3v3 Vin - # Gnd Gnd - # X1 DC - # X2 CS - # X3 Rst - # X6 CLK - # X8 DATA - pdc = machine.Pin('Y1', machine.Pin.OUT_PP) - pcs = machine.Pin('Y2', machine.Pin.OUT_PP) - prst = machine.Pin('Y3', machine.Pin.OUT_PP) - if soft: - spi = machine.SPI(sck=machine.Pin('Y6'), mosi=machine.Pin('Y8'), miso=machine.Pin('Y7')) - else: - spi = machine.SPI(2) - ssd = SSD1306_SPI(WIDTH, HEIGHT, spi, pdc, prst, pcs) - else: # I2C - # Pyb SSD - # 3v3 Vin - # Gnd Gnd - # Y9 CLK - # Y10 DATA - if soft: - pscl = machine.Pin('Y9', machine.Pin.OPEN_DRAIN) - psda = machine.Pin('Y10', machine.Pin.OPEN_DRAIN) - i2c = machine.I2C(scl=pscl, sda=psda) - else: - i2c = machine.I2C(2) - ssd = SSD1306_I2C(WIDTH, HEIGHT, i2c) - return ssd