├── .gitignore ├── ADAFRUIT.md ├── ASYNC.md ├── DISPLAYS.md ├── DRIVERS.md ├── FPLOT.md ├── IMAGE_DISPLAY.md ├── LICENSE ├── README.md ├── drivers ├── boolpalette.py ├── epaper │ ├── epaper2in7_fb.py │ ├── epd29.py │ ├── epd29_ssd1680.py │ ├── package.json │ ├── pico_epaper_42.py │ ├── pico_epaper_42_gs.py │ ├── pico_epaper_42_v2.py │ └── pico_epaper_42_v2_gs.py ├── gc9a01 │ ├── gc9a01.py │ ├── gc9a01_8_bit.py │ └── package.json ├── ili93xx │ ├── ili9341.py │ ├── ili9341_8bit.py │ └── package.json ├── ili94xx │ ├── ili9486.py │ ├── ili9488.py │ └── package.json ├── sh1106 │ ├── package.json │ └── sh1106.py ├── sharp │ ├── __init__.py │ ├── package.json │ └── sharp.py ├── ssd1306 │ ├── package.json │ └── ssd1306.py ├── ssd1327 │ ├── package.json │ └── ssd1327.py ├── ssd1331 │ ├── __init__.py │ ├── package.json │ ├── ssd1331.py │ ├── ssd1331_16bit.py │ └── test_colors.py ├── ssd1351 │ ├── __init__.py │ ├── package.json │ ├── ssd1351.py │ ├── ssd1351_16bit.py │ ├── ssd1351_4bit.py │ ├── ssd1351_generic.py │ ├── test128_row.py │ ├── test96_row.py │ └── test_colors_96.py ├── st7567s │ ├── package.json │ └── st7567s.py ├── st7735r │ ├── package.json │ ├── st7735r.py │ ├── st7735r144.py │ ├── st7735r144_4bit.py │ └── st7735r_4bit.py └── st7789 │ ├── package.json │ ├── st7789_4bit.py │ └── st7789_8bit.py ├── extras ├── DATE.md ├── README.md ├── date.py ├── demos │ ├── calendar.py │ ├── clock_test.py │ ├── eclock_async.py │ └── eclock_test.py ├── parse2d.py └── widgets │ ├── calendar.py │ ├── clock.py │ ├── eclock.py │ └── grid.py ├── gui ├── core │ ├── __init__.py │ ├── colors.py │ ├── fplot.py │ ├── nanogui.py │ └── writer.py ├── demos │ ├── __init__.py │ ├── aclock.py │ ├── aclock_large.py │ ├── aclock_ttgo.py │ ├── alevel.py │ ├── asnano.py │ ├── asnano_sync.py │ ├── clock_batt.py │ ├── clocktest.py │ ├── color15.py │ ├── color96.py │ ├── epd29_async.py │ ├── epd29_lowpower.py │ ├── epd29_sync.py │ ├── epd_async.py │ ├── fpt.py │ ├── mono_test.py │ ├── round.py │ ├── scale.py │ ├── sharptest.py │ └── tbox.py ├── fonts │ ├── arial10.py │ ├── arial35.py │ ├── arial_50.py │ ├── courier20.py │ ├── font10.py │ ├── font6.py │ └── freesans20.py └── widgets │ ├── __init__.py │ ├── dial.py │ ├── label.py │ ├── led.py │ ├── meter.py │ ├── scale.py │ └── textbox.py ├── images ├── IMG_2885.png ├── IMG_2887.png ├── V1_EPD.JPG ├── V2_EPD.JPG ├── alevel.png ├── bernoulli.png ├── cartesian.png ├── cat_color.JPG ├── clock.png ├── deer_mono.JPG ├── discont.png ├── epd_enable.png ├── esp8266_tree.JPG ├── fonts.png ├── lissajous.png ├── me_color.JPG ├── meters.png ├── partial.JPG ├── polar.png ├── round.JPG ├── rtpolar.png ├── scale.JPG ├── seismo.JPG ├── sine.png └── textbox1.JPG ├── img_cvt.py ├── package.json └── setup_examples ├── epd29_pico.py ├── epd29_pyb_async.py ├── epd29_pyb_sync.py ├── epd_waveshare_42_pico.py ├── gc9a01_pico.py ├── ili9341_esp32.py ├── ili9341_esp32_2432S024c.py ├── ili9341_esp32_2432S028r.py ├── ili9341_esp32_wroverkit.py ├── ili9341_pico.py ├── ili9486_pico.py ├── ili9488_esp32.py ├── ili9488_pico.py ├── sh1106_spi_pico.py ├── sharp_pyb.py ├── ssd1306_i2c_esp32.py ├── ssd1306_i2c_esp8266.py ├── ssd1306_pyb.py ├── ssd1331_pyb.py ├── ssd1351_esp32.py ├── ssd1351_esp8266.py ├── ssd1351_pico.py ├── ssd1351_pyb.py ├── ssd1680_esp32.py ├── st7567_i2c_esp32.py ├── st7735r144_pyb.py ├── st7735r_esp32.py ├── st7735r_pyb.py ├── st7789_pico.py ├── st7789_pico_lcd_114.py ├── st7789_ttgo.py ├── st7789_ws_pico_res_touch_pico.py ├── waveshare_demo.py └── waveshare_pyb.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /ADAFRUIT.md: -------------------------------------------------------------------------------- 1 | # Adafruit and other OLED displays 2 | 3 | ###### [Main README](./README.md) 4 | 5 | # SPI Pin names and wiring 6 | 7 | The names used on the Pyboard are the correct names for SPI signals. Some OLED 8 | displays use different names. Adafruit use abbreviated names where space is at 9 | a premium. The following table shows the correct names followed by others I 10 | have seen. The column labelled "Adafruit" references pin numbers on the 1.27 11 | and 1.5 inch displays. Pin numbering on the 0.96 inch display differs: pin 12 | names are as below (SCK is CLK on this unit). 13 | 14 | Pyboard pins are for SPI(1). Adapt for SPI(2) or other hardware. 15 | 16 | | Pin | Pyboard | Display | Adafruit | Alternative names | 17 | |:---:|:-------:|:-------:|:--------:|:---------:| 18 | | 3V3 | 3V3 | | Vin (10) | | 19 | | Gnd | Gnd | | Gnd (11) | | 20 | | X1 | X1 | | DC (3) | | 21 | | X2 | X2 | | CS (5) | OC OLEDCS | 22 | | X3 | X3 | | Rst (4) | R RESET | 23 | | X6 | SCK | SCK | CL (2) | SCK CLK | 24 | | X8 | MOSI | MOSI | SI (1) | DATA SI | 25 | | X7 | MISO | MISO | SO (7) | MISO (see below) | 26 | | X21 | X21 | | SC (6) | SDCS (see below) | 27 | 28 | The last two pins above are specific to Adafruit 1.27 and 1.5 inch displays and 29 | only need to be connected if the SD card is to be used. The pin labelled CD on 30 | those displays is a card detect signal; it can be ignored. The pin labelled 3Vo 31 | is an output: these displays can be powered from +5V. 32 | 33 | Pyboard pins are arbitrary with the exception of MOSI, SCK and MISO. These can 34 | be changed if software SPI is used. 35 | 36 | # I2C pin names and wiring 37 | 38 | I2C is generally only available on monochrome displays. Monochrome OLED panels 39 | typically use the SSD1306 chip which is 40 | [officially supported](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). 41 | At the time of writing (Sept 2018) this works only with software SPI. See 42 | [this issue](https://github.com/micropython/micropython/pull/4020). Wiring 43 | details: 44 | 45 | | Pin | Pyboard | Display | 46 | |:---:|:-------:|:-------:| 47 | | 3V3 | 3V3 | Vin | 48 | | Gnd | Gnd | Gnd | 49 | | Y9 | SCL | CLK | 50 | | Y10 | SDA | DATA | 51 | 52 | Typical initialisation on a Pyboard: 53 | ```python 54 | pscl = machine.Pin('Y9', machine.Pin.OPEN_DRAIN) 55 | psda = machine.Pin('Y10', machine.Pin.OPEN_DRAIN) 56 | i2c = machine.I2C(scl=pscl, sda=psda) 57 | ``` 58 | 59 | # Adafruit - use of the onboard SD card 60 | 61 | If the SD card is to be used, the official `sdcard.py` driver should be 62 | employed. This may be found 63 | [here](https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py). 64 | It is necessary to initialise the SPI bus before accessing the SD card. This is 65 | because the display drivers use a high baudrate unsupported by SD cards. Ensure 66 | applications do this before the first SD card access and before subsequent ones 67 | if the display has been refreshed. See 68 | [sdtest.py](https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdtest.py). 69 | 70 | # Notes on OLED displays 71 | 72 | ## Hardware note: SPI clock rate 73 | 74 | For performance reasons the drivers for the Adafruit color displays run the SPI 75 | bus at a high rate (currently 10.5MHz). Leads should be short and direct. An 76 | attempt to use 21MHz failed. The datasheet limit is 20MHz. Whether a 5% 77 | overclock caused this is moot: with very short leads or a PCB this might well 78 | work. Note that the Pyboard hardware SPI supports only 10.5MHz and 21MHz. 79 | 80 | In practice the 41ms update time is visually fast for most purposes except some 81 | games. 82 | 83 | Update: even with a PCB and an ESP32 (which supports exactly 20MHz) it did not 84 | work at that rate. 85 | 86 | ## Power consumption 87 | 88 | The power consumption of OLED displays is roughly proportional to the number 89 | and brightness of illuminated pixels. I tested a 1.27 inch Adafruit display 90 | running the `clock.py` demo. It consumed 19.7mA. Initial current with screen 91 | blank was 3.3mA. 92 | 93 | ## Wearout 94 | 95 | OLED displays suffer gradual loss of luminosity over long periods of 96 | illumination. Wikipedia refers to 15,000 hours for significant loss, which 97 | equates to 1.7 years of 24/7 usage. However it also refers to fabrication 98 | techniques which ameliorate this which implies the likelihood of better 99 | figures. I have not seen figures for the Adafruit displays. 100 | 101 | Options are to blank the display when not required, or to design screens where 102 | the elements are occasionally moved slightly to preserve individual pixels. 103 | 104 | ###### [Main README](./README.md) 105 | -------------------------------------------------------------------------------- /ASYNC.md: -------------------------------------------------------------------------------- 1 | # nanogui: Use in asynchronous code 2 | 3 | ###### [Main README](./README.md) 4 | 5 | ###### [Driver doc](./DRIVERS.md) 6 | 7 | ## Blocking 8 | 9 | The suitability of `nanogui` for use with cooperative schedulers such as 10 | `asyncio` is constrained by the underlying display driver. The GUI supports 11 | displays whose driver is subclassed from `framebuf`. Such drivers hold the 12 | frame buffer on the host, transferring its entire contents to the display 13 | hardware, usually via I2C or SPI. Current drivers block for the time taken by 14 | this. 15 | 16 | In the case of the Pyboard driver for Adafruit 1.5 and 1.27 inch OLED displays, 17 | running on a Pyboard 1.x, blocking is for 41ms. Blocking periods for monochrome 18 | or smaller colour displays will be shorter. On hosts which don't support inline 19 | Arm Thumb assembler or the viper emitter it will be very much longer. 20 | 21 | For large displays such as ePaper the blocking time is on the order of 250ms on 22 | a Pyboard, longer on hardware such as ESP32. Such drivers have a special `asyn` 23 | constructor arg which causes refresh to be performed by a coroutine; this 24 | periodically yields to the scheduler and limits blocking to around 30ms. 25 | 26 | Blocking occurs when the `nanogui.refresh` function is called. In typical 27 | applications which might wait for user input from a switch this blocking is 28 | not apparent and the response appears immediate. It may have consequences in 29 | applications performing fast concurrent input over devices such as UARTs. 30 | 31 | ### Reducing latency 32 | 33 | Some display drivers have an asynchronous `do_refresh()` method which takes a 34 | single optional arg `split=4`. This may be used in place of the synchronous 35 | `refresh()` method. With the default value the method will yield to the 36 | scheduler four times during a refresh, reducing the latency experienced by 37 | other tasks by a factor of four. A `ValueError` will result if `split` is not 38 | an integer divisor of the `height` passed to the constructor. 39 | 40 | Such applications should issue the synchronous 41 | ```python 42 | refresh(ssd, True) 43 | ``` 44 | at the start to initialise the display. This will block for the full refresh 45 | period. 46 | 47 | The coroutine performing screen refresh might use the following for portability 48 | between devices having a `do_refresh` method and those that do not: 49 | ```python 50 | while True: 51 | # Update widgets 52 | if hasattr(ssd, 'do_refresh'): 53 | # Option to reduce asyncio latency 54 | await ssd.do_refresh() 55 | else: 56 | # Normal synchronous call 57 | refresh(ssd) 58 | await asyncio.sleep_ms(250) # Determine update rate 59 | ``` 60 | 61 | ## Demo scripts 62 | 63 | These require MicroPython firmware V1.13 or later. The `asnano` and 64 | `asnano_sync` demos assume a Pyboard. `scale.py` is portable between hosts and 65 | sufficiently large displays. 66 | 67 | * `asnano.py` Runs until the usr button is pressed. In this demo each meter 68 | updates independently and mutually asynchronously to test the response to 69 | repeated display refreshes. 70 | * `asnano_sync.py` Provides a less hectic visual. Display objects update 71 | themselves as data becomes available but screen updates occur asynchronously 72 | at a low frequency. An asynchronous iterator is used to stop the demo when the 73 | pyboard usr button is pressed. 74 | * `scale.py` Illustrates the use of `do_refresh()` where available. 75 | 76 | ###### [Main README](./README.md) 77 | 78 | ###### [Driver doc](./DRIVERS.md) 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Peter Hinch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /drivers/boolpalette.py: -------------------------------------------------------------------------------- 1 | # boolpalette.py Implement BoolPalette class 2 | # This is a 2-value color palette for rendering monochrome glyphs to color 3 | # FrameBuffer instances. Supports destinations with up to 16 bit color. 4 | 5 | # Copyright (c) Peter Hinch 2021 6 | # Released under the MIT license see LICENSE 7 | 8 | import framebuf 9 | 10 | class BoolPalette(framebuf.FrameBuffer): 11 | 12 | def __init__(self, mode): 13 | buf = bytearray(4) # OK for <= 16 bit color 14 | super().__init__(buf, 2, 1, mode) 15 | 16 | def fg(self, color): # Set foreground color 17 | self.pixel(1, 0, color) 18 | 19 | def bg(self, color): 20 | self.pixel(0, 0, color) 21 | -------------------------------------------------------------------------------- /drivers/epaper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/epaper/epd29.py", "github:peterhinch/micropython-nano-gui/drivers/epaper/epd29.py"], 4 | ["drivers/epaper/epaper2in7_fb.py", "github:peterhinch/micropython-nano-gui/drivers/epaper/epaper2in7_fb.py"], 5 | ["drivers/epaper/pico_epaper_42_gs.py", "github:peterhinch/micropython-nano-gui/drivers/epaper/pico_epaper_42_gs.py"], 6 | ["drivers/epaper/pico_epaper_42.py", "github:peterhinch/micropython-nano-gui/drivers/epaper/pico_epaper_42.py"], 7 | ["drivers/epaper/pico_epaper_42_v2.py", "github:peterhinch/micropython-nano-gui/drivers/epaper/pico_epaper_42_v2.py"], 8 | ["drivers/epaper/epd29_ssd1680.py", "github:peterhinch/micropython-nano-gui/drivers/epaper/epd29_ssd1680.py"], 9 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 10 | ], 11 | "version": "0.1" 12 | } 13 | -------------------------------------------------------------------------------- /drivers/gc9a01/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/gc9a01/gc9a01.py", "github:peterhinch/micropython-nano-gui/drivers/gc9a01/gc9a01.py"], 4 | ["drivers/gc9a01/gc9a01_8_bit.py", "github:peterhinch/micropython-nano-gui/drivers/gc9a01/gc9a01_8_bit.py"], 5 | ["drivers/gc9a01/gc9a01_16_bit.py", "github:peterhinch/micropython-nano-gui/drivers/gc9a01/gc9a01_16_bit.py"], 6 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 7 | ], 8 | "version": "0.1" 9 | } 10 | -------------------------------------------------------------------------------- /drivers/ili93xx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/ili93xx/ili9341.py", "github:peterhinch/micropython-nano-gui/drivers/ili93xx/ili9341.py"], 4 | ["drivers/ili93xx/ili9341_8bit.py", "github:peterhinch/micropython-nano-gui/drivers/ili93xx/ili9341_8bit.py"], 5 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 6 | ], 7 | "version": "0.1" 8 | } 9 | -------------------------------------------------------------------------------- /drivers/ili94xx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/ili94xx/ili9486.py", "github:peterhinch/micropython-nano-gui/drivers/ili94xx/ili9486.py"], 4 | ["drivers/ili94xx/ili9488.py", "github:peterhinch/micropython-nano-gui/drivers/ili94xx/ili9488.py"], 5 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 6 | ], 7 | "version": "0.1" 8 | } 9 | -------------------------------------------------------------------------------- /drivers/sh1106/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/sh1106/sh1106.py", "github:peterhinch/micropython-nano-gui/drivers/sh1106/sh1106.py"], 4 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 5 | ], 6 | "version": "0.1" 7 | } 8 | -------------------------------------------------------------------------------- /drivers/sharp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/drivers/sharp/__init__.py -------------------------------------------------------------------------------- /drivers/sharp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/sharp/sharp.py", "github:peterhinch/micropython-nano-gui/drivers/sharp/sharp.py"], 4 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 5 | ], 6 | "version": "0.1" 7 | } 8 | -------------------------------------------------------------------------------- /drivers/sharp/sharp.py: -------------------------------------------------------------------------------- 1 | # sharp.py Device driver for monochrome sharp displays 2 | 3 | # Tested on 4 | # https://www.adafruit.com/product/4694 2.7 inch 400x240 Monochrome 5 | # Should also work on 6 | # https://www.adafruit.com/product/3502 1.3 inch 144x168 7 | # https://www.adafruit.com/product/1393 1.3 inch 96x96 Monochrome 8 | 9 | # Copyright (c) Peter Hinch 2020-2021 10 | # Released under the MIT license see LICENSE 11 | 12 | # Code checked against https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay 13 | # Current draw on 2.7" Adafruit display ~90uA. 14 | # 2.7" schematic https://learn.adafruit.com/assets/94077 15 | # Datasheet 2.7" https://cdn-learn.adafruit.com/assets/assets/000/094/215/original/LS027B7DH01_Rev_Jun_2010.pdf?1597872422 16 | # Datasheet 1.3" http://www.adafruit.com/datasheets/LS013B4DN04-3V_FPC-204284.pdf 17 | import framebuf 18 | import machine 19 | from micropython import const 20 | 21 | _WRITECMD = const(1) # Command bits 22 | _VCOM = const(2) 23 | 24 | 25 | class SHARP(framebuf.FrameBuffer): 26 | @staticmethod 27 | def rgb(r, g, b): 28 | return int((r > 127) or (g > 127) or (b > 127)) 29 | 30 | def __init__(self, spi, pincs, height=240, width=400, vcom=False): 31 | spi.init(baudrate=2_000_000, firstbit=machine.SPI.LSB) # Data sheet: should support 2MHz 32 | self._spi = spi 33 | self._pincs = pincs 34 | self.height = height # Required by Writer class and nanogui 35 | self.width = width 36 | self._buffer = bytearray(self.height * self.width // 8) 37 | self._mvb = memoryview(self._buffer) 38 | super().__init__(self._buffer, self.width, self.height, framebuf.MONO_HMSB) 39 | self._cmd = bytearray(1) # Buffer for command. Holds current VCOM bit 40 | self._cmd[0] = _WRITECMD | _VCOM if vcom else _WRITECMD 41 | self._lno = bytearray(1) # Line no. 42 | self._dummy = bytearray(1) # Dummy (0) 43 | 44 | # .show should be called periodically to avoid frame inversion flag 45 | # (VCOM) retaining the same value for long periods 46 | def show(self): 47 | spi = self._spi 48 | bpl = self.width // 8 # Bytes per line 49 | self._pincs(1) # CS is active high 50 | spi.write(self._cmd) 51 | start = 0 52 | lno = self._lno 53 | lno[0] = 1 # Gate line address (starts at 1) 54 | for _ in range(self.height): 55 | spi.write(lno) 56 | spi.write(self._mvb[start : start + bpl]) 57 | spi.write(self._dummy) 58 | start += bpl 59 | lno[0] += 1 # Gate line address 60 | spi.write(self._dummy) 61 | self._pincs(0) 62 | self._cmd[0] ^= _VCOM # Toggle frame inversion flag 63 | 64 | # Toggle the VCOM bit without changing the display. Power saving method. 65 | def update(self): 66 | self._pincs(1) 67 | self._lno[0] = self._cmd[0] & _VCOM 68 | self._spi.write(self._lno) 69 | self._cmd[0] ^= _VCOM # Toggle frame inversion flag 70 | self._pincs(0) 71 | -------------------------------------------------------------------------------- /drivers/ssd1306/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/ssd1306/ssd1306.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1306/ssd1306.py"], 4 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 5 | ], 6 | "version": "0.1" 7 | } 8 | -------------------------------------------------------------------------------- /drivers/ssd1306/ssd1306.py: -------------------------------------------------------------------------------- 1 | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces 2 | 3 | from micropython import const 4 | import framebuf 5 | from drivers.boolpalette import BoolPalette 6 | 7 | # register definitions 8 | SET_CONTRAST = const(0x81) 9 | SET_ENTIRE_ON = const(0xA4) 10 | SET_NORM_INV = const(0xA6) 11 | SET_DISP = const(0xAE) 12 | SET_MEM_ADDR = const(0x20) 13 | SET_COL_ADDR = const(0x21) 14 | SET_PAGE_ADDR = const(0x22) 15 | SET_DISP_START_LINE = const(0x40) 16 | SET_SEG_REMAP = const(0xA0) 17 | SET_MUX_RATIO = const(0xA8) 18 | SET_COM_OUT_DIR = const(0xC0) 19 | SET_DISP_OFFSET = const(0xD3) 20 | SET_COM_PIN_CFG = const(0xDA) 21 | SET_DISP_CLK_DIV = const(0xD5) 22 | SET_PRECHARGE = const(0xD9) 23 | SET_VCOM_DESEL = const(0xDB) 24 | SET_CHARGE_PUMP = const(0x8D) 25 | 26 | # Subclassing FrameBuffer provides support for graphics primitives 27 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 28 | class SSD1306(framebuf.FrameBuffer): 29 | @staticmethod 30 | def rgb(r, g, b): # Color compatibility 31 | return int((r > 127) or (g > 127) or (b > 127)) 32 | 33 | def __init__(self, width, height, external_vcc): 34 | self.width = width 35 | self.height = height 36 | self.external_vcc = external_vcc 37 | self.pages = self.height // 8 38 | self.buffer = bytearray(self.pages * self.width) 39 | mode = framebuf.MONO_VLSB 40 | self.palette = BoolPalette(mode) # Ensure color compatibility 41 | super().__init__(self.buffer, self.width, self.height, mode) 42 | self.init_display() 43 | 44 | def init_display(self): 45 | for cmd in ( 46 | SET_DISP | 0x00, # off 47 | # address setting 48 | SET_MEM_ADDR, 49 | 0x00, # horizontal 50 | # resolution and layout 51 | SET_DISP_START_LINE | 0x00, 52 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 53 | SET_MUX_RATIO, 54 | self.height - 1, 55 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 56 | SET_DISP_OFFSET, 57 | 0x00, 58 | SET_COM_PIN_CFG, 59 | 0x02 if self.width > 2 * self.height else 0x12, 60 | # timing and driving scheme 61 | SET_DISP_CLK_DIV, 62 | 0x80, 63 | SET_PRECHARGE, 64 | 0x22 if self.external_vcc else 0xF1, 65 | SET_VCOM_DESEL, 66 | 0x30, # 0.83*Vcc 67 | # display 68 | SET_CONTRAST, 69 | 0xFF, # maximum 70 | SET_ENTIRE_ON, # output follows RAM contents 71 | SET_NORM_INV, # not inverted 72 | # charge pump 73 | SET_CHARGE_PUMP, 74 | 0x10 if self.external_vcc else 0x14, 75 | SET_DISP | 0x01, 76 | ): # on 77 | self.write_cmd(cmd) 78 | self.fill(0) 79 | self.show() 80 | 81 | def poweroff(self): 82 | self.write_cmd(SET_DISP | 0x00) 83 | 84 | def poweron(self): 85 | self.write_cmd(SET_DISP | 0x01) 86 | 87 | def contrast(self, contrast): 88 | self.write_cmd(SET_CONTRAST) 89 | self.write_cmd(contrast) 90 | 91 | def invert(self, invert): 92 | self.write_cmd(SET_NORM_INV | (invert & 1)) 93 | 94 | def show(self): 95 | x0 = 0 96 | x1 = self.width - 1 97 | if self.width == 64: 98 | # displays with width of 64 pixels are shifted by 32 99 | x0 += 32 100 | x1 += 32 101 | self.write_cmd(SET_COL_ADDR) 102 | self.write_cmd(x0) 103 | self.write_cmd(x1) 104 | self.write_cmd(SET_PAGE_ADDR) 105 | self.write_cmd(0) 106 | self.write_cmd(self.pages - 1) 107 | self.write_data(self.buffer) 108 | 109 | 110 | class SSD1306_I2C(SSD1306): 111 | def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): 112 | self.i2c = i2c 113 | self.addr = addr 114 | self.temp = bytearray(2) 115 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 116 | super().__init__(width, height, external_vcc) 117 | 118 | def write_cmd(self, cmd): 119 | self.temp[0] = 0x80 # Co=1, D/C#=0 120 | self.temp[1] = cmd 121 | self.i2c.writeto(self.addr, self.temp) 122 | 123 | def write_data(self, buf): 124 | self.write_list[1] = buf 125 | self.i2c.writevto(self.addr, self.write_list) 126 | 127 | 128 | class SSD1306_SPI(SSD1306): 129 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 130 | self.rate = 10 * 1024 * 1024 131 | dc.init(dc.OUT, value=0) 132 | res.init(res.OUT, value=0) 133 | cs.init(cs.OUT, value=1) 134 | self.spi = spi 135 | self.dc = dc 136 | self.res = res 137 | self.cs = cs 138 | import time 139 | 140 | self.res(1) 141 | time.sleep_ms(1) 142 | self.res(0) 143 | time.sleep_ms(10) 144 | self.res(1) 145 | super().__init__(width, height, external_vcc) 146 | 147 | def write_cmd(self, cmd): 148 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 149 | self.cs(1) 150 | self.dc(0) 151 | self.cs(0) 152 | self.spi.write(bytearray([cmd])) 153 | self.cs(1) 154 | 155 | def write_data(self, buf): 156 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 157 | self.cs(1) 158 | self.dc(1) 159 | self.cs(0) 160 | self.spi.write(buf) 161 | self.cs(1) 162 | -------------------------------------------------------------------------------- /drivers/ssd1327/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/ssd1327/ssd1327.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1327/ssd1327.py"], 4 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 5 | ], 6 | "version": "0.1" 7 | } 8 | -------------------------------------------------------------------------------- /drivers/ssd1331/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/drivers/ssd1331/__init__.py -------------------------------------------------------------------------------- /drivers/ssd1331/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/ssd1331/ssd1331.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1331/ssd1331.py"], 4 | ["drivers/ssd1331/ssd1331_16bit.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1331/ssd1331_16bit.py"], 5 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 6 | ], 7 | "version": "0.1" 8 | } 9 | -------------------------------------------------------------------------------- /drivers/ssd1331/ssd1331.py: -------------------------------------------------------------------------------- 1 | # SSD1331.py MicroPython driver for Adafruit 0.96" OLED display 2 | # https://www.adafruit.com/product/684 3 | 4 | # The MIT License (MIT) 5 | 6 | # Copyright (c) Peter Hinch 2018-2020 7 | # Released under the MIT license see LICENSE 8 | 9 | # Show command 10 | # 0x15, 0, 0x5f, 0x75, 0, 0x3f Col 0-95 row 0-63 11 | 12 | # Initialisation command 13 | # 0xae display off (sleep mode) 14 | # 0xa0, 0x32 256 color RGB, horizontal RAM increment 15 | # 0xa1, 0x00 Startline row 0 16 | # 0xa2, 0x00 Vertical offset 0 17 | # 0xa4 Normal display 18 | # 0xa8, 0x3f Set multiplex ratio 19 | # 0xad, 0x8e Ext supply 20 | # 0xb0, 0x0b Disable power save mode 21 | # 0xb1, 0x31 Phase period 22 | # 0xb3, 0xf0 Oscillator frequency 23 | # 0x8a, 0x64, 0x8b, 0x78, 0x8c, 0x64, # Precharge 24 | # 0xbb, 0x3a Precharge voltge 25 | # 0xbe, 0x3e COM deselect level 26 | # 0x87, 0x06 master current attenuation factor 27 | # 0x81, 0x91 contrast for all color "A" segment 28 | # 0x82, 0x50 contrast for all color "B" segment 29 | # 0x83, 0x7d contrast for all color "C" segment 30 | # 0xaf Display on 31 | 32 | import framebuf 33 | import utime 34 | import gc 35 | import sys 36 | from drivers.boolpalette import BoolPalette 37 | 38 | # https://github.com/peterhinch/micropython-nano-gui/issues/2 39 | # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. 40 | # Mode 0, 0 works on ESP and STM 41 | 42 | # Data sheet SPI spec: 150ns min clock period 6.66MHz 43 | 44 | class SSD1331(framebuf.FrameBuffer): 45 | # Convert r, g, b in range 0-255 to an 8 bit colour value 46 | # acceptable to hardware: rrrgggbb 47 | @staticmethod 48 | def rgb(r, g, b): 49 | return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6) 50 | 51 | def __init__(self, spi, pincs, pindc, pinrs, height=64, width=96, init_spi=False): 52 | self._spi = spi 53 | self._pincs = pincs 54 | self._pindc = pindc # 1 = data 0 = cmd 55 | self.height = height # Required by Writer class 56 | self.width = width 57 | self._spi_init = init_spi 58 | mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color. 59 | self.palette = BoolPalette(mode) 60 | gc.collect() 61 | self.buffer = bytearray(self.height * self.width) 62 | super().__init__(self.buffer, self.width, self.height, mode) 63 | pinrs(0) # Pulse the reset line 64 | utime.sleep_ms(1) 65 | pinrs(1) 66 | utime.sleep_ms(1) 67 | self._write(b'\xae\xa0\x32\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\ 68 | b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\ 69 | b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0) 70 | gc.collect() 71 | self.show() 72 | 73 | def _write(self, buf, dc): 74 | self._pincs(1) 75 | self._pindc(dc) 76 | self._pincs(0) 77 | self._spi.write(buf) 78 | self._pincs(1) 79 | 80 | def show(self, _cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate 81 | if self._spi_init: # A callback was passed 82 | self._spi_init(spi) # Bus may be shared 83 | self._write(_cmd, 0) 84 | self._write(self.buffer, 1) 85 | -------------------------------------------------------------------------------- /drivers/ssd1331/ssd1331_16bit.py: -------------------------------------------------------------------------------- 1 | # SSD1331.py MicroPython driver for Adafruit 0.96" OLED display 2 | # https://www.adafruit.com/product/684 3 | 4 | # Copyright (c) Peter Hinch 2019-2020 5 | # Released under the MIT license see LICENSE 6 | 7 | # Show command 8 | # 0x15, 0, 0x5f, 0x75, 0, 0x3f Col 0-95 row 0-63 9 | 10 | # Initialisation command 11 | # 0xae display off (sleep mode) 12 | # 0xa0, 0x72 16 bit RGB, horizontal RAM increment 13 | # 0xa1, 0x00 Startline row 0 14 | # 0xa2, 0x00 Vertical offset 0 15 | # 0xa4 Normal display 16 | # 0xa8, 0x3f Set multiplex ratio 17 | # 0xad, 0x8e Ext supply 18 | # 0xb0, 0x0b Disable power save mode 19 | # 0xb1, 0x31 Phase period 20 | # 0xb3, 0xf0 Oscillator frequency 21 | # 0x8a, 0x64, 0x8b, 0x78, 0x8c, 0x64, # Precharge 22 | # 0xbb, 0x3a Precharge voltge 23 | # 0xbe, 0x3e COM deselect level 24 | # 0x87, 0x06 master current attenuation factor 25 | # 0x81, 0x91 contrast for all color "A" segment 26 | # 0x82, 0x50 contrast for all color "B" segment 27 | # 0x83, 0x7d contrast for all color "C" segment 28 | # 0xaf Display on 29 | 30 | import framebuf 31 | import utime 32 | import gc 33 | from drivers.boolpalette import BoolPalette 34 | 35 | # https://github.com/peterhinch/micropython-nano-gui/issues/2 36 | # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. 37 | # Mode 0, 0 works on ESP and STM 38 | 39 | # Data sheet SPI spec: 150ns min clock period 6.66MHz 40 | class SSD1331(framebuf.FrameBuffer): 41 | # Convert r, g, b in range 0-255 to a 16 bit colour value RGB565 42 | # acceptable to hardware: rrrrrggggggbbbbb 43 | # LS byte of 16 bit result is shifted out 1st 44 | @staticmethod 45 | def rgb(r, g, b): 46 | return ((b & 0xf8) << 5) | ((g & 0x1c) << 11) | (r & 0xf8) | ((g & 0xe0) >> 5) 47 | 48 | def __init__(self, spi, pincs, pindc, pinrs, height=64, width=96, init_spi=False): 49 | self._spi = spi 50 | self._pincs = pincs 51 | self._pindc = pindc # 1 = data 0 = cmd 52 | self.height = height # Required by Writer class 53 | self.width = width 54 | self._spi_init = init_spi 55 | mode = framebuf.RGB565 56 | self.palette = BoolPalette(mode) 57 | gc.collect() 58 | self.buffer = bytearray(self.height * self.width * 2) 59 | super().__init__(self.buffer, self.width, self.height, mode) 60 | pinrs(0) # Pulse the reset line 61 | utime.sleep_ms(1) 62 | pinrs(1) 63 | utime.sleep_ms(1) 64 | if self._spi_init: # A callback was passed 65 | self._spi_init(spi) # Bus may be shared 66 | self._write(b'\xae\xa0\x72\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\ 67 | b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\ 68 | b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0) 69 | gc.collect() 70 | self.show() 71 | 72 | def _write(self, buf, dc): 73 | self._pincs(1) 74 | self._pindc(dc) 75 | self._pincs(0) 76 | self._spi.write(buf) 77 | self._pincs(1) 78 | 79 | def show(self, _cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate 80 | if self._spi_init: # A callback was passed 81 | self._spi_init(spi) # Bus may be shared 82 | self._write(_cmd, 0) 83 | self._write(self.buffer, 1) 84 | -------------------------------------------------------------------------------- /drivers/ssd1331/test_colors.py: -------------------------------------------------------------------------------- 1 | # test_colors.py Test color rendition on SSD1331 (Adafruit 0.96" OLED). 2 | # Author Peter Hinch 3 | # Released under the MIT licence. 4 | 5 | import machine 6 | # Can test either driver 7 | # from ssd1331 import SSD1331 as SSD 8 | from ssd1331_16bit import SSD1331 as SSD 9 | 10 | # Initialise hardware 11 | def setup(): 12 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 13 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 14 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 15 | spi = machine.SPI(1) 16 | ssd = SSD(spi, pcs, pdc, prst) # Create a display instance 17 | return ssd 18 | 19 | ssd = setup() 20 | ssd.fill(0) 21 | # Operate in landscape mode 22 | x = 0 23 | for y in range(96): 24 | ssd.line(y, x, y, x+20, ssd.rgb(round(255*y/96), 0, 0)) 25 | x += 20 26 | for y in range(96): 27 | ssd.line(y, x, y, x+20, ssd.rgb(0, round(255*y/96), 0)) 28 | x += 20 29 | for y in range(96): 30 | ssd.line(y, x, y, x+20, ssd.rgb(0, 0, round(255*y/96))) 31 | ssd.show() 32 | -------------------------------------------------------------------------------- /drivers/ssd1351/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/drivers/ssd1351/__init__.py -------------------------------------------------------------------------------- /drivers/ssd1351/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/ssd1351/ssd1351.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1351/ssd1351.py"], 4 | ["drivers/ssd1351/ssd1351_4bit.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1351/ssd1351_4bit.py"], 5 | ["drivers/ssd1351/ssd1351_16bit.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1351/ssd1351_16bit.py"], 6 | ["drivers/ssd1351/ssd1351_generic.py", "github:peterhinch/micropython-nano-gui/drivers/ssd1351/ssd1351_generic.py"], 7 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 8 | ], 9 | "version": "0.1" 10 | } 11 | -------------------------------------------------------------------------------- /drivers/ssd1351/ssd1351_16bit.py: -------------------------------------------------------------------------------- 1 | # SSD1351_16bit.py MicroPython driver for Adafruit color OLED displays. 2 | 3 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 4 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 5 | # For wiring details see drivers/ADAFRUIT.md in this repo. 6 | 7 | # This driver is based on the Adafruit C++ library for Arduino 8 | # https://github.com/adafruit/Adafruit-SSD1351-library.git 9 | 10 | # Copyright (c) Peter Hinch 2019-2020 11 | # Released under the MIT license see LICENSE 12 | 13 | import framebuf 14 | import utime 15 | import gc 16 | import micropython 17 | from drivers.boolpalette import BoolPalette 18 | 19 | # https://github.com/peterhinch/micropython-nano-gui/issues/2 20 | # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. 21 | # Now using 0,0 on STM and ESP32 22 | 23 | # Initialisation commands in cmd_init: 24 | # 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode 25 | # 0xae, # display off (sleep mode) 26 | # 0xb3, 0xf1, # clock div 27 | # 0xca, 0x7f, # mux ratio 28 | # 0xa0, 0x74, # setremap 0x74 29 | # 0x15, 0, 0x7f, # setcolumn 30 | # 0x75, 0, 0x7f, # setrow 31 | # 0xa1, 0, # set display start line 32 | # 0xa2, 0, # displayoffset 33 | # 0xb5, 0, # setgpio 34 | # 0xab, 1, # functionselect: serial interface, internal Vdd regulator 35 | # 0xb1, 0x32, # Precharge 36 | # 0xbe, 0x05, # vcommh 37 | # 0xa6, # normaldisplay 38 | # 0xc1, 0xc8, 0x80, 0xc8, # contrast abc 39 | # 0xc7, 0x0f, # Master contrast 40 | # 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit) 41 | # 0xb6, 1, # Precharge 2 42 | # 0xaf, # Display on 43 | 44 | 45 | class SSD1351(framebuf.FrameBuffer): 46 | # Convert r, g, b in range 0-255 to a 16 bit colour value RGB565 47 | # acceptable to hardware: rrrrrggggggbbbbb 48 | @staticmethod 49 | def rgb(r, g, b): 50 | return ((r & 0xf8) << 5) | ((g & 0x1c) << 11) | (b & 0xf8) | ((g & 0xe0) >> 5) 51 | 52 | def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=False): 53 | if height not in (96, 128): 54 | raise ValueError('Unsupported height {}'.format(height)) 55 | self.spi = spi 56 | self.spi_init = init_spi 57 | self.pincs = pincs 58 | self.pindc = pindc # 1 = data 0 = cmd 59 | self.height = height # Required by Writer class 60 | self.width = width 61 | mode = framebuf.RGB565 62 | self.palette = BoolPalette(mode) 63 | gc.collect() 64 | self.buffer = bytearray(self.height * self.width * 2) 65 | super().__init__(self.buffer, self.width, self.height, mode) 66 | self.mvb = memoryview(self.buffer) 67 | pinrs(0) # Pulse the reset line 68 | utime.sleep_ms(1) 69 | pinrs(1) 70 | utime.sleep_ms(1) 71 | if self.spi_init: # A callback was passed 72 | self.spi_init(spi) # Bus may be shared 73 | # See above comment to explain this allocation-saving gibberish. 74 | self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\ 75 | b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\ 76 | b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\ 77 | b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0) 78 | self.show() 79 | gc.collect() 80 | 81 | def _write(self, mv, dc): 82 | self.pincs(1) 83 | self.pindc(dc) 84 | self.pincs(0) 85 | self.spi.write(bytes(mv)) 86 | self.pincs(1) 87 | 88 | # Write lines from the framebuf out of order to match the mapping of the 89 | # SSD1351 RAM to the OLED device. 90 | def show(self): 91 | mvb = self.mvb 92 | bw = self.width * 2 # Width in bytes 93 | if self.spi_init: # A callback was passed 94 | self.spi_init(self.spi) # Bus may be shared 95 | self._write(b'\x5c', 0) # Enable data write 96 | if self.height == 128: 97 | for l in range(128): 98 | l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126 .. 96 99 | start = l0 * self.width * 2 100 | self._write(mvb[start : start + bw], 1) # Send a line 101 | else: 102 | for l in range(128): 103 | if l < 64: 104 | start = (63 -l) * self.width * 2 # 63 62 .. 1 0 105 | elif l < 96: 106 | start = 0 107 | else: 108 | start = (191 - l) * self.width * 2 # 127 126 .. 95 109 | self._write(mvb[start : start + bw], 1) # Send a line 110 | -------------------------------------------------------------------------------- /drivers/ssd1351/test128_row.py: -------------------------------------------------------------------------------- 1 | # test128_row.py Test for device driver on 96 row display 2 | import machine 3 | from ssd1351 import SSD1351 as SSD 4 | 5 | # Initialise hardware 6 | def setup(): 7 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 8 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 9 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 10 | spi = machine.SPI(1) 11 | ssd = SSD(spi, pcs, pdc, prst) # Create a display instance 12 | return ssd 13 | 14 | ssd = setup() 15 | ssd.fill(0) 16 | ssd.line(0, 0, 127, 127, ssd.rgb(0, 255, 0)) 17 | ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) 18 | ssd.show() 19 | -------------------------------------------------------------------------------- /drivers/ssd1351/test96_row.py: -------------------------------------------------------------------------------- 1 | # test96.py Test for device driver on 96 row display 2 | import machine 3 | from ssd1351 import SSD1351 as SSD 4 | 5 | # Initialise hardware 6 | def setup(): 7 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 8 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 9 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 10 | spi = machine.SPI(1) 11 | ssd = SSD(spi, pcs, pdc, prst, height=96) # Create a display instance 12 | return ssd 13 | 14 | ssd = setup() 15 | ssd.fill(0) 16 | ssd.line(0, 0, 127, 95, ssd.rgb(0, 255, 0)) 17 | ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) 18 | ssd.show() 19 | -------------------------------------------------------------------------------- /drivers/ssd1351/test_colors_96.py: -------------------------------------------------------------------------------- 1 | # test_colors_96.py 2 | # Check color mapping. Runs on 96 row display (change height for 128 row) 3 | 4 | import machine 5 | from ssd1351_16bit import SSD1351 as SSD 6 | 7 | # Initialise hardware 8 | def setup(): 9 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 10 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 11 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 12 | spi = machine.SPI(1) 13 | ssd = SSD(spi, pcs, pdc, prst, height=96) # Create a display instance 14 | return ssd 15 | 16 | ssd = setup() 17 | ssd.fill(0) 18 | x = 0 19 | for y in range(128): 20 | ssd.line(y, x, y, x+20, ssd.rgb(round(255*y/128), 0, 0)) 21 | x += 20 22 | for y in range(128): 23 | ssd.line(y, x, y, x+20, ssd.rgb(0, round(255*y/128), 0)) 24 | x += 20 25 | for y in range(128): 26 | ssd.line(y, x, y, x+20, ssd.rgb(0, 0, round(255*y/128))) 27 | ssd.show() 28 | -------------------------------------------------------------------------------- /drivers/st7567s/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/st7567s/st7567s.py", "github:peterhinch/micropython-nano-gui/drivers/st7567s/st7567s.pyy"], 4 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 5 | ], 6 | "version": "0.1" 7 | } 8 | -------------------------------------------------------------------------------- /drivers/st7567s/st7567s.py: -------------------------------------------------------------------------------- 1 | # st7567s.py 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | 5 | # Driver contributed by Enrico Rossini @EnricoRoss98 6 | # https://github.com/peterhinch/micropython-nano-gui/pull/57 7 | 8 | from micropython import const 9 | from gui.drivers.boolpalette import BoolPalette 10 | from time import sleep_ms 11 | import framebuf 12 | 13 | # LCD Commands definition 14 | _CMD_DISPLAY_ON = const(0xAF) 15 | _CMD_DISPLAY_OFF = const(0xAE) 16 | _CMD_SET_START_LINE = const(0x40) 17 | _CMD_SET_PAGE = const(0xB0) 18 | _CMD_COLUMN_UPPER = const(0x10) 19 | _CMD_COLUMN_LOWER = const(0x00) 20 | _CMD_SET_ADC_NORMAL = const(0xA0) 21 | _CMD_SET_ADC_REVERSE = const(0xA1) 22 | _CMD_SET_COL_NORMAL = const(0xC0) 23 | _CMD_SET_COL_REVERSE = const(0xC8) 24 | _CMD_SET_DISPLAY_NORMAL = const(0xA6) 25 | _CMD_SET_DISPLAY_REVERSE = const(0xA7) 26 | _CMD_SET_ALLPX_ON = const(0xA5) 27 | _CMD_SET_ALLPX_NORMAL = const(0xA4) 28 | _CMD_SET_BIAS_9 = const(0xA2) 29 | _CMD_SET_BIAS_7 = const(0xA3) 30 | _CMD_DISPLAY_RESET = const(0xE2) 31 | _CMD_NOP = const(0xE3) 32 | _CMD_TEST = const(0xF0) # Exit this mode with _CMD_NOP 33 | _CMD_SET_POWER = const(0x28) 34 | _CMD_SET_RESISTOR_RATIO = const(0x20) 35 | _CMD_SET_VOLUME = const(0x81) 36 | 37 | # Display parameters 38 | _DISPLAY_W = const(128) 39 | _DISPLAY_H = const(64) 40 | _DISPLAY_CONTRAST = const(0x1B) 41 | _DISPLAY_RESISTOR_RATIO = const(5) 42 | _DISPLAY_POWER_MODE = 7 43 | 44 | 45 | class ST7567(framebuf.FrameBuffer): 46 | @staticmethod 47 | def rgb(r, g, b): 48 | return min((r + g + b) >> 7, 3) # Greyscale in range 0 <= gs <= 3 49 | 50 | def __init__(self, width, height, i2c, addr=0x3F, external_vcc=False): 51 | self.i2c = i2c 52 | self.addr = addr 53 | self.temp = bytearray(2) 54 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 55 | self.width = width 56 | self.height = height 57 | self.external_vcc = external_vcc 58 | self.pages = self.height // 8 59 | self.buffer = bytearray(self.pages * self.width) 60 | mode = framebuf.MONO_VLSB 61 | self.palette = BoolPalette(mode) # Ensure color compatibility 62 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 63 | self.display_init() 64 | 65 | def display_init(self): 66 | self.write_cmd(_CMD_DISPLAY_RESET) 67 | sleep_ms(1) 68 | for cmd in ( 69 | _CMD_DISPLAY_OFF, # Display off 70 | _CMD_SET_BIAS_9, # Display drive voltage 1/9 bias 71 | _CMD_SET_ADC_NORMAL, # Normal SEG 72 | _CMD_SET_COL_REVERSE, # Commmon mode reverse direction 73 | _CMD_SET_RESISTOR_RATIO + _DISPLAY_RESISTOR_RATIO, # V5 R ratio 74 | _CMD_SET_VOLUME, # Contrast 75 | _DISPLAY_CONTRAST, # Contrast value 76 | _CMD_SET_POWER + _DISPLAY_POWER_MODE): 77 | self.write_cmd(cmd) 78 | 79 | self.show() 80 | self.write_cmd(_CMD_DISPLAY_ON) 81 | 82 | def set_contrast(self, value): 83 | if (0x1 <= value <= 0x3f): 84 | for cmd in (_CMD_SET_VOLUME, value): 85 | self.write_cmd(cmd) 86 | 87 | def show(self): 88 | for i in range(8): 89 | for cmd in ( 90 | _CMD_SET_START_LINE, 91 | _CMD_SET_PAGE + i, 92 | _CMD_COLUMN_UPPER, 93 | _CMD_COLUMN_LOWER): 94 | self.write_cmd(cmd) 95 | self.write_data(self.buffer[i * 128:(i + 1) * 128]) 96 | 97 | def write_cmd(self, cmd): 98 | self.temp[0] = 0x80 # Co=1, D/C#=0 99 | self.temp[1] = cmd 100 | self.i2c.writeto(self.addr, self.temp) 101 | 102 | def write_data(self, buf): 103 | self.write_list[1] = buf 104 | self.i2c.writevto(self.addr, self.write_list) 105 | -------------------------------------------------------------------------------- /drivers/st7735r/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/st7735r/st7735r.py", "github:peterhinch/micropython-nano-gui/drivers/st7735r/st7735r.py"], 4 | ["drivers/st7735r/st7735r_4bit.py", "github:peterhinch/micropython-nano-gui/drivers/st7735r/st7735r_4bit.py"], 5 | ["drivers/st7735r/st7735r144.py", "github:peterhinch/micropython-nano-gui/drivers/st7735r/st7735r144.py"], 6 | ["drivers/st7735r/st7735r144_4bit.py", "github:peterhinch/micropython-nano-gui/drivers/st7735r/st7735r144_4bit.py"], 7 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 8 | ], 9 | "version": "0.1" 10 | } 11 | -------------------------------------------------------------------------------- /drivers/st7789/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["drivers/st7789/st7789_4bit.py", "github:peterhinch/micropython-nano-gui/drivers/st7789/st7789_4bit.py"], 4 | ["drivers/st7789/st7789_8bit.py", "github:peterhinch/micropython-nano-gui/drivers/st7789/st7789_8bit.py"], 5 | ["drivers/boolpalette.py", "github:peterhinch/micropython-nano-gui/drivers/boolpalette.py"] 6 | ], 7 | "version": "0.1" 8 | } 9 | -------------------------------------------------------------------------------- /extras/DATE.md: -------------------------------------------------------------------------------- 1 | # Simple Date classes 2 | 3 | The official [datetime module](https://github.com/micropython/micropython-lib/tree/master/python-stdlib/datetime) 4 | is fully featured but substantial. This `Date` class has no concept of time, 5 | but is very compact. Dates are stored as a small int. Contrary to normal MP 6 | practice, properties are used. This allows basic arithmetic syntax while 7 | ensuring automatic rollover. The speed penalty of properties is unlikely to be 8 | a factor in date operations. 9 | 10 | The `Date` class provides basic arithmetic and comparison methods. The 11 | `DateCal` subclass adds pretty printing and methods to assist in creating 12 | calendars. 13 | 14 | # Date class 15 | 16 | The `Date` class embodies a single date value which may be modified, copied 17 | and compared with other `Date` instances. 18 | 19 | ## Constructor 20 | 21 | This takes a single optional arg: 22 | * `lt=None` By default the date is initialised from system time. To set the 23 | date from another time source, a valid 24 | [localtime/gmtime](http://docs.micropython.org/en/latest/library/time.html#time.localtime) 25 | tuple may be passed. 26 | 27 | ## Method 28 | 29 | * `now` Arg `lt=None`. Sets the instance to the current date, from system time 30 | or `lt` as described above. 31 | 32 | ## Writeable properties 33 | 34 | * `year` e.g. 2023. 35 | * `month` 1 == January. May be set to any number, years will roll over if 36 | necessary. e.g. `d.month += 15` or `d.month -= 1`. 37 | * `mday` Adjust day in current month. Allowed range `1..month_length`. 38 | * `day` Days since epoch. Note that the epoch varies with platform - the value 39 | may be treated as an opaque small integer. Use to adjust a date with rollover 40 | (`d.day += 7`) or to assign one date to another (`date2.day = date1.day`). May 41 | also be used to represnt a date as a small int for saving to a file. 42 | 43 | ## Read-only property 44 | 45 | * `wday` Day of week. 0==Monday 6==Sunday. 46 | 47 | ## Date comparisons 48 | 49 | Python "magic methods" enable date comparisons using standard operators `<`, 50 | `<=`, `>`, `>=`, `==`, `!=`. 51 | 52 | # DateCal class 53 | 54 | This adds pretty formatting and functionality to return additional information 55 | about the current date. The added methods and properties do not change the 56 | date value. Primarily intended for calendars. 57 | 58 | ## Constructor 59 | 60 | This takes a single optional arg: 61 | * `lt=None` See `Date` constructor. 62 | 63 | ## Methods 64 | 65 | * `time_offset` arg `hr=6`. This returns 0 or 1, being the offset in hours of 66 | UK local time to UTC. By default the change occurs when the date changes at 67 | 00:00 UTC on the last Sunday in March and October. If an hour value is passed, 68 | the change will occur at the correct 01:00 UTC. This method will need to be 69 | adapted for other geographic locations. 70 | * `wday_n` arg `mday=1`. Return the weekday for a given day of the month. 71 | * `mday_list` arg `wday`. Given a weekday, for the current month return an 72 | ordered list of month days matching that weekday. 73 | 74 | ## Read-only properties 75 | 76 | * `month_length` Length of month in days. 77 | * `day_str` Day of week as a string, e.g. "Wednesday". 78 | * `month_str` Month as a string, e.g. "August". 79 | 80 | ## Class variables 81 | 82 | * `days` A 7-tuple `("Monday", "Tuesday"...)` 83 | * `months` A 12-tuple `("January", "February",...)` 84 | 85 | # Example usage 86 | 87 | ```python 88 | from date import Date 89 | d = Date() 90 | d.month = 1 # Set to January 91 | d.month -= 2 # Date changes to same mday in November previous year. 92 | d.mday = 25 # Set absolute day of month 93 | d.day += 7 # Advance date by one week. Month/year rollover is handled. 94 | today = Date() 95 | if d == today: # Date comparisons 96 | # do something 97 | new_date = Date() 98 | new_date.day = d.day # Assign d to new_date: now new_date == d. 99 | print(d) # Basic numeric print. 100 | ``` 101 | The DateCal class: 102 | ```python 103 | from date import DateCal 104 | d = DateCal() 105 | # Correct a UK clock for DST 106 | d.now() 107 | hour = (hour_utc + d.time_offset(hour_utc)) % 24 108 | print(d) # Pretty print 109 | x = d.wday_n(1) # Get day of week of 1st day of month 110 | sundays = d.mday_list(6) # List Sundays for the month. 111 | wday_last = d.wday_n(d.month_length) # Weekday of last day of month 112 | ``` 113 | -------------------------------------------------------------------------------- /extras/date.py: -------------------------------------------------------------------------------- 1 | # date.py Minimal Date class for micropython 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2023 Peter Hinch 5 | 6 | from time import mktime, localtime 7 | 8 | _SECS_PER_DAY = const(86400) 9 | def leap(year): 10 | return bool((not year % 4) ^ (not year % 100)) 11 | 12 | class Date: 13 | 14 | def __init__(self, lt=None): 15 | self.callback = lambda : None # No callback until set 16 | self.now(lt) 17 | 18 | def now(self, lt=None): 19 | self._lt = list(localtime()) if lt is None else list(lt) 20 | self._update() 21 | 22 | def _update(self, ltmod=True): # If ltmod is False ._cur has been changed 23 | if ltmod: # Otherwise ._lt has been modified 24 | self._lt[3] = 6 25 | self._cur = mktime(self._lt) // _SECS_PER_DAY 26 | self._lt = list(localtime(self._cur * _SECS_PER_DAY)) 27 | self.callback() 28 | 29 | def _mlen(self, d=bytearray((31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31))): 30 | days = d[self._lt[1] - 1] 31 | return days if days else (29 if leap(self._lt[0]) else 28) 32 | 33 | @property 34 | def year(self): 35 | return self._lt[0] 36 | 37 | @year.setter 38 | def year(self, v): 39 | if self.mday == 29 and self.month == 2 and not leap(v): 40 | self.mday = 28 # Ensure it doesn't skip a month 41 | self._lt[0] = v 42 | self._update() 43 | 44 | @property 45 | def month(self): 46 | return self._lt[1] 47 | 48 | # Can write d.month = 4 or d.month += 15 49 | @month.setter 50 | def month(self, v): 51 | y, m = divmod(v - 1, 12) 52 | self._lt[0] += y 53 | self._lt[1] = m + 1 54 | self._lt[2] = min(self._lt[2], self._mlen()) 55 | self._update() 56 | 57 | @property 58 | def mday(self): 59 | return self._lt[2] 60 | 61 | @mday.setter 62 | def mday(self, v): 63 | if not 0 < v <= self._mlen(): 64 | raise ValueError(f"mday {v} is out of range") 65 | self._lt[2] = v 66 | self._update() 67 | 68 | @property 69 | def day(self): # Days since epoch. 70 | return self._cur 71 | 72 | @day.setter 73 | def day(self, v): # Usage: d.day += 7 or date_1.day = d.day. 74 | self._cur = v 75 | self._update(False) # Flag _cur change 76 | 77 | # Read-only properties 78 | 79 | @property 80 | def wday(self): 81 | return self._lt[6] 82 | 83 | # Date comparisons 84 | 85 | def __lt__(self, other): 86 | return self.day < other.day 87 | 88 | def __le__(self, other): 89 | return self.day <= other.day 90 | 91 | def __eq__(self, other): 92 | return self.day == other.day 93 | 94 | def __ne__(self, other): 95 | return self.day != other.day 96 | 97 | def __gt__(self, other): 98 | return self.day > other.day 99 | 100 | def __ge__(self, other): 101 | return self.day >= other.day 102 | 103 | def __str__(self): 104 | return f"{self.year}/{self.month}/{self.mday}" 105 | 106 | 107 | class DateCal(Date): 108 | days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") 109 | months = ( 110 | "January", 111 | "February", 112 | "March", 113 | "April", 114 | "May", 115 | "June", 116 | "July", 117 | "August", 118 | "September", 119 | "October", 120 | "November", 121 | "December", 122 | ) 123 | 124 | def __init__(self, lt=None): 125 | super().__init__(lt) 126 | 127 | @property 128 | def month_length(self): 129 | return self._mlen() 130 | 131 | @property 132 | def day_str(self): 133 | return self.days[self.wday] 134 | 135 | @property 136 | def month_str(self): 137 | return self.months[self.month - 1] 138 | 139 | def wday_n(self, mday=1): 140 | return (self._lt[6] - self._lt[2] + mday) % 7 141 | 142 | def mday_list(self, wday): 143 | ml = self._mlen() # 1 + ((wday - wday1) % 7) 144 | d0 = 1 + ((wday - (self._lt[6] - self._lt[2] + 1)) % 7) 145 | return [d for d in range(d0, ml + 1, 7)] 146 | 147 | # Optional: return UK DST offset in hours. Can pass hr to ensure that time change occurs 148 | # at 1am UTC otherwise it occurs on date change (0:0 UTC) 149 | # offs is offset by month 150 | def time_offset(self, hr=6, offs=bytearray((0, 0, 3, 1, 1, 1, 1, 1, 1, 10, 0, 0))): 151 | ml = self._mlen() 152 | wdayld = self.wday_n(ml) # Weekday of last day of month 153 | mday_sun = self.mday_list(6)[-1] # Month day of last Sunday 154 | m = offs[self._lt[1] - 1] 155 | if m < 3: 156 | return m # Deduce time offset from month alone 157 | return int( 158 | ((self._lt[2] < mday_sun) or (self._lt[2] == mday_sun and hr <= 1)) ^ (m == 3) 159 | ) # Months where offset changes 160 | 161 | def __str__(self): 162 | return f"{self.day_str} {self.mday} {self.month_str} {self.year}" 163 | -------------------------------------------------------------------------------- /extras/demos/calendar.py: -------------------------------------------------------------------------------- 1 | 2 | from color_setup import ssd 3 | from time import sleep 4 | from gui.core.writer import CWriter 5 | from gui.core.nanogui import refresh 6 | import gui.fonts.font10 as font 7 | from gui.core.colors import * 8 | from extras.widgets.calendar import Calendar 9 | from gui.widgets.label import Label 10 | 11 | epaper = hasattr(ssd, "wait_until_ready") 12 | 13 | 14 | def test(): 15 | wri = CWriter(ssd, font, verbose=False) 16 | wri.set_clip(True, True, False) # Clip to screen, no wrap 17 | refresh(ssd, True) # Clear screen and initialise GUI 18 | lbl = Label(wri, 200, 5, 300, bdcolor=RED) 19 | # Invert today. On ePper also invert current date. 20 | cal = Calendar(wri, 10, 10, 35, GREEN, BLACK, RED, CYAN, BLUE, True, epaper) 21 | lbl.value("Show today's date.") 22 | refresh(ssd) # With ePaper should issue wait_until_ready() 23 | sleep(5) # but we're waiting 5 seconds anyway, which is long enough 24 | date = cal.date 25 | lbl.value("Adding one month") 26 | date.month += 1 27 | refresh(ssd) 28 | sleep(5) 29 | lbl.value("Adding one day") 30 | date.day += 1 31 | refresh(ssd) 32 | sleep(5) 33 | date.now() # Today 34 | for n in range(13): 35 | lbl.value(f"Go to {n + 1} weeks of 13 after today") 36 | date.day += 7 37 | refresh(ssd) 38 | sleep(5) 39 | lbl.value("Back to today") 40 | date.now() # Back to today 41 | refresh(ssd) 42 | sleep(5) 43 | 44 | try: 45 | test() 46 | finally: 47 | if epaper: 48 | ssd.sleep() 49 | -------------------------------------------------------------------------------- /extras/demos/clock_test.py: -------------------------------------------------------------------------------- 1 | # eclock_test.py Unusual clock display for nanogui 2 | # see micropython-epaper/epd-clock 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2023 Peter Hinch 6 | 7 | """ 8 | # color_setup.py: 9 | import gc 10 | from drivers.epaper.pico_epaper_42 import EPD as SSD 11 | gc.collect() # Precaution before instantiating framebuf 12 | ssd = SSD() #asyn=True) # Create a display instance 13 | """ 14 | 15 | from color_setup import ssd 16 | import time 17 | from machine import lightsleep, RTC 18 | from gui.core.writer import CWriter 19 | from gui.core.nanogui import refresh 20 | import gui.fonts.font10 as font 21 | from gui.core.colors import * 22 | from extras.widgets.clock import Clock 23 | 24 | epaper = hasattr(ssd, "wait_until_ready") 25 | if epaper and not hasattr(ssd, "set_partial"): 26 | raise OSError("ePaper display does not support partial update.") 27 | 28 | def test(): 29 | #rtc = RTC() 30 | #rtc.datetime((2023, 3, 18, 5, 10, 0, 0, 0)) 31 | wri = CWriter(ssd, font, GREEN, BLACK, verbose=False) 32 | wri.set_clip(True, True, False) # Clip to screen, no wrap 33 | if epaper: 34 | ssd.set_full() 35 | refresh(ssd, True) 36 | if epaper: 37 | ssd.wait_until_ready() 38 | ec = Clock(wri, 10, 10, 200, label=120, pointers=(CYAN, CYAN, None)) 39 | ec.value(t := time.localtime()) # Initial drawing 40 | refresh(ssd) 41 | if epaper: 42 | ssd.wait_until_ready() 43 | ssd.set_partial() 44 | mins = t[4] 45 | 46 | while True: 47 | t = time.localtime() 48 | if t[4] != mins: # Minute has changed 49 | mins = t[4] 50 | if epaper: 51 | if mins == 0: # Full refresh on the hour 52 | ssd.set_full() 53 | else: 54 | ssd.set_partial() 55 | ec.value(t) 56 | refresh(ssd) 57 | if epaper: 58 | ssd.wait_until_ready() 59 | #lightsleep(10_000) 60 | time.sleep(10) 61 | 62 | try: 63 | test() 64 | finally: 65 | if epaper: 66 | ssd.sleep() 67 | -------------------------------------------------------------------------------- /extras/demos/eclock_async.py: -------------------------------------------------------------------------------- 1 | # eclock_async.py Unusual clock display for nanogui 2 | # see micropython-epaper/epd-clock 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2023 Peter Hinch 6 | 7 | """ 8 | # color_setup.py: 9 | import gc 10 | from drivers.epaper.pico_epaper_42 import EPD as SSD 11 | gc.collect() # Precaution before instantiating framebuf 12 | ssd = SSD() #asyn=True) # Create a display instance. See link for meaning of asyn 13 | # https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md#6-epd-asynchronous-support 14 | """ 15 | 16 | from color_setup import ssd 17 | import asyncio 18 | import time 19 | from gui.core.writer import Writer 20 | from gui.core.nanogui import refresh 21 | import gui.fonts.font10 as font 22 | from gui.core.colors import * 23 | from extras.widgets.eclock import EClock 24 | 25 | epaper = hasattr(ssd, "wait_until_ready") 26 | if epaper and not hasattr(ssd, "set_partial"): 27 | raise OSError("ePaper display does not support partial update.") 28 | 29 | 30 | async def test(): 31 | wri = Writer(ssd, font, verbose=False) 32 | wri.set_clip(True, True, False) # Clip to screen, no wrap 33 | refresh(ssd, True) 34 | if epaper: 35 | await ssd.complete.wait() 36 | ec = EClock(wri, 10, 10, 200, fgcolor=WHITE, bgcolor=BLACK) 37 | ec.value(t := time.localtime()) # Initial drawing 38 | refresh(ssd) 39 | if epaper: 40 | await ssd.complete.wait() 41 | mins = t[4] 42 | 43 | while True: 44 | t = time.localtime() 45 | if t[4] != mins: # Minute has changed 46 | mins = t[4] 47 | if epaper: 48 | if mins == 30: 49 | ssd.set_full() 50 | else: 51 | ssd.set_partial() 52 | ec.value(t) 53 | refresh(ssd) 54 | if epaper: 55 | await ssd.complete.wait() 56 | await asyncio.sleep(10) 57 | 58 | 59 | try: 60 | asyncio.run(test()) 61 | finally: 62 | _ = asyncio.new_event_loop() 63 | if epaper: 64 | ssd.sleep() 65 | -------------------------------------------------------------------------------- /extras/demos/eclock_test.py: -------------------------------------------------------------------------------- 1 | # eclock_test.py Unusual clock display for nanogui 2 | # see micropython-epaper/epd-clock 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2023 Peter Hinch 6 | 7 | """ 8 | # color_setup.py: 9 | import gc 10 | from drivers.epaper.pico_epaper_42 import EPD as SSD 11 | gc.collect() # Precaution before instantiating framebuf 12 | ssd = SSD() #asyn=True) # Create a display instance 13 | """ 14 | 15 | from color_setup import ssd 16 | import time 17 | from machine import lightsleep, RTC 18 | from gui.core.writer import CWriter 19 | from gui.core.nanogui import refresh 20 | import gui.fonts.font10 as font 21 | from gui.core.colors import * 22 | from extras.widgets.eclock import EClock 23 | 24 | epaper = hasattr(ssd, "wait_until_ready") 25 | if epaper and not hasattr(ssd, "set_partial"): 26 | raise OSError("ePaper display does not support partial update.") 27 | 28 | def test(): 29 | rtc = RTC() 30 | #rtc.datetime((2023, 3, 18, 5, 10, 0, 0, 0)) 31 | wri = CWriter(ssd, font, verbose=False) 32 | wri.set_clip(True, True, False) # Clip to screen, no wrap 33 | refresh(ssd, True) 34 | if epaper: 35 | ssd.wait_until_ready() 36 | ec = EClock(wri, 10, 10, 200, fgcolor=WHITE, bgcolor=BLACK) 37 | ec.value(t := time.localtime()) # Initial drawing 38 | refresh(ssd) 39 | if epaper: 40 | ssd.wait_until_ready() 41 | mins = t[4] 42 | 43 | while True: 44 | t = time.localtime() 45 | if t[4] != mins: # Minute has changed 46 | mins = t[4] 47 | if epaper: 48 | if mins == 30: 49 | ssd.set_full() 50 | else: 51 | ssd.set_partial() 52 | ec.value(t) 53 | refresh(ssd) 54 | if epaper: 55 | ssd.wait_until_ready() 56 | #lightsleep(10_000) 57 | time.sleep(10) 58 | 59 | try: 60 | test() 61 | finally: 62 | if epaper: 63 | ssd.sleep() 64 | -------------------------------------------------------------------------------- /extras/parse2d.py: -------------------------------------------------------------------------------- 1 | # parse2d.py Parse args for item access dunder methods for a 2D array. 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2023 Peter Hinch 5 | 6 | 7 | # Called from __getitem__ or __setitem__ args is a 1-tuple. The single item may be an int or a 8 | # slice for 1D access. Or it may be a 2-tuple for 2D access. Items in the 2-tuple may be ints 9 | # or slices in any combination. 10 | # As a generator it returns offsets into the underlying 1D array or list. 11 | def do_args(args, nrows, ncols): 12 | # Given a slice and a maximum address return start and stop addresses (or None on error) 13 | # Step value must be 1, hence does not support start > stop (used with step < 0) 14 | def do_slice(sli, nbytes): 15 | step = sli.step if sli.step is not None else 1 16 | start = sli.start if sli.start is not None else 0 17 | stop = sli.stop if sli.stop is not None else nbytes 18 | start = min(start if start >= 0 else max(nbytes + start, 0), nbytes) 19 | stop = min(stop if stop >= 0 else max(nbytes + stop, 0), nbytes) 20 | ok = (start < stop and step > 0) or (start > stop and step < 0) 21 | return (start, stop, step) if ok else None # Caller should check 22 | 23 | def ivalid(n, nmax): # Validate an integer arg, handle -ve args 24 | n = n if n >= 0 else nmax + n 25 | if n < 0 or n > nmax - 1: 26 | raise IndexError("Index out of range") 27 | return n 28 | 29 | def fail(n): 30 | raise IndexError("Invalid index", n) 31 | 32 | ncells = nrows * ncols 33 | n = args[0] 34 | if isinstance(n, int): # Index into 1D array 35 | yield ivalid(n, ncells) 36 | elif isinstance(n, slice): # Slice of 1D array 37 | cells = do_slice(n, ncells) 38 | if cells is not None: 39 | for cell in range(*cells): 40 | yield cell 41 | elif isinstance(n, tuple) or isinstance(n, list): # list allows for old [[]] syntax 42 | if len(n) != 2: 43 | fail(n) 44 | row = n[0] # May be slice 45 | if isinstance(row, int): 46 | row = ivalid(row, nrows) 47 | col = n[1] 48 | if isinstance(col, int): 49 | col = ivalid(col, ncols) 50 | if isinstance(row, int) and isinstance(col, int): 51 | yield row * ncols + col 52 | elif isinstance(row, slice) and isinstance(col, int): 53 | rows = do_slice(row, nrows) 54 | if rows is not None: 55 | for row in range(*rows): 56 | yield row * ncols + col 57 | elif isinstance(row, int) and isinstance(col, slice): 58 | cols = do_slice(col, ncols) 59 | if cols is not None: 60 | for col in range(*cols): 61 | yield row * ncols + col 62 | elif isinstance(row, slice) and isinstance(col, slice): 63 | rows = do_slice(row, nrows) 64 | cols = do_slice(col, ncols) 65 | if cols is not None and rows is not None: 66 | for row in range(*rows): 67 | for col in range(*cols): 68 | yield row * ncols + col 69 | else: 70 | fail(n) 71 | else: 72 | fail(n) 73 | -------------------------------------------------------------------------------- /extras/widgets/calendar.py: -------------------------------------------------------------------------------- 1 | # calendar.py Calendar object 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2023 Peter Hinch 5 | from extras.widgets.grid import Grid 6 | 7 | from gui.widgets.label import Label, ALIGN_CENTER 8 | from extras.date import DateCal 9 | 10 | class Calendar: 11 | def __init__( 12 | self, wri, row, col, colwidth, fgcolor, bgcolor, today_c, cur_c, sun_c, today_inv=False, cur_inv=False 13 | ): 14 | self.fgcolor = fgcolor 15 | self.bgcolor = bgcolor 16 | self.today_c = today_c # Color of "today" cell 17 | self.today_inv = today_inv 18 | self.cur_c = cur_c # Calendar currency 19 | self.cur_inv = cur_inv 20 | self.sun_c = sun_c # Sundays 21 | self.date = DateCal() 22 | self.date.callback = self.show 23 | rows = 6 24 | cols = 7 25 | self.ncells = cols * (rows - 1) # Row 0 has day labels 26 | self.last_cell = cols * rows 27 | lw = (colwidth + 4) * cols # Label width = width of grid 28 | kwargs = {"align": ALIGN_CENTER, "fgcolor": fgcolor, "bgcolor": bgcolor} 29 | self.lbl = Label(wri, row, col, lw, **kwargs) 30 | row += self.lbl.height + 3 # Two border widths 31 | self.grid = Grid(wri, row, col, colwidth, rows, cols, **kwargs) 32 | self.grid.show() # Draw grid lines 33 | self.grid[0, 0:7] = iter([d[:3] for d in DateCal.days]) # 3-char day names 34 | self.show() 35 | 36 | def days(self, month_length): # Produce content for every cell 37 | for n in range(self.ncells + 1): 38 | yield str(n + 1) if n < month_length else "" 39 | 40 | def show(self): 41 | grid = self.grid 42 | cur = self.date # Currency 43 | self.lbl.value(f"{DateCal.months[cur.month - 1]} {cur.year}") 44 | values = self.days(cur.month_length) # Instantiate generator 45 | idx_1 = 7 + cur.wday_n(1) # Index of 1st of month 46 | grid[idx_1 : self.last_cell] = values 47 | grid[7 : idx_1] = values 48 | # Assign colors. Last to be applied has priority. 49 | grid[1:6, 6] = {"fgcolor": self.sun_c} # Sunday color 50 | idx_cur = idx_1 + cur.mday - 1 # Currency (overrides Sunday) 51 | if self.cur_inv: 52 | grid[idx_cur] = {"fgcolor": self.bgcolor, "bgcolor": self.cur_c} 53 | else: 54 | grid[idx_cur] = {"fgcolor": self.cur_c} 55 | today = DateCal() 56 | if cur.year == today.year and cur.month == today.month: # Today is in current month 57 | idx = idx_1 + today.mday - 1 58 | if self.today_inv: 59 | grid[idx] = {"fgcolor": self.bgcolor, "bgcolor": self.fgcolor} 60 | else: 61 | grid[idx] = {"fgcolor": self.today_c} 62 | -------------------------------------------------------------------------------- /extras/widgets/clock.py: -------------------------------------------------------------------------------- 1 | # clock.py Analog clock widget for nanogui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2023 Peter Hinch 5 | 6 | from gui.core.nanogui import DObject 7 | from gui.widgets.dial import Dial, Pointer 8 | from gui.core.colors import * 9 | from cmath import rect, pi 10 | 11 | class Clock(DObject): 12 | def __init__( 13 | self, 14 | writer, 15 | row, 16 | col, 17 | height, 18 | fgcolor=None, 19 | bgcolor=None, 20 | bdcolor=RED, 21 | pointers=(CYAN, CYAN, RED), 22 | label=None, 23 | ): 24 | super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor) 25 | dial = Dial(writer, row, col, height=height, ticks=12, bdcolor=None, label=label) 26 | self._dial = dial 27 | self._pcolors = pointers 28 | self._hrs = Pointer(dial) 29 | self._mins = Pointer(dial) 30 | if pointers[2] is not None: 31 | self._secs = Pointer(dial) 32 | 33 | def value(self, t): 34 | super().value(t) 35 | self.show() 36 | 37 | def show(self): 38 | super().show() 39 | t = super().value() 40 | # Return a unit vector of phase phi. Multiplying by this will 41 | # rotate a vector anticlockwise which is mathematically correct. 42 | # Alas clocks, modelled on sundials, were invented in the northern 43 | # hemisphere. Otherwise they would have rotated widdershins like 44 | # the maths. Hence negative sign when called. 45 | def uv(phi): 46 | return rect(1, phi) 47 | 48 | hc, mc, sc = self._pcolors # Colors for pointers 49 | 50 | hstart = 0 + 0.7j # Pointer lengths. Will rotate relative to top. 51 | mstart = 0 + 1j 52 | sstart = 0 + 1j 53 | self._hrs.value(hstart * uv(-t[3] * pi / 6 - t[4] * pi / 360), hc) 54 | self._mins.value(mstart * uv(-t[4] * pi / 30), mc) 55 | if sc is not None: 56 | self._secs.value(sstart * uv(-t[5] * pi / 30), sc) 57 | if self._dial.label is not None: 58 | v = f"{t[3]:02d}.{t[4]:02d}" if sc is None else f"{t[3]:02d}.{t[4]:02d}.{t[5]:02d}" 59 | self._dial.label.value(v) 60 | -------------------------------------------------------------------------------- /extras/widgets/grid.py: -------------------------------------------------------------------------------- 1 | # grid.py nano-gui widget providing the Grid class: a 2d array of Label instances. 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2023 Peter Hinch 5 | 6 | from gui.core.nanogui import DObject, Writer 7 | from gui.core.colors import * 8 | from gui.widgets.label import Label 9 | from extras.parse2d import do_args 10 | 11 | # lwidth may be integer Label width in pixels or a tuple/list of widths 12 | class Grid(DObject): 13 | def __init__(self, writer, row, col, lwidth, nrows, ncols, invert=False, fgcolor=None, bgcolor=BLACK, bdcolor=None, align=0): 14 | self.nrows = nrows 15 | self.ncols = ncols 16 | self.ncells = nrows * ncols 17 | self.cheight = writer.height + 4 # Cell height including borders 18 | # Build column width list. Column width is Label width + 4. 19 | if isinstance(lwidth, int): 20 | self.cwidth = [lwidth + 4] * ncols 21 | else: # Ensure len(.cwidth) == ncols 22 | self.cwidth = [w + 4 for w in lwidth][:ncols] 23 | self.cwidth.extend([lwidth[-1] + 4] * (ncols - len(lwidth))) 24 | width = sum(self.cwidth) - 4 # Dimensions of widget interior 25 | height = nrows * self.cheight - 4 26 | super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor) 27 | self.cells = [] 28 | r = row 29 | c = col 30 | for _ in range(self.nrows): 31 | for cw in self.cwidth: 32 | self.cells.append(Label(writer, r, c, cw - 4, invert, fgcolor, bgcolor, False, align)) # No border 33 | c += cw 34 | r += self.cheight 35 | c = col 36 | 37 | def __getitem__(self, *args): 38 | indices = do_args(args, self.nrows, self.ncols) 39 | for i in indices: 40 | yield self.cells[i] 41 | 42 | # allow grid[r, c] = "foo" or kwargs for Label: 43 | # grid[r, c] = {"text": str(n), "fgcolor" : RED} 44 | def __setitem__(self, *args): 45 | x = args[1] # Value 46 | indices = do_args(args[: -1], self.nrows, self.ncols) 47 | for i in indices: 48 | try: 49 | z = next(x) # May be a generator 50 | except StopIteration: 51 | pass # Repeat last value 52 | except TypeError: 53 | z = x 54 | v = self.cells[i].value # method of Label 55 | _ = v(**z) if isinstance(x, dict) else v(z) 56 | 57 | def show(self): 58 | super().show() # Draw border 59 | if self.has_border: # Draw grid 60 | dev = self.device 61 | color = self.bdcolor 62 | x = self.col - 2 # Border top left corner 63 | y = self.row - 2 64 | dy = self.cheight 65 | for row in range(1, self.nrows): 66 | dev.hline(x, y + row * dy, self.width + 4, color) 67 | for cw in self.cwidth[:-1]: 68 | x += cw 69 | dev.vline(x, y, self.height + 4, color) 70 | -------------------------------------------------------------------------------- /gui/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/gui/core/__init__.py -------------------------------------------------------------------------------- /gui/core/colors.py: -------------------------------------------------------------------------------- 1 | # colors.py Standard color constants for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | from color_setup import SSD 7 | from gui.core.writer import CWriter 8 | 9 | # Code can be portable between 4-bit and other drivers by calling create_color 10 | def create_color(idx, r, g, b): 11 | return CWriter.create_color(SSD, idx, r, g, b) 12 | 13 | if hasattr(SSD, 'lut'): # Colors defined by LUT 14 | BLACK = create_color(0, 0, 0, 0) 15 | GREEN = create_color(1, 0, 255, 0) 16 | RED = create_color(2, 255, 0, 0) 17 | LIGHTRED = create_color(3, 140, 0, 0) 18 | BLUE = create_color(4, 0, 0, 255) 19 | YELLOW = create_color(5, 255, 255, 0) 20 | GREY = create_color(6, 100, 100, 100) 21 | MAGENTA = create_color(7, 255, 0, 255) 22 | CYAN = create_color(8, 0, 255, 255) 23 | LIGHTGREEN = create_color(9, 0, 100, 0) 24 | DARKGREEN = create_color(10, 0, 80, 0) 25 | DARKBLUE = create_color(11, 0, 0, 90) 26 | # 12, 13, 14 free for user definition 27 | WHITE = create_color(15, 255, 255, 255) 28 | else: 29 | BLACK = SSD.rgb(0, 0, 0) 30 | GREEN = SSD.rgb(0, 255, 0) 31 | RED = SSD.rgb(255, 0, 0) 32 | LIGHTRED = SSD.rgb(140, 0, 0) 33 | BLUE = SSD.rgb(0, 0, 255) 34 | YELLOW = SSD.rgb(255, 255, 0) 35 | GREY = SSD.rgb(100, 100, 100) 36 | MAGENTA = SSD.rgb(255, 0, 255) 37 | CYAN = SSD.rgb(0, 255, 255) 38 | LIGHTGREEN = SSD.rgb(0, 100, 0) 39 | DARKGREEN = SSD.rgb(0, 80, 0) 40 | DARKBLUE = SSD.rgb(0, 0, 90) 41 | WHITE = SSD.rgb(255, 255, 255) 42 | -------------------------------------------------------------------------------- /gui/core/nanogui.py: -------------------------------------------------------------------------------- 1 | # nanogui.py Displayable objects based on the Writer and CWriter classes 2 | # V0.41 Peter Hinch 16th Nov 2020 3 | # Move cmath dependency to widgets/dial 4 | 5 | # Released under the MIT License (MIT). See LICENSE. 6 | # Copyright (c) 2018-2021 Peter Hinch 7 | 8 | # Base class for a displayable object. Subclasses must implement .show() and .value() 9 | # Has position, colors and border definition. 10 | # border: False no border None use bgcolor, int: treat as color 11 | 12 | from gui.core.colors import * # Populate color LUT before use. 13 | from gui.core.writer import Writer 14 | import framebuf 15 | import gc 16 | import sys 17 | 18 | version_info = (1, 0, 0) # pep-440-like version number using semantic version number 19 | __version__ = '.'.join(map(str, version_info)) 20 | 21 | if sys.implementation.version < (1, 20, 0): 22 | raise OSError("Firmware V1.20 or later required.") 23 | 24 | def circle(dev, x0, y0, r, color, _=1): # Draw circle 25 | x, y, r = int(x0), int(y0), int(r) 26 | dev.ellipse(x, y, r, r, color) 27 | 28 | 29 | def fillcircle(dev, x0, y0, r, color): # Draw filled circle 30 | x, y, r = int(x0), int(y0), int(r) 31 | dev.ellipse(x, y, r, r, color, True) 32 | 33 | # If a (framebuf based) device is passed to refresh, the screen is cleared. 34 | # None causes pending widgets to be drawn and the result to be copied to hardware. 35 | # The pend mechanism enables a displayable object to postpone its renedering 36 | # until it is complete: efficient for e.g. Dial which may have multiple Pointers 37 | def refresh(device, clear=False): 38 | if not isinstance(device, framebuf.FrameBuffer): 39 | raise ValueError('Device must be derived from FrameBuffer.') 40 | if device not in DObject.devices: 41 | DObject.devices[device] = set() 42 | device.fill(0) 43 | else: 44 | if clear: 45 | DObject.devices[device].clear() # Clear the pending set 46 | device.fill(0) 47 | else: 48 | for obj in DObject.devices[device]: 49 | obj.show() 50 | DObject.devices[device].clear() 51 | device.show() 52 | 53 | # Displayable object: effectively an ABC for all GUI objects. 54 | class DObject(): 55 | devices = {} # Index device instance, value is a set of pending objects 56 | 57 | @classmethod 58 | def _set_pend(cls, obj): 59 | cls.devices[obj.device].add(obj) 60 | 61 | def __init__(self, writer, row, col, height, width, fgcolor, bgcolor, bdcolor): 62 | writer.set_clip(True, True, False) # Disable scrolling text 63 | self.writer = writer 64 | device = writer.device 65 | self.device = device 66 | # The following assumes that the widget is mal-positioned, not oversize. 67 | if row < 0: 68 | row = 0 69 | self.warning() 70 | elif row + height >= device.height: 71 | row = device.height - height - 1 72 | self.warning() 73 | if col < 0: 74 | col = 0 75 | self.warning() 76 | elif col + width >= device.width: 77 | col = device.width - width - 1 78 | self.warning() 79 | self.row = row 80 | self.col = col 81 | self.width = width 82 | self.height = height 83 | self._value = None # Type depends on context but None means don't display. 84 | # Current colors 85 | if fgcolor is None: 86 | fgcolor = writer.fgcolor 87 | if bgcolor is None: 88 | bgcolor = writer.bgcolor 89 | if bdcolor is None: 90 | bdcolor = fgcolor 91 | self.fgcolor = fgcolor 92 | self.bgcolor = bgcolor 93 | # bdcolor is False if no border is to be drawn 94 | self.bdcolor = bdcolor 95 | # Default colors allow restoration after dynamic change 96 | self.def_fgcolor = fgcolor 97 | self.def_bgcolor = bgcolor 98 | self.def_bdcolor = bdcolor 99 | # has_border is True if a border was drawn 100 | self.has_border = False 101 | 102 | def warning(self): 103 | print('Warning: attempt to create {} outside screen dimensions.'.format(self.__class__.__name__)) 104 | 105 | # Blank working area 106 | # Draw a border if .bdcolor specifies a color. If False, erase an existing border 107 | def show(self): 108 | wri = self.writer 109 | dev = self.device 110 | dev.fill_rect(self.col, self.row, self.width, self.height, self.bgcolor) 111 | if isinstance(self.bdcolor, bool): # No border 112 | if self.has_border: # Border exists: erase it 113 | dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bgcolor) 114 | self.has_border = False 115 | elif self.bdcolor: # Border is required 116 | dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bdcolor) 117 | self.has_border = True 118 | 119 | def value(self, v=None): 120 | if v is not None: 121 | self._value = v 122 | return self._value 123 | 124 | def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None): 125 | if hasattr(self, 'label'): 126 | self.label.value(text, invert, fgcolor, bgcolor, bdcolor) 127 | else: 128 | raise ValueError('Attempt to update nonexistent label.') 129 | -------------------------------------------------------------------------------- /gui/demos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/gui/demos/__init__.py -------------------------------------------------------------------------------- /gui/demos/aclock.py: -------------------------------------------------------------------------------- 1 | # aclock.py Test/demo program for nanogui 2 | # Orinally for ssd1351-based OLED displays but runs on most displays 3 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 4 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 5 | 6 | # Released under the MIT License (MIT). See LICENSE. 7 | # Copyright (c) 2018-2020 Peter Hinch 8 | 9 | # Initialise hardware and framebuf before importing modules. 10 | from color_setup import ssd # Create a display instance 11 | from gui.core.nanogui import refresh # Color LUT is updated now. 12 | from gui.widgets.label import Label 13 | from gui.widgets.dial import Dial, Pointer 14 | refresh(ssd, True) # Initialise and clear display. 15 | 16 | # Now import other modules 17 | import cmath 18 | import utime 19 | from gui.core.writer import CWriter 20 | 21 | # Font for CWriter 22 | import gui.fonts.arial10 as arial10 23 | from gui.core.colors import * 24 | 25 | def aclock(): 26 | uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi 27 | pi = cmath.pi 28 | days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 29 | 'Sunday') 30 | months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 31 | 'Aug', 'Sept', 'Oct', 'Nov', 'Dec') 32 | # Instantiate CWriter 33 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 34 | wri = CWriter(ssd, arial10, GREEN, BLACK) # Report on fast mode. Or use verbose=False 35 | wri.set_clip(True, True, False) 36 | 37 | # Instantiate displayable objects 38 | dial = Dial(wri, 2, 2, height = 75, ticks = 12, bdcolor=None, label=120, pip=False) # Border in fg color 39 | lbltim = Label(wri, 5, 85, 35) 40 | hrs = Pointer(dial) 41 | mins = Pointer(dial) 42 | secs = Pointer(dial) 43 | 44 | hstart = 0 + 0.7j # Pointer lengths and position at top 45 | mstart = 0 + 0.92j 46 | sstart = 0 + 0.92j 47 | while True: 48 | t = utime.localtime() 49 | hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360), YELLOW) 50 | mins.value(mstart * uv(-t[4] * pi/30), YELLOW) 51 | secs.value(sstart * uv(-t[5] * pi/30), RED) 52 | lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5])) 53 | dial.text('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0])) 54 | refresh(ssd) 55 | utime.sleep(1) 56 | 57 | aclock() 58 | -------------------------------------------------------------------------------- /gui/demos/aclock_large.py: -------------------------------------------------------------------------------- 1 | # aclock_large.py Test/demo program for displays of 240x240 pixels or larger 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2018-2021 Peter Hinch 5 | 6 | # Initialise hardware and framebuf before importing modules. 7 | from color_setup import ssd # Create a display instance 8 | from gui.core.nanogui import refresh 9 | refresh(ssd, True) # Initialise and clear display. 10 | 11 | # Now import other modules 12 | from gui.widgets.label import Label 13 | from gui.widgets.dial import Dial, Pointer 14 | import cmath 15 | import utime 16 | from gui.core.writer import CWriter 17 | 18 | # Font for CWriter 19 | import gui.fonts.freesans20 as font 20 | from gui.core.colors import * 21 | 22 | def aclock(): 23 | uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi 24 | pi = cmath.pi 25 | days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 26 | 'Sunday') 27 | months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 28 | 'Aug', 'Sept', 'Oct', 'Nov', 'Dec') 29 | # Instantiate CWriter 30 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 31 | wri = CWriter(ssd, font, GREEN, BLACK, verbose=False) 32 | wri.set_clip(True, True, False) 33 | 34 | # Instantiate displayable objects 35 | dial = Dial(wri, 2, 2, height = 150, ticks = 12, bdcolor=None, label=240, pip=False) # Border in fg color 36 | lbltim = Label(wri, 200, 2, 35) 37 | hrs = Pointer(dial) 38 | mins = Pointer(dial) 39 | secs = Pointer(dial) 40 | 41 | hstart = 0 + 0.7j # Pointer lengths and position at top 42 | mstart = 0 + 0.92j 43 | sstart = 0 + 0.92j 44 | while True: 45 | t = utime.localtime() 46 | hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360), YELLOW) 47 | mins.value(mstart * uv(-t[4] * pi/30), YELLOW) 48 | secs.value(sstart * uv(-t[5] * pi/30), RED) 49 | lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5])) 50 | dial.text('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0])) 51 | refresh(ssd) 52 | utime.sleep(1) 53 | 54 | aclock() 55 | -------------------------------------------------------------------------------- /gui/demos/aclock_ttgo.py: -------------------------------------------------------------------------------- 1 | # aclock.py Test/demo program for nanogui 2 | # Orinally for ssd1351-based OLED displays but runs on most displays 3 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 4 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 5 | 6 | # Released under the MIT License (MIT). See LICENSE. 7 | # Copyright (c) 2021 Peter Hinch 8 | 9 | # UK daylight saving code adapted from 10 | # https://forum.micropython.org/viewtopic.php?f=2&t=4034 11 | # Winter UTC Summer (BST) is UTC+1H 12 | # Changes happen last Sundays of March (BST) and October (UTC) at 01:00 UTC 13 | # Ref. formulas : http://www.webexhibits.org/daylightsaving/i.html 14 | # Since 1996, valid through 2099 15 | 16 | 17 | # Initialise hardware and framebuf before importing modules. 18 | from color_setup import ssd # Create a display instance 19 | from gui.core.nanogui import refresh 20 | from gui.widgets.label import Label 21 | from gui.widgets.dial import Dial, Pointer 22 | 23 | refresh(ssd, True) # Initialise and clear display. 24 | 25 | # Now import other modules 26 | import cmath 27 | import time 28 | from gui.core.writer import CWriter 29 | from machine import RTC 30 | import asyncio 31 | import ntptime 32 | import do_connect # WiFi connction script 33 | 34 | # Font for CWriter 35 | import gc 36 | 37 | gc.collect() 38 | import gui.fonts.freesans20 as font 39 | from gui.core.colors import * 40 | 41 | bst = False 42 | 43 | 44 | def gbtime(now): 45 | global bst 46 | bst = False 47 | year = time.localtime(now)[0] # get current year 48 | # Time of March change to BST 49 | t_march = time.mktime((year, 3, (31 - (int(5 * year / 4 + 4)) % 7), 1, 0, 0, 0, 0, 0)) 50 | # Time of October change to UTC 51 | t_october = time.mktime((year, 10, (31 - (int(5 * year / 4 + 1)) % 7), 1, 0, 0, 0, 0, 0)) 52 | if now < t_march: # we are before last sunday of march 53 | gbt = time.localtime(now) # UTC 54 | elif now < t_october: # we are before last sunday of october 55 | gbt = time.localtime(now + 3600) # BST: UTC+1H 56 | bst = True 57 | else: # we are after last sunday of october 58 | gbt = time.localtime(now) # UTC 59 | return gbt 60 | 61 | 62 | async def set_rtc(): 63 | rtc = RTC() 64 | while True: 65 | t = -1 66 | while t < 0: 67 | try: 68 | t = ntptime.time() 69 | except OSError: 70 | print("ntp timeout") 71 | await asyncio.sleep(5) 72 | 73 | s = gbtime(t) # Convert UTC to local (GB) time 74 | t0 = time.time() 75 | rtc.datetime(s[0:3] + (0,) + s[3:6] + (0,)) 76 | print("RTC was set, delta =", time.time() - t0) 77 | await asyncio.sleep(600) 78 | 79 | 80 | async def ramcheck(): 81 | while True: 82 | gc.collect() 83 | print("Free RAM:", gc.mem_free()) 84 | await asyncio.sleep(600) 85 | 86 | 87 | async def aclock(): 88 | do_connect.do_connect() 89 | asyncio.create_task(set_rtc()) 90 | asyncio.create_task(ramcheck()) 91 | uv = lambda phi: cmath.rect(1, phi) # Return a unit vector of phase phi 92 | pi = cmath.pi 93 | days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") 94 | months = ( 95 | "January", 96 | "February", 97 | "March", 98 | "April", 99 | "May", 100 | "June", 101 | "July", 102 | "August", 103 | "September", 104 | "October", 105 | "November", 106 | "December", 107 | ) 108 | # Instantiate CWriter 109 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 110 | wri = CWriter(ssd, font, GREEN, BLACK, verbose=False) 111 | wri.set_clip(True, True, False) 112 | 113 | # Instantiate displayable objects 114 | dial = Dial(wri, 2, 2, height=130, ticks=12, bdcolor=None) # Border in fg color 115 | lbltim = Label(wri, 140, 2, 130) 116 | lblday = Label(wri, 170, 2, 130) 117 | lblmonth = Label(wri, 190, 2, 130) 118 | lblyr = Label(wri, 210, 2, 130) 119 | hrs = Pointer(dial) 120 | mins = Pointer(dial) 121 | secs = Pointer(dial) 122 | 123 | hstart = 0 + 0.7j # Pointer lengths and position at top 124 | mstart = 0 + 0.92j 125 | sstart = 0 + 0.92j 126 | t = time.localtime() 127 | while True: 128 | hrs.value(hstart * uv(-t[3] * pi / 6 - t[4] * pi / 360), YELLOW) 129 | mins.value(mstart * uv(-t[4] * pi / 30 - t[5] * pi / 1800), YELLOW) 130 | secs.value(sstart * uv(-t[5] * pi / 30), RED) 131 | lbltim.value("{:02d}.{:02d}.{:02d} {}".format(t[3], t[4], t[5], "BST" if bst else "UTC")) 132 | lblday.value("{}".format(days[t[6]])) 133 | lblmonth.value("{} {}".format(t[2], months[t[1] - 1])) 134 | lblyr.value("{}".format(t[0])) 135 | refresh(ssd) 136 | st = t 137 | while st == t: 138 | await asyncio.sleep_ms(100) 139 | t = time.localtime() 140 | 141 | 142 | try: 143 | asyncio.run(aclock()) 144 | finally: 145 | _ = asyncio.new_event_loop() 146 | -------------------------------------------------------------------------------- /gui/demos/alevel.py: -------------------------------------------------------------------------------- 1 | # alevel.py Test/demo "spirit level" program. 2 | # Requires Pyboard for accelerometer. 3 | # Tested with Adafruit ssd1351 OLED display. 4 | 5 | # Released under the MIT License (MIT). See LICENSE. 6 | # Copyright (c) 2018-2020 Peter Hinch 7 | 8 | # Initialise hardware and framebuf before importing modules. 9 | from color_setup import ssd # Create a display instance 10 | 11 | from gui.core.nanogui import refresh 12 | from gui.widgets.dial import Dial, Pointer 13 | refresh(ssd, True) # Initialise and clear display. 14 | 15 | # Now import other modules 16 | 17 | import utime 18 | import pyb 19 | from gui.core.writer import CWriter 20 | import gui.fonts.arial10 as arial10 21 | from gui.core.colors import * 22 | 23 | def main(): 24 | print('alevel test is running.') 25 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 26 | wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) 27 | wri.set_clip(True, True, False) 28 | acc = pyb.Accel() 29 | dial = Dial(wri, 5, 5, height = 75, ticks = 12, bdcolor=None, 30 | label='Tilt Pyboard', style = Dial.COMPASS, pip=YELLOW) # Border in fg color 31 | ptr = Pointer(dial) 32 | scale = 1/40 33 | while True: 34 | x, y, z = acc.filtered_xyz() 35 | # Depending on relative alignment of display and Pyboard this line may 36 | # need changing: swap x and y or change signs so arrow points in direction 37 | # board is tilted. 38 | ptr.value(-y*scale + 1j*x*scale, YELLOW) 39 | refresh(ssd) 40 | utime.sleep_ms(200) 41 | 42 | main() 43 | -------------------------------------------------------------------------------- /gui/demos/asnano.py: -------------------------------------------------------------------------------- 1 | # asnano.py Test/demo program for use of nanogui with asyncio 2 | # Uses Adafruit ssd1351-based OLED displays (change height to suit) 3 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 4 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 5 | 6 | # Copyright (c) 2020 Peter Hinch 7 | # Released under the MIT License (MIT) - see LICENSE file 8 | 9 | # Initialise hardware and framebuf before importing modules. 10 | from color_setup import ssd # Create a display instance 11 | 12 | import asyncio 13 | import pyb 14 | import uos 15 | from gui.core.writer import CWriter 16 | from gui.core.nanogui import refresh 17 | from gui.widgets.led import LED 18 | from gui.widgets.meter import Meter 19 | 20 | refresh(ssd) 21 | 22 | # Fonts 23 | import gui.fonts.arial10 as arial10 24 | import gui.fonts.freesans20 as freesans20 25 | 26 | from gui.core.colors import * 27 | 28 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 29 | wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) 30 | wri.set_clip(True, True, False) 31 | 32 | color = lambda v: RED if v > 0.7 else YELLOW if v > 0.5 else GREEN 33 | txt = lambda v: "ovr" if v > 0.7 else "high" if v > 0.5 else "ok" 34 | 35 | 36 | async def meter(n, x, text, t): 37 | print("Meter {} test.".format(n)) 38 | m = Meter( 39 | wri, 40 | 5, 41 | x, 42 | divisions=4, 43 | ptcolor=YELLOW, 44 | label=text, 45 | style=Meter.BAR, 46 | legends=("0.0", "0.5", "1.0"), 47 | ) 48 | l = LED(wri, ssd.height - 16 - wri.height, x, bdcolor=YELLOW, label="over") 49 | while True: 50 | v = int.from_bytes(uos.urandom(3), "little") / 16777216 51 | m.value(v, color(v)) 52 | l.color(color(v)) 53 | l.text(txt(v), fgcolor=color(v)) 54 | refresh(ssd) 55 | await asyncio.sleep_ms(t) 56 | 57 | 58 | async def flash(n, t): 59 | led = pyb.LED(n) 60 | while True: 61 | led.toggle() 62 | await asyncio.sleep_ms(t) 63 | 64 | 65 | async def killer(tasks): 66 | sw = pyb.Switch() 67 | while not sw(): 68 | await asyncio.sleep_ms(100) 69 | for task in tasks: 70 | task.cancel() 71 | 72 | 73 | async def main(): 74 | tasks = [] 75 | tasks.append(asyncio.create_task(meter(1, 2, "left", 700))) 76 | tasks.append(asyncio.create_task(meter(2, 50, "right", 1000))) 77 | tasks.append(asyncio.create_task(meter(3, 98, "bass", 1500))) 78 | tasks.append(asyncio.create_task(flash(1, 200))) 79 | tasks.append(asyncio.create_task(flash(2, 233))) 80 | await killer(tasks) 81 | 82 | 83 | print("Press Pyboard usr button to stop test.") 84 | try: 85 | asyncio.run(main()) 86 | finally: # Reset asyncio case of KeyboardInterrupt 87 | asyncio.new_event_loop() 88 | -------------------------------------------------------------------------------- /gui/demos/asnano_sync.py: -------------------------------------------------------------------------------- 1 | # asnano_sync.py Test/demo program for use of nanogui with asyncio 2 | # Requires Pyboard for switch and LEDs. 3 | # Tested with Adafruit ssd1351 OLED display. 4 | 5 | # Copyright (c) 2020 Peter Hinch 6 | # Released under the MIT License (MIT) - see LICENSE file 7 | 8 | # Initialise hardware and framebuf before importing modules 9 | from color_setup import ssd # Create a display instance 10 | 11 | import asyncio 12 | import pyb 13 | import uos 14 | from gui.core.writer import CWriter 15 | from gui.core.nanogui import refresh 16 | from gui.widgets.led import LED 17 | from gui.widgets.meter import Meter 18 | 19 | refresh(ssd, True) 20 | 21 | # Fonts 22 | import gui.fonts.arial10 as arial10 23 | import gui.fonts.freesans20 as freesans20 24 | 25 | from gui.core.colors import * 26 | 27 | color = lambda v: RED if v > 0.7 else YELLOW if v > 0.5 else GREEN 28 | txt = lambda v: "ovr" if v > 0.7 else "high" if v > 0.5 else "ok" 29 | 30 | 31 | class MyMeter(Meter): 32 | def __init__(self, x, text): 33 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 34 | wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) 35 | wri.set_clip(True, True, False) 36 | super().__init__( 37 | wri, 38 | 5, 39 | x, 40 | divisions=4, 41 | ptcolor=YELLOW, 42 | label=text, 43 | style=Meter.BAR, 44 | legends=("0.0", "0.5", "1.0"), 45 | ) 46 | self.led = LED(wri, ssd.height - 16 - wri.height, x, bdcolor=YELLOW, label="over") 47 | self.task = asyncio.create_task(self._run()) 48 | 49 | async def _run(self): 50 | while True: 51 | v = int.from_bytes(uos.urandom(3), "little") / 16777216 52 | self.value(v, color(v)) 53 | self.led.color(color(v)) 54 | self.led.text(txt(v), fgcolor=color(v)) 55 | # Slow asynchronous data acquisition might occur here. Note 56 | # that meters update themselves asynchronously (in a real 57 | # application as data becomes available). 58 | await asyncio.sleep(v) # Demo variable times 59 | 60 | 61 | async def flash(n, t): 62 | led = pyb.LED(n) 63 | try: 64 | while True: 65 | led.toggle() 66 | await asyncio.sleep_ms(t) 67 | except asyncio.CancelledError: 68 | led.off() # Demo tidying up on cancellation. 69 | 70 | 71 | class Killer: 72 | def __init__(self): 73 | self.sw = pyb.Switch() 74 | 75 | async def wait(self, t): 76 | while t >= 0: 77 | if self.sw(): 78 | return True 79 | await asyncio.sleep_ms(50) 80 | t -= 50 81 | return False 82 | 83 | 84 | # The main task instantiates other tasks then does the display update process. 85 | async def main(): 86 | print("Press Pyboard usr button to stop test.") 87 | # Asynchronously flash Pyboard LED's. Because we can. 88 | leds = [asyncio.create_task(flash(1, 200)), asyncio.create_task(flash(2, 233))] 89 | # Task for each meter and GUI LED 90 | mtasks = [MyMeter(2, "left").task, MyMeter(50, "right").task, MyMeter(98, "bass").task] 91 | k = Killer() 92 | while True: 93 | if await k.wait(800): # Switch was pressed 94 | break 95 | refresh(ssd) 96 | for task in mtasks + leds: 97 | task.cancel() 98 | await asyncio.sleep_ms(0) 99 | ssd.fill(0) # Clear display at end. 100 | refresh(ssd) 101 | 102 | 103 | def test(): 104 | try: 105 | asyncio.run(main()) 106 | finally: # Reset asyncio case of KeyboardInterrupt 107 | asyncio.new_event_loop() 108 | print("asnano_sync.test() to re-run test.") 109 | 110 | 111 | test() 112 | -------------------------------------------------------------------------------- /gui/demos/clock_batt.py: -------------------------------------------------------------------------------- 1 | # clock_batt.py Battery powered clock demo for Pyboard/Adafruit sharp 2.7" display 2 | 3 | # Copyright (c) 2020 Peter Hinch 4 | # Released under the MIT license. See LICENSE 5 | 6 | # HARDWARE 7 | # This assumes a Pybaord D in WBUS-DIP28 and powered by a LiPo cell 8 | # WIRING 9 | # Pyb SSD 10 | # Vin Vin Pyboard D: Vin on DIP28 is an output when powered by LiPo 11 | # Gnd Gnd 12 | # Y8 DI 13 | # Y6 CLK 14 | # Y5 CS 15 | 16 | 17 | # Demo of initialisation procedure designed to minimise risk of memory fail 18 | # when instantiating the frame buffer. The aim is to do this as early as 19 | # possible before importing other modules. 20 | 21 | from color_setup import ssd # Create a display instance 22 | 23 | import upower 24 | from gui.core.nanogui import refresh 25 | from gui.widgets.label import Label 26 | from gui.widgets.dial import Dial, Pointer 27 | 28 | import pyb 29 | import cmath 30 | 31 | from gui.core.writer import Writer 32 | 33 | # Fonts for Writer 34 | import gui.fonts.freesans20 as font_small 35 | import gui.fonts.arial35 as font_large 36 | 37 | refresh(ssd) # Initialise display. 38 | 39 | def aclock(): 40 | rtc = pyb.RTC() 41 | uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi 42 | pi = cmath.pi 43 | days = ('Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun') 44 | months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 45 | 'Aug', 'Sept', 'Oct', 'Nov', 'Dec') 46 | # Instantiate Writer 47 | Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it 48 | wri = Writer(ssd, font_small, verbose=False) 49 | wri.set_clip(True, True, False) 50 | wri_tim = Writer(ssd, font_large, verbose=False) 51 | wri_tim.set_clip(True, True, False) 52 | 53 | # Instantiate displayable objects 54 | dial = Dial(wri, 2, 2, height = 215, ticks = 12, bdcolor=None, pip=True) 55 | lbltim = Label(wri_tim, 50, 230, '00.00.00') 56 | lbldat = Label(wri, 100, 230, 100) 57 | hrs = Pointer(dial) 58 | mins = Pointer(dial) 59 | 60 | hstart = 0 + 0.7j # Pointer lengths and position at top 61 | mstart = 0 + 0.92j 62 | while True: 63 | t = rtc.datetime() # (year, month, day, weekday, hours, minutes, seconds, subseconds) 64 | hang = -t[4]*pi/6 - t[5]*pi/360 # Angles of hands in radians 65 | mang = -t[5] * pi/30 66 | if abs(hang - mang) < pi/360: # Avoid visually confusing overlap of hands 67 | hang += pi/30 # by making hr hand lag slightly 68 | hrs.value(hstart * uv(hang)) 69 | mins.value(mstart * uv(mang)) 70 | lbltim.value('{:02d}.{:02d}'.format(t[4], t[5])) 71 | lbldat.value('{} {} {} {}'.format(days[t[3] - 1], t[2], months[t[1] - 1], t[0])) 72 | refresh(ssd) 73 | # Power saving: only refresh every 30s 74 | for _ in range(30): 75 | upower.lpdelay(1000) 76 | ssd.update() # Toggle VCOM 77 | 78 | aclock() 79 | -------------------------------------------------------------------------------- /gui/demos/clocktest.py: -------------------------------------------------------------------------------- 1 | # clocktest.py Test/demo program for Adafruit sharp 2.7" display 2 | 3 | # Copyright (c) 2020 Peter Hinch 4 | # Released under the MIT license. See LICENSE 5 | 6 | # WIRING 7 | # Pyb SSD 8 | # Vin Vin Pyboard: Vin is an output when powered by USB 9 | # Gnd Gnd 10 | # Y8 DI 11 | # Y6 CLK 12 | # Y5 CS 13 | 14 | 15 | # Demo of initialisation procedure designed to minimise risk of memory fail 16 | # when instantiating the frame buffer. The aim is to do this as early as 17 | # possible before importing other modules. 18 | 19 | from color_setup import ssd # Create a display instance 20 | 21 | from gui.core.nanogui import refresh 22 | from gui.widgets.label import Label 23 | from gui.widgets.dial import Dial, Pointer 24 | 25 | import cmath 26 | import utime 27 | 28 | from gui.core.writer import Writer 29 | 30 | # Fonts for Writer 31 | import gui.fonts.freesans20 as font_small 32 | import gui.fonts.arial35 as font_large 33 | 34 | refresh(ssd) # Initialise display. 35 | 36 | def aclock(): 37 | uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi 38 | pi = cmath.pi 39 | days = ('Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun') 40 | months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 41 | 'Aug', 'Sept', 'Oct', 'Nov', 'Dec') 42 | # Instantiate Writer 43 | Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it 44 | wri = Writer(ssd, font_small, verbose=False) 45 | wri.set_clip(True, True, False) 46 | wri_tim = Writer(ssd, font_large, verbose=False) 47 | wri_tim.set_clip(True, True, False) 48 | 49 | # Instantiate displayable objects 50 | dial = Dial(wri, 2, 2, height = 215, ticks = 12, bdcolor=None, pip=True) 51 | lbltim = Label(wri_tim, 50, 230, '00.00.00') 52 | lbldat = Label(wri, 100, 230, 100) 53 | hrs = Pointer(dial) 54 | mins = Pointer(dial) 55 | secs = Pointer(dial) 56 | 57 | hstart = 0 + 0.7j # Pointer lengths and position at top 58 | mstart = 0 + 0.92j 59 | sstart = 0 + 0.92j 60 | while True: 61 | t = utime.localtime() 62 | hang = -t[3]*pi/6 - t[4]*pi/360 # Angles of hour and minute hands 63 | mang = -t[4] * pi/30 64 | sang = -t[5] * pi/30 65 | if abs(hang - mang) < pi/360: # Avoid overlap of hr and min hands 66 | hang += pi/30 # which is visually confusing. Add slight lag to hrs 67 | hrs.value(hstart * uv(hang)) 68 | mins.value(mstart * uv(mang)) 69 | secs.value(sstart * uv(sang)) 70 | lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5])) 71 | lbldat.value('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0])) 72 | refresh(ssd) 73 | utime.sleep(1) 74 | 75 | aclock() 76 | -------------------------------------------------------------------------------- /gui/demos/color96.py: -------------------------------------------------------------------------------- 1 | # color96.py Test/demo program for ssd1331 Adafruit 0.96" OLED display. 2 | # Cross-platfom. 3 | # Works on larger displays, but only occupies the top left region. 4 | # https://www.adafruit.com/product/684 5 | # For wiring details see drivers/ADAFRUIT.md in this repo. 6 | 7 | # Released under the MIT License (MIT). See LICENSE. 8 | # Copyright (c) 2018-2020 Peter Hinch 9 | 10 | # Initialise hardware and framebuf before importing modules. 11 | from color_setup import ssd # Create a display instance 12 | 13 | from gui.core.nanogui import refresh 14 | from gui.widgets.led import LED 15 | from gui.widgets.meter import Meter 16 | from gui.widgets.label import Label 17 | 18 | refresh(ssd, True) 19 | # Fonts 20 | import gui.fonts.arial10 as arial10 21 | 22 | from gui.core.writer import Writer, CWriter 23 | import utime 24 | import uos 25 | from gui.core.colors import * 26 | 27 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 28 | wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) 29 | wri.set_clip(True, True, False) 30 | 31 | def meter(): 32 | print('meter') 33 | refresh(ssd, True) # Clear any prior image 34 | m = Meter(wri, 5, 2, height = 45, divisions = 4, ptcolor=YELLOW, 35 | label='level', style=Meter.BAR, legends=('0.0', '0.5', '1.0')) 36 | l = LED(wri, 5, 40, bdcolor=YELLOW, label ='over') 37 | steps = 10 38 | for _ in range(steps): 39 | v = int.from_bytes(uos.urandom(3),'little')/16777216 40 | m.value(v) 41 | l.color(GREEN if v < 0.5 else RED) 42 | refresh(ssd) 43 | utime.sleep(1) 44 | refresh(ssd) 45 | 46 | 47 | def multi_fields(t): 48 | print('multi_fields') 49 | refresh(ssd, True) # Clear any prior image 50 | nfields = [] 51 | dy = wri.height + 6 52 | y = 2 53 | col = 15 54 | width = wri.stringlen('99.99') 55 | for txt in ('X:', 'Y:', 'Z:'): 56 | Label(wri, y, 0, txt) # Use wri default colors 57 | nfields.append(Label(wri, y, col, width, bdcolor=None)) # Specify a border, color TBD 58 | y += dy 59 | 60 | end = utime.ticks_add(utime.ticks_ms(), t * 1000) 61 | while utime.ticks_diff(end, utime.ticks_ms()) > 0: 62 | for field in nfields: 63 | value = int.from_bytes(uos.urandom(3),'little')/167772 64 | overrange = None if value < 70 else YELLOW if value < 90 else RED 65 | field.value('{:5.2f}'.format(value), fgcolor = overrange, bdcolor = overrange) 66 | refresh(ssd) 67 | utime.sleep(1) 68 | Label(wri, 0, 64, ' OK ', True, fgcolor = RED) 69 | refresh(ssd) 70 | utime.sleep(1) 71 | 72 | def vari_fields(): 73 | print('vari_fields') 74 | refresh(ssd, True) # Clear any prior image 75 | Label(wri, 0, 0, 'Text:') 76 | Label(wri, 20, 0, 'Border:') 77 | width = wri.stringlen('Yellow') 78 | lbl_text = Label(wri, 0, 40, width) 79 | lbl_bord = Label(wri, 20, 40, width) 80 | lbl_text.value('Red') 81 | lbl_bord.value('Red') 82 | lbl_var = Label(wri, 40, 2, '25.46', fgcolor=RED, bdcolor=RED) 83 | refresh(ssd) 84 | utime.sleep(2) 85 | lbl_text.value('Red') 86 | lbl_bord.value('Yellow') 87 | lbl_var.value(bdcolor=YELLOW) 88 | refresh(ssd) 89 | utime.sleep(2) 90 | lbl_text.value('Red') 91 | lbl_bord.value('None') 92 | lbl_var.value(bdcolor=False) 93 | refresh(ssd) 94 | utime.sleep(2) 95 | lbl_text.value('Yellow') 96 | lbl_bord.value('None') 97 | lbl_var.value(fgcolor=YELLOW) 98 | refresh(ssd) 99 | utime.sleep(2) 100 | lbl_text.value('Blue') 101 | lbl_bord.value('Green') 102 | lbl_var.value('18.99', fgcolor=BLUE, bdcolor=GREEN) 103 | refresh(ssd) 104 | 105 | print('Color display test is running.') 106 | print('Test runs to completion.') 107 | meter() 108 | multi_fields(t = 10) 109 | vari_fields() 110 | print('Test complete.') 111 | -------------------------------------------------------------------------------- /gui/demos/epd29_async.py: -------------------------------------------------------------------------------- 1 | # epd29_async.py Demo program for nano_gui on an Adafruit 2.9" flexible ePaper screen 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # color_setup must set landcsape True, asyn True and must not set demo_mode 7 | from cmath import exp, pi 8 | import asyncio 9 | from color_setup import ssd 10 | 11 | # On a monochrome display Writer is more efficient than CWriter. 12 | from gui.core.writer import Writer 13 | from gui.core.nanogui import refresh 14 | from gui.widgets.meter import Meter 15 | from gui.widgets.label import Label 16 | from gui.widgets.dial import Dial, Pointer 17 | 18 | # Fonts 19 | import gui.fonts.arial10 as arial10 20 | import gui.fonts.font6 as small 21 | 22 | # ssd._asyn = True # HACK to make it config agnostic 23 | # Some ports don't support uos.urandom. 24 | # See https://github.com/peterhinch/micropython-samples/tree/master/random 25 | def xorshift64star(modulo, seed=0xF9AC6BA4): 26 | x = seed 27 | 28 | def func(): 29 | nonlocal x 30 | x ^= x >> 12 31 | x ^= (x << 25) & 0xFFFFFFFFFFFFFFFF # modulo 2**64 32 | x ^= x >> 27 33 | return (x * 0x2545F4914F6CDD1D) % modulo 34 | 35 | return func 36 | 37 | 38 | async def compass(evt): 39 | wri = Writer(ssd, arial10, verbose=False) 40 | wri.set_clip(False, False, False) 41 | v1 = 0 + 0.9j 42 | v2 = exp(0 - (pi / 6) * 1j) 43 | dial = Dial( 44 | wri, 5, 5, height=75, ticks=12, bdcolor=None, label="Direction", style=Dial.COMPASS 45 | ) 46 | ptr = Pointer(dial) 47 | while True: 48 | ptr.value(v1) 49 | v1 *= v2 50 | await evt.wait() 51 | 52 | 53 | async def multi_fields(evt): 54 | wri = Writer(ssd, small, verbose=False) 55 | wri.set_clip(False, False, False) 56 | 57 | nfields = [] 58 | dy = small.height() + 10 59 | row = 2 60 | col = 100 61 | width = wri.stringlen("99.990") 62 | for txt in ("X:", "Y:", "Z:"): 63 | Label(wri, row, col, txt) 64 | nfields.append(Label(wri, row, col, width, bdcolor=None)) # Draw border 65 | row += dy 66 | 67 | random = xorshift64star(2 ** 24 - 1) 68 | while True: 69 | for _ in range(10): 70 | for field in nfields: 71 | value = random() / 167772 72 | field.value("{:5.2f}".format(value)) 73 | await evt.wait() 74 | 75 | 76 | async def meter(evt): 77 | wri = Writer(ssd, arial10, verbose=False) 78 | wri.set_clip(False, False, False) 79 | row = 10 80 | col = 170 81 | args = {"height": 80, "width": 15, "divisions": 4, "style": Meter.BAR} 82 | m0 = Meter(wri, row, col, legends=("0.0", "0.5", "1.0"), **args) 83 | m1 = Meter(wri, row, col + 40, legends=("-1", "0", "+1"), **args) 84 | m2 = Meter(wri, row, col + 80, legends=("-1", "0", "+1"), **args) 85 | random = xorshift64star(2 ** 24 - 1) 86 | while True: 87 | steps = 10 88 | for n in range(steps + 1): 89 | m0.value(random() / 16777216) 90 | m1.value(n / steps) 91 | m2.value(1 - n / steps) 92 | await evt.wait() 93 | 94 | 95 | async def main(): 96 | refresh(ssd, True) # Clear display 97 | await ssd.complete.wait() 98 | print("Ready") 99 | evt = asyncio.Event() 100 | asyncio.create_task(meter(evt)) 101 | asyncio.create_task(multi_fields(evt)) 102 | asyncio.create_task(compass(evt)) 103 | while True: 104 | # Normal procedure before refresh, but 10s sleep should mean it always returns immediately 105 | await ssd.complete.wait() 106 | refresh(ssd) # Launches ._as_show() 107 | await ssd.updated.wait() 108 | # Content has now been shifted out so coros can update 109 | # framebuffer in background 110 | evt.set() 111 | evt.clear() 112 | await asyncio.sleep(10) # Allow for slow refresh 113 | 114 | 115 | tstr = """Test of asynchronous code updating the EPD. This should 116 | not be run for long periods as the EPD should not be updated more 117 | frequently than every 180s. 118 | """ 119 | 120 | print(tstr) 121 | 122 | try: 123 | asyncio.run(main()) 124 | except KeyboardInterrupt: 125 | # Defensive code: avoid leaving EPD hardware in an undefined state. 126 | print("Waiting for display to become idle") 127 | ssd.sleep() # Synchronous code. May block for 5s if display is updating. 128 | finally: 129 | _ = asyncio.new_event_loop() 130 | -------------------------------------------------------------------------------- /gui/demos/epd29_lowpower.py: -------------------------------------------------------------------------------- 1 | # epd29_sync.py Demo of synchronous code on 2.9" EPD display 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # color_setup must set landcsape True, asyn False and must not set demo_mode 7 | 8 | from math import pi, sin 9 | import upower 10 | import machine 11 | import pyb 12 | pon = machine.Pin('Y5', machine.Pin.OUT_PP, value=1) # Power on before instantiating display 13 | upower.lpdelay(1000) # Give the valves (tubes) time to warm up :) 14 | from color_setup import ssd # Instantiate 15 | from gui.core.writer import Writer 16 | from gui.core.nanogui import refresh 17 | from gui.core.fplot import CartesianGraph, Curve 18 | from gui.widgets.meter import Meter 19 | from gui.widgets.label import Label 20 | from gui.widgets.dial import Dial, Pointer 21 | 22 | # Fonts 23 | import gui.fonts.arial10 as arial10 24 | import gui.fonts.freesans20 as large 25 | 26 | wri = Writer(ssd, arial10, verbose=False) 27 | wri.set_clip(False, False, False) 28 | 29 | wri_large = Writer(ssd, large, verbose=False) 30 | wri_large.set_clip(False, False, False) 31 | 32 | def graph(): 33 | row, col, ht, wd = 5, 140, 75, 150 34 | def populate(): 35 | x = -0.998 36 | while x < 1.01: 37 | z = 6 * pi * x 38 | y = sin(z) / z 39 | yield x, y 40 | x += 0.05 41 | 42 | g = CartesianGraph(wri, row, col, height = ht, width = wd, bdcolor=False) 43 | curve2 = Curve(g, None, populate()) 44 | Label(wri, row + ht + 5, col - 10, '-2.0 t: secs') 45 | Label(wri, row + ht + 5, col - 8 + int(wd//2), '0.0') 46 | Label(wri, row + ht + 5, col - 10 + wd, '2.0') 47 | 48 | def compass(): 49 | dial = Dial(wri, 5, 5, height = 75, ticks = 12, bdcolor=None, 50 | label='Direction', style = Dial.COMPASS) 51 | ptr = Pointer(dial) 52 | ptr.value(1 + 1j) 53 | 54 | def meter(): 55 | m = Meter(wri, 5, 100, height = 75, divisions = 4, 56 | label='Peak', style=Meter.BAR, legends=('0', '50', '100')) 57 | m.value(0.72) 58 | 59 | def labels(): 60 | row = 100 61 | col = 0 62 | Label(wri_large, row, col, 'Seismograph') 63 | col = 140 64 | Label(wri, row, col + 0, 'Event time') 65 | Label(wri, row, col + 60, '01:35', bdcolor=None) 66 | Label(wri, row, col + 95, 'UTC') 67 | row = 115 68 | Label(wri, row, col + 0, 'Event date') 69 | Label(wri, row, col + 60, '6th Jan 2021', bdcolor=None) 70 | 71 | 72 | # Populate the display - GUI and Writer code goes here 73 | def populate(): 74 | graph() 75 | compass() 76 | meter() 77 | labels() 78 | 79 | # Initialise GUI clearing display. Populate frame buffer. Update diplay and 80 | # leave in power down state ready for phsyical loss of power 81 | def show(): 82 | # Low power version of .wait_until_ready() 83 | def wait_ready(): 84 | while not ssd.ready(): 85 | upower.lpdelay(1000) 86 | 87 | refresh(ssd, True) # Init and clear. busy will go True for ~5s 88 | populate() 89 | wait_ready() # wait for display ready (seconds) 90 | refresh(ssd) 91 | wait_ready() 92 | ssd.sleep() # Put into "off" state 93 | 94 | # Handle initial power up and subsequent wakeup. 95 | rtc = pyb.RTC() 96 | # If we have a backup battery clear down any setting from a previously running program 97 | rtc.wakeup(None) 98 | reason = machine.reset_cause() # Why have we woken? 99 | red = pyb.LED(1) 100 | if reason in (machine.PWRON_RESET, machine.HARD_RESET, machine.SOFT_RESET): 101 | # Code to run when the application is first started 102 | aa = upower.Alarm('a') 103 | aa.timeset(second = 39) 104 | ab = upower.Alarm('b') 105 | ab.timeset(second = 9) 106 | elif reason == machine.DEEPSLEEP_RESET: 107 | # Display on. Pin is pulled down by 2K2 so hi-z turns display off. 108 | red.on() 109 | show() 110 | pon(0) # Physically power down display 111 | red.off() 112 | 113 | pyb.standby() 114 | -------------------------------------------------------------------------------- /gui/demos/epd29_sync.py: -------------------------------------------------------------------------------- 1 | # epd29_sync.py Demo of synchronous code on 2.9" EPD display 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # color_setup must set landcsape True, asyn False and must not set demo_mode 7 | 8 | from math import pi, sin 9 | from color_setup import ssd 10 | from gui.core.writer import Writer 11 | from gui.core.nanogui import refresh 12 | from gui.core.fplot import CartesianGraph, Curve 13 | from gui.widgets.meter import Meter 14 | from gui.widgets.label import Label 15 | from gui.widgets.dial import Dial, Pointer 16 | 17 | # Fonts 18 | import gui.fonts.arial10 as arial10 19 | import gui.fonts.freesans20 as large 20 | 21 | wri = Writer(ssd, arial10, verbose=False) 22 | wri.set_clip(False, False, False) 23 | 24 | wri_large = Writer(ssd, large, verbose=False) 25 | wri_large.set_clip(False, False, False) 26 | 27 | # 296*128 28 | def graph(): 29 | row, col, ht, wd = 5, 140, 75, 150 30 | def populate(): 31 | x = -0.998 32 | while x < 1.01: 33 | z = 6 * pi * x 34 | y = sin(z) / z 35 | yield x, y 36 | x += 0.05 37 | 38 | g = CartesianGraph(wri, row, col, height = ht, width = wd, bdcolor=False) 39 | curve2 = Curve(g, None, populate()) 40 | Label(wri, row + ht + 5, col - 10, '-2.0 t: secs') 41 | Label(wri, row + ht + 5, col - 8 + int(wd//2), '0.0') 42 | Label(wri, row + ht + 5, col - 10 + wd, '2.0') 43 | 44 | def compass(): 45 | dial = Dial(wri, 5, 5, height = 75, ticks = 12, bdcolor=None, 46 | label='Direction', style = Dial.COMPASS) 47 | ptr = Pointer(dial) 48 | ptr.value(1 + 1j) 49 | 50 | def meter(): 51 | m = Meter(wri, 5, 100, height = 75, divisions = 4, 52 | label='Peak', style=Meter.BAR, legends=('0', '50', '100')) 53 | m.value(0.72) 54 | 55 | def labels(): 56 | row = 100 57 | col = 0 58 | Label(wri_large, row, col, 'Seismograph') 59 | col = 140 60 | Label(wri, row, col + 0, 'Event time') 61 | Label(wri, row, col + 60, '01:35', bdcolor=None) 62 | Label(wri, row, col + 95, 'UTC') 63 | row = 115 64 | Label(wri, row, col + 0, 'Event date') 65 | Label(wri, row, col + 60, '6th Jan 2021', bdcolor=None) 66 | 67 | def main(): 68 | refresh(ssd, True) 69 | graph() 70 | compass() 71 | meter() 72 | labels() 73 | ssd.wait_until_ready() 74 | refresh(ssd) 75 | print('Waiting for display update') 76 | ssd.wait_until_ready() 77 | 78 | main() 79 | 80 | -------------------------------------------------------------------------------- /gui/demos/epd_async.py: -------------------------------------------------------------------------------- 1 | # epd_async.py Demo of nano_gui asynchronous code. 2 | # Needs a large screen e.g. 3 | # https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT 4 | # or 4.2" Waveshare Pico ePaper display. 5 | 6 | # Released under the MIT License (MIT). See LICENSE. 7 | # Copyright (c) 2020-2022 Peter Hinch 8 | 9 | # color_setup must set landcsape False, asyn True and must not set demo_mode 10 | import asyncio 11 | from color_setup import ssd 12 | from gui.core.writer import Writer 13 | from gui.core.nanogui import refresh 14 | from gui.widgets.meter import Meter 15 | from gui.widgets.label import Label 16 | 17 | # Fonts 18 | import gui.fonts.arial10 as arial10 19 | import gui.fonts.courier20 as fixed 20 | import gui.fonts.font6 as small 21 | 22 | # Some ports don't support uos.urandom. 23 | # See https://github.com/peterhinch/micropython-samples/tree/master/random 24 | def xorshift64star(modulo, seed=0xF9AC6BA4): 25 | x = seed 26 | 27 | def func(): 28 | nonlocal x 29 | x ^= x >> 12 30 | x ^= (x << 25) & 0xFFFFFFFFFFFFFFFF # modulo 2**64 31 | x ^= x >> 27 32 | return (x * 0x2545F4914F6CDD1D) % modulo 33 | 34 | return func 35 | 36 | 37 | async def fields(evt): 38 | wri = Writer(ssd, fixed, verbose=False) 39 | wri.set_clip(False, False, False) 40 | textfield = Label(wri, 0, 2, wri.stringlen("longer")) 41 | numfield = Label(wri, 25, 2, wri.stringlen("99.990"), bdcolor=None) 42 | countfield = Label(wri, 0, 90, wri.stringlen("1")) 43 | n = 1 44 | random = xorshift64star(65535) 45 | while True: 46 | for s in ("short", "longer", "1", ""): 47 | textfield.value(s) 48 | numfield.value("{:5.2f}".format(random() / 1000)) 49 | countfield.value("{:1d}".format(n)) 50 | n += 1 51 | await evt.wait() 52 | 53 | 54 | async def multi_fields(evt): 55 | wri = Writer(ssd, small, verbose=False) 56 | wri.set_clip(False, False, False) 57 | 58 | nfields = [] 59 | dy = small.height() + 10 60 | y = 80 61 | col = 20 62 | width = wri.stringlen("99.990") 63 | for txt in ("X:", "Y:", "Z:"): 64 | Label(wri, y, 0, txt) 65 | nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border 66 | y += dy 67 | 68 | random = xorshift64star(2 ** 24 - 1) 69 | while True: 70 | for _ in range(10): 71 | for field in nfields: 72 | value = random() / 167772 73 | field.value("{:5.2f}".format(value)) 74 | await evt.wait() 75 | 76 | 77 | async def meter(evt): 78 | wri = Writer(ssd, arial10, verbose=False) 79 | args = {"height": 80, "width": 15, "divisions": 4, "style": Meter.BAR} 80 | m0 = Meter(wri, 165, 2, legends=("0.0", "0.5", "1.0"), **args) 81 | m1 = Meter(wri, 165, 62, legends=("-1", "0", "+1"), **args) 82 | m2 = Meter(wri, 165, 122, legends=("-1", "0", "+1"), **args) 83 | random = xorshift64star(2 ** 24 - 1) 84 | while True: 85 | steps = 10 86 | for n in range(steps + 1): 87 | m0.value(random() / 16777216) 88 | m1.value(n / steps) 89 | m2.value(1 - n / steps) 90 | await evt.wait() 91 | 92 | 93 | async def main(): 94 | # ssd.fill(1) 95 | # ssd.show() 96 | # await ssd.wait() 97 | refresh(ssd, True) # Clear display 98 | await ssd.complete.wait() 99 | print("Ready") 100 | evt = asyncio.Event() 101 | asyncio.create_task(meter(evt)) 102 | asyncio.create_task(multi_fields(evt)) 103 | asyncio.create_task(fields(evt)) 104 | while True: 105 | # Normal procedure before refresh, but 10s sleep should mean it always returns immediately 106 | await ssd.complete.wait() 107 | refresh(ssd) # Launches ._as_show() 108 | await ssd.updated.wait() 109 | # Content has now been shifted out so coros can update 110 | # framebuffer in background 111 | evt.set() 112 | evt.clear() 113 | await asyncio.sleep(9) # Allow for slow refresh 114 | 115 | 116 | tstr = """Runs the following tests, updates every 10s 117 | fields() Label test with dynamic data. 118 | multi_fields() More Labels. 119 | meter() Demo of Meter object. 120 | """ 121 | 122 | print(tstr) 123 | 124 | try: 125 | asyncio.run(main()) 126 | except KeyboardInterrupt: 127 | print("Waiting for display to become idle") 128 | ssd.wait_until_ready() # Synchronous code 129 | finally: 130 | _ = asyncio.new_event_loop() 131 | -------------------------------------------------------------------------------- /gui/demos/mono_test.py: -------------------------------------------------------------------------------- 1 | # mono_test.py Demo program for nano_gui on an SSD1306 OLED display. 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2018-2021 Peter Hinch 5 | 6 | # https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display 7 | # https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html 8 | 9 | # V0.33 16th Jan 2021 Hardware configuration is now defined in color_setup to be 10 | # consistent with other displays. 11 | # V0.32 5th Nov 2020 Replace uos.urandom for minimal ports 12 | 13 | import utime 14 | # import uos 15 | from color_setup import ssd 16 | # On a monochrome display Writer is more efficient than CWriter. 17 | from gui.core.writer import Writer 18 | from gui.core.nanogui import refresh 19 | from gui.widgets.meter import Meter 20 | from gui.widgets.label import Label 21 | 22 | # Fonts 23 | import gui.fonts.arial10 as arial10 24 | import gui.fonts.courier20 as fixed 25 | import gui.fonts.font6 as small 26 | 27 | # Some ports don't support uos.urandom. 28 | # See https://github.com/peterhinch/micropython-samples/tree/master/random 29 | def xorshift64star(modulo, seed = 0xf9ac6ba4): 30 | x = seed 31 | def func(): 32 | nonlocal x 33 | x ^= x >> 12 34 | x ^= ((x << 25) & 0xffffffffffffffff) # modulo 2**64 35 | x ^= x >> 27 36 | return (x * 0x2545F4914F6CDD1D) % modulo 37 | return func 38 | 39 | def fields(): 40 | ssd.fill(0) 41 | refresh(ssd) 42 | Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it 43 | wri = Writer(ssd, fixed, verbose=False) 44 | wri.set_clip(False, False, False) 45 | textfield = Label(wri, 0, 2, wri.stringlen('longer')) 46 | numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bdcolor=None) 47 | countfield = Label(wri, 0, 90, wri.stringlen('1')) 48 | n = 1 49 | random = xorshift64star(65535) 50 | for s in ('short', 'longer', '1', ''): 51 | textfield.value(s) 52 | numfield.value('{:5.2f}'.format(random() /1000)) 53 | countfield.value('{:1d}'.format(n)) 54 | n += 1 55 | refresh(ssd) 56 | utime.sleep(2) 57 | textfield.value('Done', True) 58 | refresh(ssd) 59 | 60 | def multi_fields(): 61 | ssd.fill(0) 62 | refresh(ssd) 63 | Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it 64 | wri = Writer(ssd, small, verbose=False) 65 | wri.set_clip(False, False, False) 66 | 67 | nfields = [] 68 | dy = small.height() + 6 69 | y = 2 70 | col = 15 71 | width = wri.stringlen('99.99') 72 | for txt in ('X:', 'Y:', 'Z:'): 73 | Label(wri, y, 0, txt) 74 | nfields.append(Label(wri, y, col, width, bdcolor=None)) # Draw border 75 | y += dy 76 | 77 | random = xorshift64star(2**24 - 1) 78 | for _ in range(10): 79 | for field in nfields: 80 | value = random() / 167772 81 | field.value('{:5.2f}'.format(value)) 82 | refresh(ssd) 83 | utime.sleep(1) 84 | Label(wri, 0, 64, ' DONE ', True) 85 | refresh(ssd) 86 | 87 | def meter(): 88 | ssd.fill(0) 89 | refresh(ssd) 90 | wri = Writer(ssd, arial10, verbose=False) 91 | m0 = Meter(wri, 5, 2, height = 50, divisions = 4, legends=('0.0', '0.5', '1.0')) 92 | m1 = Meter(wri, 5, 44, height = 50, divisions = 4, legends=('-1', '0', '+1')) 93 | m2 = Meter(wri, 5, 86, height = 50, divisions = 4, legends=('-1', '0', '+1')) 94 | steps = 10 95 | random = xorshift64star(2**24 - 1) 96 | for n in range(steps + 1): 97 | m0.value(random() / 16777216) 98 | m1.value(n/steps) 99 | m2.value(1 - n/steps) 100 | refresh(ssd) 101 | utime.sleep(1) 102 | 103 | 104 | tstr = '''Test assumes a 128*64 (w*h) display. Edit WIDTH and HEIGHT in ssd1306_setup.py for others. 105 | Device pinouts are comments in ssd1306_setup.py. 106 | 107 | Test runs to completion. 108 | ''' 109 | 110 | print(tstr) 111 | print('Basic test of fields.') 112 | fields() 113 | print('More fields.') 114 | multi_fields() 115 | print('Meters.') 116 | meter() 117 | print('Done.') 118 | -------------------------------------------------------------------------------- /gui/demos/round.py: -------------------------------------------------------------------------------- 1 | # round.py Test/demo of scale widget for nano-gui on round gc9a01 screen 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2024 Peter Hinch 5 | 6 | # Usage: 7 | # import gui.demos.round 8 | 9 | # Initialise hardware and framebuf before importing modules. 10 | # Uses asyncio and also the asynchronous do_refresh method if the driver 11 | # supports it. 12 | 13 | from color_setup import ssd # Create a display instance 14 | 15 | from gui.core.nanogui import refresh 16 | from gui.core.writer import CWriter 17 | 18 | import asyncio 19 | from gui.core.colors import * 20 | import gui.fonts.arial10 as arial10 21 | from gui.widgets.label import Label 22 | from gui.widgets.scale import Scale 23 | 24 | # COROUTINES 25 | async def radio(scale): 26 | cv = 88.0 # Current value 27 | val = 108.0 # Target value 28 | while True: 29 | v1, v2 = val, cv 30 | steps = 200 31 | delta = (val - cv) / steps 32 | for _ in range(steps): 33 | cv += delta 34 | # Map user variable to -1.0..+1.0 35 | scale.value(2 * (cv - 88) / (108 - 88) - 1) 36 | await asyncio.sleep_ms(200) 37 | val, cv = v2, v1 38 | 39 | 40 | async def default(scale, lbl): 41 | cv = -1.0 # Current 42 | val = 1.0 43 | while True: 44 | v1, v2 = val, cv 45 | steps = 400 46 | delta = (val - cv) / steps 47 | for _ in range(steps): 48 | cv += delta 49 | scale.value(cv) 50 | lbl.value("{:4.3f}".format(cv)) 51 | if hasattr(ssd, "do_refresh"): 52 | # Option to reduce asyncio latency 53 | await ssd.do_refresh() 54 | else: 55 | # Normal synchronous call 56 | refresh(ssd) 57 | await asyncio.sleep_ms(250) 58 | val, cv = v2, v1 59 | 60 | 61 | def test(): 62 | def tickcb(f, c): 63 | if f > 0.8: 64 | return RED 65 | if f < -0.8: 66 | return BLUE 67 | return c 68 | 69 | def legendcb(f): 70 | return "{:2.0f}".format(88 + ((f + 1) / 2) * (108 - 88)) 71 | 72 | refresh(ssd, True) # Initialise and clear display. 73 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 74 | wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) 75 | wri.set_clip(True, True, False) 76 | scale1 = Scale(wri, 64, 64, width=124, legendcb=legendcb, pointercolor=RED, fontcolor=YELLOW) 77 | asyncio.create_task(radio(scale1)) 78 | 79 | lbl = Label(wri, 180, 64, 50, bgcolor=DARKGREEN, bdcolor=RED, fgcolor=WHITE) 80 | # do_refresh is called with arg 4. In landscape mode this splits screen 81 | # into segments of 240/4=60 lines. Here we ensure a scale straddles 82 | # this boundary 83 | scale = Scale( 84 | wri, 140, 64, width=124, tickcb=tickcb, pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN 85 | ) 86 | asyncio.run(default(scale, lbl)) 87 | 88 | 89 | test() 90 | -------------------------------------------------------------------------------- /gui/demos/scale.py: -------------------------------------------------------------------------------- 1 | # scale.py Test/demo of scale widget for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020-2021 Peter Hinch 5 | 6 | # Usage: 7 | # import gui.demos.scale 8 | 9 | # Initialise hardware and framebuf before importing modules. 10 | # Uses asyncio and also the asynchronous do_refresh method if the driver 11 | # supports it. 12 | 13 | from color_setup import ssd # Create a display instance 14 | 15 | from gui.core.nanogui import refresh 16 | from gui.core.writer import CWriter 17 | 18 | import asyncio 19 | from gui.core.colors import * 20 | import gui.fonts.arial10 as arial10 21 | from gui.widgets.label import Label 22 | from gui.widgets.scale import Scale 23 | 24 | # COROUTINES 25 | async def radio(scale): 26 | cv = 88.0 # Current value 27 | val = 108.0 # Target value 28 | while True: 29 | v1, v2 = val, cv 30 | steps = 200 31 | delta = (val - cv) / steps 32 | for _ in range(steps): 33 | cv += delta 34 | # Map user variable to -1.0..+1.0 35 | scale.value(2 * (cv - 88) / (108 - 88) - 1) 36 | await asyncio.sleep_ms(200) 37 | val, cv = v2, v1 38 | 39 | 40 | async def default(scale, lbl): 41 | cv = -1.0 # Current 42 | val = 1.0 43 | while True: 44 | v1, v2 = val, cv 45 | steps = 400 46 | delta = (val - cv) / steps 47 | for _ in range(steps): 48 | cv += delta 49 | scale.value(cv) 50 | lbl.value("{:4.3f}".format(cv)) 51 | if hasattr(ssd, "do_refresh"): 52 | # Option to reduce asyncio latency 53 | await ssd.do_refresh() 54 | else: 55 | # Normal synchronous call 56 | refresh(ssd) 57 | await asyncio.sleep_ms(250) 58 | val, cv = v2, v1 59 | 60 | 61 | def test(): 62 | def tickcb(f, c): 63 | if f > 0.8: 64 | return RED 65 | if f < -0.8: 66 | return BLUE 67 | return c 68 | 69 | def legendcb(f): 70 | return "{:2.0f}".format(88 + ((f + 1) / 2) * (108 - 88)) 71 | 72 | refresh(ssd, True) # Initialise and clear display. 73 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 74 | wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) 75 | wri.set_clip(True, True, False) 76 | scale1 = Scale(wri, 2, 2, width=124, legendcb=legendcb, pointercolor=RED, fontcolor=YELLOW) 77 | asyncio.create_task(radio(scale1)) 78 | 79 | lbl = Label( 80 | wri, ssd.height - wri.height - 2, 2, 50, bgcolor=DARKGREEN, bdcolor=RED, fgcolor=WHITE 81 | ) 82 | # do_refresh is called with arg 4. In landscape mode this splits screen 83 | # into segments of 240/4=60 lines. Here we ensure a scale straddles 84 | # this boundary 85 | scale = Scale( 86 | wri, 55, 2, width=124, tickcb=tickcb, pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN 87 | ) 88 | asyncio.run(default(scale, lbl)) 89 | 90 | 91 | test() 92 | -------------------------------------------------------------------------------- /gui/demos/sharptest.py: -------------------------------------------------------------------------------- 1 | # sharptest.py Test script for monochrome sharp displays 2 | # Tested on 3 | # https://www.adafruit.com/product/4694 2.7 inch 400x240 Monochrome 4 | 5 | # Copyright (c) Peter Hinch 2020 6 | # Released under the MIT license see LICENSE 7 | 8 | # WIRING 9 | # Pyb SSD 10 | # Vin Vin Pyboard: Vin is an output when powered by USB 11 | # Gnd Gnd 12 | # Y8 DI 13 | # Y6 CLK 14 | # Y5 CS 15 | 16 | from color_setup import ssd # Create a display instance 17 | # Fonts for Writer 18 | import gui.fonts.freesans20 as freesans20 19 | import gui.fonts.arial_50 as arial_50 20 | 21 | from gui.core.writer import Writer 22 | import time 23 | 24 | def test(): 25 | rhs = ssd.width -1 26 | ssd.line(rhs - 80, 0, rhs, 80, 1) 27 | square_side = 40 28 | ssd.fill_rect(rhs - square_side, 0, square_side, square_side, 1) 29 | 30 | wri = Writer(ssd, freesans20) 31 | Writer.set_textpos(ssd, 0, 0) # verbose = False to suppress console output 32 | wri.printstring('Sunday\n') 33 | wri.printstring('12 Aug 2018\n') 34 | wri.printstring('10.30am') 35 | 36 | wri = Writer(ssd, arial_50) 37 | Writer.set_textpos(ssd, 0, 120) 38 | wri.printstring('10:30') 39 | ssd.show() 40 | 41 | test() 42 | -------------------------------------------------------------------------------- /gui/demos/tbox.py: -------------------------------------------------------------------------------- 1 | # tbox.py Test/demo of Textbox widget for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # Usage: 7 | # import gui.demos.tbox 8 | 9 | # Initialise hardware and framebuf before importing modules. 10 | from color_setup import ssd # Create a display instance 11 | 12 | from gui.core.nanogui import refresh 13 | from gui.core.writer import CWriter 14 | 15 | import asyncio 16 | from gui.core.colors import * 17 | import gui.fonts.arial10 as arial10 18 | from gui.widgets.label import Label 19 | from gui.widgets.textbox import Textbox 20 | 21 | # Args common to both Textbox instances 22 | # Positional 23 | pargs = (2, 2, 124, 7) # Row, Col, Width, nlines 24 | 25 | # Keyword 26 | tbargs = { 27 | "fgcolor": YELLOW, 28 | "bdcolor": RED, 29 | "bgcolor": DARKGREEN, 30 | } 31 | 32 | 33 | async def wrap(wri): 34 | s = """The textbox displays multiple lines of text in a field of fixed dimensions. \ 35 | Text may be clipped to the width of the control or may be word-wrapped. If the number \ 36 | of lines of text exceeds the height available, scrolling may be performed \ 37 | by calling a method. 38 | """ 39 | tb = Textbox(wri, *pargs, clip=False, **tbargs) 40 | tb.append(s, ntrim=100, line=0) 41 | refresh(ssd) 42 | while True: 43 | await asyncio.sleep(1) 44 | if not tb.scroll(1): 45 | break 46 | refresh(ssd) 47 | 48 | 49 | async def clip(wri): 50 | ss = ( 51 | "clip demo", 52 | "short", 53 | "longer line", 54 | "much longer line with spaces", 55 | "antidisestablishmentarianism", 56 | "line with\nline break", 57 | "Done", 58 | ) 59 | tb = Textbox(wri, *pargs, clip=True, **tbargs) 60 | for s in ss: 61 | tb.append(s, ntrim=100) # Default line=None scrolls to show most recent 62 | refresh(ssd) 63 | await asyncio.sleep(1) 64 | 65 | 66 | async def main(wri): 67 | await wrap(wri) 68 | await clip(wri) 69 | 70 | 71 | def test(): 72 | refresh(ssd, True) # Initialise and clear display. 73 | CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it 74 | wri = CWriter(ssd, arial10, verbose=False) 75 | wri.set_clip(True, True, False) 76 | asyncio.run(main(wri)) 77 | 78 | 79 | test() 80 | -------------------------------------------------------------------------------- /gui/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/gui/widgets/__init__.py -------------------------------------------------------------------------------- /gui/widgets/dial.py: -------------------------------------------------------------------------------- 1 | # dial.py Dial and Pointer classes for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2018-2020 Peter Hinch 5 | 6 | import cmath 7 | from gui.core.nanogui import DObject, circle, fillcircle 8 | from gui.widgets.label import Label 9 | 10 | # Line defined by polar coords; origin and line are complex 11 | def polar(dev, origin, line, color): 12 | xs, ys = origin.real, origin.imag 13 | theta = cmath.polar(line)[1] 14 | dev.line(round(xs), round(ys), round(xs + line.real), round(ys - line.imag), color) 15 | 16 | def conj(v): # complex conjugate 17 | return v.real - v.imag * 1j 18 | 19 | # Draw an arrow; origin and vec are complex, scalar lc defines length of chevron. 20 | # cw and ccw are unit vectors of +-3pi/4 radians for chevrons (precompiled) 21 | def arrow(dev, origin, vec, lc, color, ccw=cmath.exp(3j * cmath.pi/4), cw=cmath.exp(-3j * cmath.pi/4)): 22 | length, theta = cmath.polar(vec) 23 | uv = cmath.rect(1, theta) # Unit rotation vector 24 | start = -vec 25 | if length > 3 * lc: # If line is long 26 | ds = cmath.rect(lc, theta) 27 | start += ds # shorten to allow for length of tail chevrons 28 | chev = lc + 0j 29 | polar(dev, origin, vec, color) # Origin to tip 30 | polar(dev, origin, start, color) # Origin to tail 31 | polar(dev, origin + conj(vec), chev*ccw*uv, color) # Tip chevron 32 | polar(dev, origin + conj(vec), chev*cw*uv, color) 33 | if length > lc: # Confusing appearance of very short vectors with tail chevron 34 | polar(dev, origin + conj(start), chev*ccw*uv, color) # Tail chevron 35 | polar(dev, origin + conj(start), chev*cw*uv, color) 36 | 37 | 38 | class Pointer(): 39 | def __init__(self, dial): 40 | self.dial = dial 41 | self.val = 0 + 0j 42 | self.color = None 43 | 44 | def value(self, v=None, color=None): 45 | self.color = color 46 | if v is not None: 47 | if isinstance(v, complex): 48 | l = cmath.polar(v)[0] 49 | if l > 1: 50 | self.val = v/l 51 | else: 52 | self.val = v 53 | else: 54 | raise ValueError('Pointer value must be complex.') 55 | self.dial.vectors.add(self) 56 | self.dial._set_pend(self.dial) # avoid redrawing for each vector 57 | return self.val 58 | 59 | class Dial(DObject): 60 | CLOCK = 0 61 | COMPASS = 1 62 | def __init__(self, writer, row, col, *, height=50, 63 | fgcolor=None, bgcolor=None, bdcolor=False, ticks=4, 64 | label=None, style=0, pip=None): 65 | super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor) 66 | self.style = style 67 | self.pip = self.fgcolor if pip is None else pip 68 | if label is not None: 69 | self.label = Label(writer, row + height + 3, col, label) 70 | radius = int(height / 2) 71 | self.radius = radius 72 | self.ticks = ticks 73 | self.xorigin = col + radius 74 | self.yorigin = row + radius 75 | self.vectors = set() 76 | 77 | def show(self): 78 | super().show() 79 | # cache bound variables 80 | dev = self.device 81 | ticks = self.ticks 82 | radius = self.radius 83 | xo = self.xorigin 84 | yo = self.yorigin 85 | # vectors (complex) 86 | vor = xo + 1j * yo 87 | vtstart = 0.9 * radius + 0j # start of tick 88 | vtick = 0.1 * radius + 0j # tick 89 | vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation 90 | for _ in range(ticks): 91 | polar(dev, vor + conj(vtstart), vtick, self.fgcolor) 92 | vtick *= vrot 93 | vtstart *= vrot 94 | circle(dev, xo, yo, radius, self.fgcolor) 95 | vshort = 1000 # Length of shortest vector 96 | for v in self.vectors: 97 | color = self.fgcolor if v.color is None else v.color 98 | val = v.value() * radius # val is complex 99 | vshort = min(vshort, cmath.polar(val)[0]) 100 | if self.style == Dial.CLOCK: 101 | polar(dev, vor, val, color) 102 | else: 103 | arrow(dev, vor, val, 5, color) 104 | if isinstance(self.pip, int) and vshort > 5: 105 | fillcircle(dev, xo, yo, 2, self.pip) 106 | 107 | -------------------------------------------------------------------------------- /gui/widgets/label.py: -------------------------------------------------------------------------------- 1 | # label.py Label class for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2018-2022 Peter Hinch 5 | 6 | from micropython import const 7 | from gui.core.nanogui import DObject 8 | from gui.core.writer import Writer 9 | 10 | ALIGN_LEFT = const(0) 11 | ALIGN_RIGHT = const(1) 12 | ALIGN_CENTER = const(2) 13 | 14 | # text: str display string int save width 15 | class Label(DObject): 16 | def __init__(self, writer, row, col, text, invert=False, fgcolor=None, bgcolor=None, bdcolor=False, align=ALIGN_LEFT): 17 | # Determine width of object 18 | if isinstance(text, int): 19 | width = text 20 | text = None 21 | else: 22 | width = writer.stringlen(text) 23 | height = writer.height 24 | super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor) 25 | self.align = align 26 | if text is not None: 27 | self.value(text, invert) 28 | 29 | def value(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None, align=None): 30 | txt = super().value(text) 31 | # Redraw even if no text supplied: colors may have changed. 32 | self.invert = invert 33 | self.fgcolor = self.def_fgcolor if fgcolor is None else fgcolor 34 | self.bgcolor = self.def_bgcolor if bgcolor is None else bgcolor 35 | if bdcolor is False: 36 | self.def_bdcolor = False 37 | self.bdcolor = self.def_bdcolor if bdcolor is None else bdcolor 38 | if align is not None: 39 | self.align = align 40 | self.show() 41 | return txt 42 | 43 | def show(self): 44 | txt = super().value() 45 | if txt is None: # No content to draw. Future use. 46 | return 47 | super().show() # Draw or erase border 48 | wri = self.writer 49 | dev = self.device 50 | rcol = 0 # Relative column of LHS of text 51 | if self.align: 52 | txt_width = wri.stringlen(txt) 53 | if self.width > txt_width: 54 | rcol = self.width - txt_width if self.align == ALIGN_RIGHT else self.width // 2 - txt_width // 2 55 | Writer.set_textpos(dev, self.row, self.col + rcol) 56 | wri.setcolor(self.fgcolor, self.bgcolor) 57 | wri.printstring(txt, self.invert) 58 | wri.setcolor() # Restore defaults 59 | -------------------------------------------------------------------------------- /gui/widgets/led.py: -------------------------------------------------------------------------------- 1 | # led.py LED class for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2018-2020 Peter Hinch 5 | 6 | from gui.core.nanogui import DObject, fillcircle, circle 7 | from gui.widgets.label import Label 8 | 9 | class LED(DObject): 10 | def __init__(self, writer, row, col, *, height=12, 11 | fgcolor=None, bgcolor=None, bdcolor=None, label=None): 12 | super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor) 13 | if label is not None: 14 | self.label = Label(writer, row + height + 3, col, label) 15 | self.radius = self.height // 2 16 | 17 | def color(self, c=None): 18 | self.fgcolor = self.bgcolor if c is None else c 19 | self.show() 20 | 21 | def show(self): 22 | super().show() 23 | wri = self.writer 24 | dev = self.device 25 | r = self.radius 26 | fillcircle(dev, self.col + r, self.row + r, r, self.fgcolor) 27 | if isinstance(self.bdcolor, int): 28 | circle(dev, self.col + r, self.row + r, r, self.bdcolor) 29 | -------------------------------------------------------------------------------- /gui/widgets/meter.py: -------------------------------------------------------------------------------- 1 | # meter.py Meter class for nano-gui 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2018-2020 Peter Hinch 5 | 6 | from gui.core.nanogui import DObject 7 | from gui.widgets.label import Label 8 | 9 | 10 | class Meter(DObject): 11 | BAR = 1 12 | LINE = 0 13 | def __init__(self, writer, row, col, *, height=50, width=10, 14 | fgcolor=None, bgcolor=None, ptcolor=None, bdcolor=None, 15 | divisions=5, label=None, style=0, legends=None, value=None): 16 | super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor) 17 | self.divisions = divisions 18 | if label is not None: 19 | Label(writer, row + height + 3, col, label) 20 | self.style = style 21 | if legends is not None: # Legends 22 | x = col + width + 4 23 | y = row + height 24 | dy = 0 if len(legends) <= 1 else height / (len(legends) -1) 25 | yl = y - writer.height / 2 # Start at bottom 26 | for legend in legends: 27 | Label(writer, int(yl), x, legend) 28 | yl -= dy 29 | self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor 30 | self.value(value) 31 | 32 | def value(self, n=None, color=None): 33 | if n is None: 34 | return super().value() 35 | n = super().value(min(1, max(0, n))) 36 | if color is not None: 37 | self.ptcolor = color 38 | self.show() 39 | return n 40 | 41 | def show(self): 42 | super().show() # Draw or erase border 43 | val = super().value() 44 | wri = self.writer 45 | dev = self.device 46 | width = self.width 47 | height = self.height 48 | x0 = self.col 49 | x1 = self.col + width 50 | y0 = self.row 51 | y1 = self.row + height 52 | if self.divisions > 0: 53 | dy = height / (self.divisions) # Tick marks 54 | for tick in range(self.divisions + 1): 55 | ypos = int(y0 + dy * tick) 56 | dev.hline(x0 + 2, ypos, x1 - x0 - 4, self.fgcolor) 57 | 58 | y = int(y1 - val * height) # y position of slider 59 | if self.style == self.LINE: 60 | dev.hline(x0, y, width, self.ptcolor) # Draw pointer 61 | else: 62 | w = width / 2 63 | dev.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor) 64 | -------------------------------------------------------------------------------- /gui/widgets/scale.py: -------------------------------------------------------------------------------- 1 | # scale.py Extension to nano-gui providing the Scale class 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # Usage: 7 | # from gui.widgets.scale import Scale 8 | 9 | from gui.core.nanogui import DObject 10 | from gui.core.writer import Writer 11 | from gui.core.colors import BLACK 12 | 13 | class Scale(DObject): 14 | def __init__(self, writer, row, col, *, 15 | ticks=200, legendcb=None, tickcb=None, 16 | height=0, width=100, bdcolor=None, fgcolor=None, bgcolor=None, 17 | pointercolor=None, fontcolor=None): 18 | if ticks % 2: 19 | raise ValueError('ticks arg must be divisible by 2') 20 | self.ticks = ticks 21 | self.tickcb = tickcb 22 | def lcb(f): 23 | return '{:3.1f}'.format(f) 24 | self.legendcb = legendcb if legendcb is not None else lcb 25 | bgcolor = BLACK if bgcolor is None else bgcolor 26 | text_ht = writer.font.height() 27 | ctrl_ht = 12 # Minimum height for ticks 28 | # Add 2 pixel internal border to give a little more space 29 | min_ht = text_ht + 6 # Ht of text, borders and gap between text and ticks 30 | if height < min_ht + ctrl_ht: 31 | height = min_ht + ctrl_ht # min workable height 32 | else: 33 | ctrl_ht = height - min_ht # adjust ticks for greater height 34 | width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset 35 | super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor) 36 | self.fontcolor = fontcolor if fontcolor is not None else self.fgcolor 37 | self.x0 = col + 2 38 | self.x1 = col + self.width - 2 39 | self.y0 = row + 2 40 | self.y1 = row + self.height - 2 41 | self.ptrcolor = pointercolor if pointercolor is not None else self.fgcolor 42 | # Define tick dimensions 43 | ytop = self.y0 + text_ht + 2 # Top of scale graphic (2 pixel gap) 44 | ycl = ytop + (self.y1 - ytop) // 2 # Centre line 45 | self.sdl = round(ctrl_ht * 1 / 3) # Length of small tick. 46 | self.sdy0 = ycl - self.sdl // 2 47 | self.mdl = round(ctrl_ht * 2 / 3) # Medium tick 48 | self.mdy0 = ycl - self.mdl // 2 49 | self.ldl = ctrl_ht # Large tick 50 | self.ldy0 = ycl - self.ldl // 2 51 | 52 | def show(self): 53 | wri = self.writer 54 | dev = self.device 55 | x0: int = self.x0 # Internal rectangle occupied by scale and text 56 | x1: int = self.x1 57 | y0: int = self.y0 58 | y1: int = self.y1 59 | dev.fill_rect(x0, y0, x1 - x0, y1 - y0, self.bgcolor) 60 | super().show() 61 | # Scale is drawn using ints. Each division is 10 units. 62 | val: int = self._value # 0..ticks*10 63 | # iv increments for each tick. Its value modulo N determines tick length 64 | iv: int # val / 10 at a tick position 65 | d: int # val % 10: offset relative to a tick position 66 | fx: int # X offset of current tick in value units 67 | if val >= 100: # Whole LHS of scale will be drawn 68 | iv, d = divmod(val - 100, 10) # Initial value 69 | fx = 10 - d 70 | iv += 1 71 | else: # Scale will scroll right 72 | iv = 0 73 | fx = 100 - val 74 | 75 | # Window shows 20 divisions, each of which corresponds to 10 units of value. 76 | # So pixels per unit value == win_width/200 77 | win_width: int = x1 - x0 78 | ticks: int = self.ticks # Total # of ticks visible and hidden 79 | while True: 80 | x: int = x0 + (fx * win_width) // 200 # Current X position 81 | ys: int # Start Y position for tick 82 | yl: int # tick length 83 | if x > x1 or iv > ticks: # Out of space or data (scroll left) 84 | break 85 | if not iv % 10: 86 | txt = self.legendcb(self._fvalue(iv * 10)) 87 | tlen = wri.stringlen(txt) 88 | Writer.set_textpos(dev, y0, min(x, x1 - tlen)) 89 | wri.setcolor(self.fontcolor, self.bgcolor) 90 | wri.printstring(txt) 91 | wri.setcolor() 92 | ys = self.ldy0 # Large tick 93 | yl = self.ldl 94 | elif not iv % 5: 95 | ys = self.mdy0 96 | yl = self.mdl 97 | else: 98 | ys = self.sdy0 99 | yl = self.sdl 100 | if self.tickcb is None: 101 | color = self.fgcolor 102 | else: 103 | color = self.tickcb(self._fvalue(iv * 10), self.fgcolor) 104 | dev.vline(x, ys, yl, color) # Draw tick 105 | fx += 10 106 | iv += 1 107 | 108 | dev.vline(x0 + (x1 - x0) // 2, y0, y1 - y0, self.ptrcolor) # Draw pointer 109 | 110 | def _to_int(self, v): 111 | return round((v + 1.0) * self.ticks * 5) # 0..self.ticks*10 112 | 113 | def _fvalue(self, v=None): 114 | return v / (5 * self.ticks) - 1.0 115 | 116 | def value(self, val=None): # User method to get or set value 117 | if val is not None: 118 | val = min(max(val, - 1.0), 1.0) 119 | v = self._to_int(val) 120 | if v != self._value: 121 | self._value = v 122 | self.show() 123 | return self._fvalue(self._value) 124 | -------------------------------------------------------------------------------- /gui/widgets/textbox.py: -------------------------------------------------------------------------------- 1 | # textbox.py Extension to nanogui providing the Textbox class 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # Usage: 7 | # from gui.widgets.textbox import Textbox 8 | 9 | from gui.core.nanogui import DObject 10 | from gui.core.writer import Writer 11 | 12 | # Reason for no tab support in private/reason_for_no_tabs 13 | 14 | class Textbox(DObject): 15 | def __init__(self, writer, row, col, width, nlines, *, bdcolor=None, fgcolor=None, 16 | bgcolor=None, clip=True): 17 | height = nlines * writer.height 18 | devht = writer.device.height 19 | devwd = writer.device.width 20 | if ((row + height + 2) > devht) or ((col + width + 2) > devwd): 21 | raise ValueError('Textbox extends beyond physical screen.') 22 | super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor) 23 | self.nlines = nlines 24 | self.clip = clip 25 | self.lines = [] 26 | self.start = 0 # Start line for display 27 | 28 | def _add_lines(self, s): 29 | width = self.width 30 | font = self.writer.font 31 | n = -1 # Index into string 32 | newline = True 33 | while True: 34 | n += 1 35 | if newline: 36 | newline = False 37 | ls = n # Start of line being processed 38 | col = 0 # Column relative to text area 39 | if n >= len(s): # End of string 40 | if n > ls: 41 | self.lines.append(s[ls :]) 42 | return 43 | c = s[n] # Current char 44 | if c == '\n': 45 | self.lines.append(s[ls : n]) 46 | newline = True 47 | continue # Line fits window 48 | col += font.get_ch(c)[2] # width of current char 49 | if col > width: 50 | if self.clip: 51 | p = s[ls :].find('\n') # end of 1st line 52 | if p == -1: 53 | self.lines.append(s[ls : n]) # clip, discard all to right 54 | return 55 | self.lines.append(s[ls : n]) # clip, discard to 1st newline 56 | n = p # n will move to 1st char after newline 57 | elif c == ' ': # Easy word wrap 58 | self.lines.append(s[ls : n]) 59 | else: # Edge splits a word 60 | p = s.rfind(' ', ls, n + 1) 61 | if p >= 0: # spacechar in line: wrap at space 62 | assert (p > 0), 'space char in position 0' 63 | self.lines.append(s[ls : p]) 64 | n = p 65 | else: # No spacechar: wrap at end 66 | self.lines.append(s[ls : n]) 67 | n -= 1 # Don't skip current char 68 | newline = True 69 | 70 | def _print_lines(self): 71 | if len(self.lines) == 0: 72 | return 73 | 74 | dev = self.device 75 | wri = self.writer 76 | col = self.col 77 | row = self.row 78 | left = col 79 | ht = wri.height 80 | wri.setcolor(self.fgcolor, self.bgcolor) 81 | # Print the first (or last?) lines that fit widget's height 82 | #for line in self.lines[-self.nlines : ]: 83 | for line in self.lines[self.start : self.start + self.nlines]: 84 | Writer.set_textpos(dev, row, col) 85 | wri.printstring(line) 86 | row += ht 87 | col = left 88 | wri.setcolor() # Restore defaults 89 | 90 | def show(self): 91 | dev = self.device 92 | super().show() 93 | self._print_lines() 94 | 95 | def append(self, s, ntrim=None, line=None): 96 | self._add_lines(s) 97 | if ntrim is None: # Default to no. of lines that can fit 98 | ntrim = self.nlines 99 | if len(self.lines) > ntrim: 100 | self.lines = self.lines[-ntrim:] 101 | self.goto(line) 102 | 103 | def scroll(self, n): # Relative scrolling 104 | value = len(self.lines) 105 | if n == 0 or value <= self.nlines: # Nothing to do 106 | return False 107 | s = self.start 108 | self.start = max(0, min(self.start + n, value - self.nlines)) 109 | if s != self.start: 110 | self.show() 111 | return True 112 | return False 113 | 114 | def value(self): 115 | return len(self.lines) 116 | 117 | def clear(self): 118 | self.lines = [] 119 | self.show() 120 | 121 | def goto(self, line=None): # Absolute scrolling 122 | if line is None: 123 | self.start = max(0, len(self.lines) - self.nlines) 124 | else: 125 | self.start = max(0, min(line, len(self.lines) - self.nlines)) 126 | self.show() 127 | -------------------------------------------------------------------------------- /images/IMG_2885.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/IMG_2885.png -------------------------------------------------------------------------------- /images/IMG_2887.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/IMG_2887.png -------------------------------------------------------------------------------- /images/V1_EPD.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/V1_EPD.JPG -------------------------------------------------------------------------------- /images/V2_EPD.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/V2_EPD.JPG -------------------------------------------------------------------------------- /images/alevel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/alevel.png -------------------------------------------------------------------------------- /images/bernoulli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/bernoulli.png -------------------------------------------------------------------------------- /images/cartesian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/cartesian.png -------------------------------------------------------------------------------- /images/cat_color.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/cat_color.JPG -------------------------------------------------------------------------------- /images/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/clock.png -------------------------------------------------------------------------------- /images/deer_mono.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/deer_mono.JPG -------------------------------------------------------------------------------- /images/discont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/discont.png -------------------------------------------------------------------------------- /images/epd_enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/epd_enable.png -------------------------------------------------------------------------------- /images/esp8266_tree.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/esp8266_tree.JPG -------------------------------------------------------------------------------- /images/fonts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/fonts.png -------------------------------------------------------------------------------- /images/lissajous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/lissajous.png -------------------------------------------------------------------------------- /images/me_color.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/me_color.JPG -------------------------------------------------------------------------------- /images/meters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/meters.png -------------------------------------------------------------------------------- /images/partial.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/partial.JPG -------------------------------------------------------------------------------- /images/polar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/polar.png -------------------------------------------------------------------------------- /images/round.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/round.JPG -------------------------------------------------------------------------------- /images/rtpolar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/rtpolar.png -------------------------------------------------------------------------------- /images/scale.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/scale.JPG -------------------------------------------------------------------------------- /images/seismo.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/seismo.JPG -------------------------------------------------------------------------------- /images/sine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/sine.png -------------------------------------------------------------------------------- /images/textbox1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-nano-gui/4cf1c6b0bb11170f2e20129529b440f95ab5d033/images/textbox1.JPG -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["gui/core/__init__.py", "github:peterhinch/micropython-nano-gui/gui/core/__init__.py"], 4 | ["gui/core/colors.py", "github:peterhinch/micropython-nano-gui/gui/core/colors.py"], 5 | ["gui/core/fplot.py", "github:peterhinch/micropython-nano-gui/gui/core/fplot.py"], 6 | ["gui/core/nanogui.py", "github:peterhinch/micropython-nano-gui/gui/core/nanogui.py"], 7 | ["gui/core/writer.py", "github:peterhinch/micropython-nano-gui/gui/core/writer.py"], 8 | ["gui/fonts/arial_50.py", "github:peterhinch/micropython-nano-gui/gui/fonts/arial_50.py"], 9 | ["gui/fonts/arial10.py", "github:peterhinch/micropython-nano-gui/gui/fonts/arial10.py"], 10 | ["gui/fonts/arial35.py", "github:peterhinch/micropython-nano-gui/gui/fonts/arial35.py"], 11 | ["gui/fonts/courier20.py", "github:peterhinch/micropython-nano-gui/gui/fonts/courier20.py"], 12 | ["gui/fonts/font6.py", "github:peterhinch/micropython-nano-gui/gui/fonts/font6.py"], 13 | ["gui/fonts/font10.py", "github:peterhinch/micropython-nano-gui/gui/fonts/font10.py"], 14 | ["gui/fonts/freesans20.py", "github:peterhinch/micropython-nano-gui/gui/fonts/freesans20.py"], 15 | ["gui/widgets/__init__.py", "github:peterhinch/micropython-nano-gui/gui/widgets/__init__.py"], 16 | ["gui/widgets/dial.py", "github:peterhinch/micropython-nano-gui/gui/widgets/dial.py"], 17 | ["gui/widgets/label.py", "github:peterhinch/micropython-nano-gui/gui/widgets/label.py"], 18 | ["gui/widgets/led.py", "github:peterhinch/micropython-nano-gui/gui/widgets/led.py"], 19 | ["gui/widgets/meter.py", "github:peterhinch/micropython-nano-gui/gui/widgets/meter.py"], 20 | ["gui/widgets/scale.py", "github:peterhinch/micropython-nano-gui/gui/widgets/scale.py"], 21 | ["gui/widgets/textbox.py", "github:peterhinch/micropython-nano-gui/gui/widgets/textbox.py"], 22 | ["gui/widgets/__init__.py", "github:peterhinch/micropython-nano-gui/gui/widgets/__init__.py"], 23 | ["gui/demos/aclock_large.py", "github:peterhinch/micropython-nano-gui/gui/demos/aclock_large.py"], 24 | ["gui/demos/aclock.py", "github:peterhinch/micropython-nano-gui/gui/demos/aclock.py"], 25 | ["gui/demos/aclock_ttgo.py", "github:peterhinch/micropython-nano-gui/gui/demos/aclock_ttgo.py"], 26 | ["gui/demos/alevel.py", "github:peterhinch/micropython-nano-gui/gui/demos/alevel.py"], 27 | ["gui/demos/asnano.py", "github:peterhinch/micropython-nano-gui/gui/demos/asnano.py"], 28 | ["gui/demos/asnano_sync.py", "github:peterhinch/micropython-nano-gui/gui/demos/asnano_sync.py"], 29 | ["gui/demos/clock_batt.py", "github:peterhinch/micropython-nano-gui/gui/demos/clock_batt.py"], 30 | ["gui/demos/clocktest.py", "github:peterhinch/micropython-nano-gui/gui/demos/clocktest.py"], 31 | ["gui/demos/color15.py", "github:peterhinch/micropython-nano-gui/gui/demos/color15.py"], 32 | ["gui/demos/color96.py", "github:peterhinch/micropython-nano-gui/gui/demos/color96.py"], 33 | ["gui/demos/epd29_async.py", "github:peterhinch/micropython-nano-gui/gui/demos/epd29_async.py"], 34 | ["gui/demos/epd29_lowpower.py", "github:peterhinch/micropython-nano-gui/gui/demos/epd29_lowpower.py"], 35 | ["gui/demos/epd29_sync.py", "github:peterhinch/micropython-nano-gui/gui/demos/epd29_sync.py"], 36 | ["gui/demos/epd_async.py", "github:peterhinch/micropython-nano-gui/gui/demos/epd_async.py"], 37 | ["gui/demos/fpt.py", "github:peterhinch/micropython-nano-gui/gui/demos/fpt.py"], 38 | ["gui/demos/mono_test.py", "github:peterhinch/micropython-nano-gui/gui/demos/mono_test.py"], 39 | ["gui/demos/scale.py", "github:peterhinch/micropython-nano-gui/gui/demos/scale.py"], 40 | ["gui/demos/sharptest.py", "github:peterhinch/micropython-nano-gui/gui/demos/sharptest.py"], 41 | ["gui/demos/tbox.py", "github:peterhinch/micropython-nano-gui/gui/demos/tbox.py"] 42 | ], 43 | "version": "0.1" 44 | } 45 | -------------------------------------------------------------------------------- /setup_examples/epd29_pico.py: -------------------------------------------------------------------------------- 1 | # epd29_pico.py Config for Pico with 2.9" ePaper. 2 | # Customise for your hardware config. 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2023 Peter Hinch 6 | 7 | # Supports Adafruit 2.9" monochrome EPD with interface board connected to Pyboard. 8 | # Interface breakout: https://www.adafruit.com/product/4224 9 | # Display: https://www.adafruit.com/product/4262 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING. Adafruit schematic linked on the product web pagerefers to a different 16 | # device. These are the pins on the physical board. 17 | # Pico Breakout 18 | # Vbus Vin (1) 19 | # Gnd Gnd (3) 20 | # 4 MISO (no need to connect) 21 | # 6 SCK (4) 22 | # 7 MOSI (6) 23 | # 8 DC (8) 24 | # 9 RST (10) 25 | # 10 CS (7) 26 | # 11 BUSY (11) (Low = Busy) 27 | 28 | 29 | from machine import Pin, SPI 30 | import gc 31 | 32 | from drivers.epaper.epd29 import EPD as SSD 33 | 34 | pdc = Pin(8, Pin.OUT, value=0) 35 | prst = Pin(9, Pin.OUT, value=1) 36 | pcs = Pin(10, Pin.OUT, value=1) 37 | pbusy = Pin(11, Pin.IN) 38 | 39 | # Baudrate. Adafruit use 1MHz at 40 | # https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2 41 | # Datasheet P35 indicates up to 10MHz. 42 | spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=5_000_000) 43 | gc.collect() # Precaution before instantiating framebuf 44 | ssd = SSD(spi, pcs, pdc, prst, pbusy) # Create a display instance 45 | -------------------------------------------------------------------------------- /setup_examples/epd29_pyb_async.py: -------------------------------------------------------------------------------- 1 | # epd29_async.py Config for asynchronous applications on 2.9" ePaper. 2 | # Customise for your hardware config. 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2020 Peter Hinch 6 | 7 | # Supports Adafruit 2.9" monochrome EPD with interface board connected to Pyboard. 8 | # Interface breakout: https://www.adafruit.com/product/4224 9 | # Display: https://www.adafruit.com/product/4262 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING. Adafruit schematic linked on the product web pagerefers to a different 16 | # device. These are the pins on the physical board. 17 | # Pyb Breakout 18 | # Vin Vin (1) 19 | # Gnd Gnd (3) 20 | # Y8 MOSI (6) 21 | # Y6 SCK (4) 22 | # Y4 BUSY (11) (Low = Busy) 23 | # Y3 RST (10) 24 | # Y2 CS (7) 25 | # Y1 DC (8) 26 | import machine 27 | import gc 28 | 29 | from drivers.epaper.epd29 import EPD as SSD 30 | 31 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 32 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 33 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 34 | pbusy = machine.Pin('Y4', machine.Pin.IN) 35 | 36 | # Baudrate. Adafruit use 1MHz at 37 | # https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2 38 | # Datasheet P35 indicates up to 10MHz. 39 | spi = machine.SPI(2, baudrate=5_000_000) 40 | gc.collect() # Precaution before instantiating framebuf 41 | ssd = SSD(spi, pcs, pdc, prst, pbusy) # Create a display instance 42 | -------------------------------------------------------------------------------- /setup_examples/epd29_pyb_sync.py: -------------------------------------------------------------------------------- 1 | # epd29_sync.py Config for synchronous applications on 2.9" ePaper. 2 | # Customise for your hardware config 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2020 Peter Hinch 6 | 7 | # Supports Adafruit 2.9" monochrome EPD with interface board connected to Pyboard. 8 | # Interface breakout: https://www.adafruit.com/product/4224 9 | # Display: https://www.adafruit.com/product/4262 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING. Adafruit schematic linked on the product web pagerefers to a different 16 | # device. These are the pins on the physical board. 17 | # Pyb Breakout 18 | # Vin Vin (1) 19 | # Gnd Gnd (3) 20 | # Y8 MOSI (6) 21 | # Y6 SCK (4) 22 | # Y4 BUSY (11) (Low = Busy) 23 | # Y3 RST (10) 24 | # Y2 CS (7) 25 | # Y1 DC (8) 26 | import machine 27 | import gc 28 | 29 | from drivers.epaper.epd29 import EPD as SSD 30 | 31 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 32 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 33 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 34 | pbusy = machine.Pin('Y4', machine.Pin.IN) 35 | 36 | # Baudrate. Adafruit use 1MHz at 37 | # https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2 38 | # Datasheet P35 indicates up to 10MHz. 39 | spi = machine.SPI(2, baudrate=5_000_000) 40 | gc.collect() # Precaution before instantiating framebuf 41 | ssd = SSD(spi, pcs, pdc, prst, pbusy) # Create a display instance 42 | # ssd.demo_mode = True 43 | -------------------------------------------------------------------------------- /setup_examples/epd_waveshare_42_pico.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2022 Peter Hinch 5 | 6 | # Supports Waveshare 4.2" 400x300 ePaper display with Raspberry Pico 7 | # https://thepihut.com/collections/epaper-displays-for-raspberry-pi/products/4-2-e-paper-display-module-for-raspberry-pi-pico-black-white-400x300 8 | # Waveshare code 9 | # https://github.com/waveshare/Pico_ePaper_Code/blob/a6b26ac714d56f958010ddfca3b7fef8410c59c2/python/Pico-ePaper-4.2.py 10 | import machine 11 | import gc 12 | 13 | from drivers.epaper.pico_epaper_42 import EPD as SSD 14 | 15 | gc.collect() # Precaution before instantiating framebuf 16 | ssd = SSD() # Create a display instance 17 | # Set this to run demos written for arbitrary displays: 18 | # ssd.demo_mode = True 19 | -------------------------------------------------------------------------------- /setup_examples/gc9a01_pico.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2024 Peter Hinch 5 | 6 | # As written, supports: 7 | # gc9a01 240x240 circular display on Pi Pico 8 | # Pin mapping is for Waveshare RP2040-Touch-LCD-1.28 9 | # Edit the driver import for other displays. 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING 16 | # Pico Display 17 | # GPIO Pin 18 | # 3v3 36 Vin 19 | # IO6 9 CLK Hardware SPI0 20 | # IO7 10 DATA (AKA SI MOSI) 21 | # IO8 11 DC 22 | # IO9 12 Rst 23 | # Gnd 13 Gnd 24 | # IO10 14 CS 25 | 26 | from machine import Pin, SPI 27 | import gc 28 | from drivers.gc9a01.gc9a01 import GC9A01 as SSD 29 | 30 | from drivers.gc9a01.gc9a01_8_bit import GC9A01 as SSD 31 | 32 | pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins 33 | prst = Pin(13, Pin.OUT, value=1) 34 | pcs = Pin(9, Pin.OUT, value=1) 35 | 36 | gc.collect() # Precaution before instantiating framebuf 37 | # See DRIVERS.md 38 | spi = SPI(1, sck=Pin(10), mosi=Pin(11), miso=Pin(12), baudrate=33_000_000) 39 | ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst, lscape=False, usd=False, mirror=False) 40 | -------------------------------------------------------------------------------- /setup_examples/ili9341_esp32.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # ili9341 240x320 displays on ESP32 8 | # Edit the driver import for other displays. 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | # WIRING 15 | # ESP SSD 16 | # 3v3 Vin 17 | # Gnd Gnd 18 | # IO25 DC 19 | # IO26 CS 20 | # IO27 Rst 21 | # IO14 CLK Hardware SPI1 22 | # IO13 DATA (AKA SI MOSI) 23 | 24 | from machine import Pin, SPI 25 | import gc 26 | 27 | # *** Choose your color display driver here *** 28 | # ili9341 specific driver 29 | from drivers.ili93xx.ili9341 import ILI9341 as SSD 30 | 31 | pdc = Pin(25, Pin.OUT, value=0) # Arbitrary pins 32 | pcs = Pin(26, Pin.OUT, value=1) 33 | prst = Pin(27, Pin.OUT, value=1) 34 | 35 | # Kept as ssd to maintain compatability 36 | gc.collect() # Precaution before instantiating framebuf 37 | spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) 38 | ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst) 39 | -------------------------------------------------------------------------------- /setup_examples/ili9341_esp32_2432S024c.py: -------------------------------------------------------------------------------- 1 | # hardware_setup.py for CYD_ESP32-2432S024C --- ili9341_CST820_ESP32 2 | # 2.4" Cheap Yellow Display 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2021-2024 Peter Hinch 6 | # 17-dec-2024 ZolAnd 7 | # Schematic 8 | # https://github.com/jtobinart/Micropython_CYDc_ESP32-2432S024C/blob/main/resources/5-Schematic/2432S024-2-V1.0.png 9 | # also in schematics 10 | 11 | # This 2.4" Cheap Yellow Display comes in resistive and capacitive versions. 12 | # Both use a vanilla ESP32 with an ili9341 240x320 display. 13 | # Resistive version uses XPT2046 on same SPI bus as display, cs/ on GPIO33 14 | # This setup is for the capacitive version with CST820 controller on I2C. 15 | 16 | """ 17 | Pin Reference (display and touch) 18 | 19 | D 0 Digital Boot Button 20 | D 2 Digital Display - Display: TFT_RS / TFT_DC 21 | 12 Digital Display - Display: TFT_SDO / TFT_MISO [HSPI] 22 | D 13 Digital Display - Display: TFT_SDI / TFT_MOSI [HSPI] 23 | D 14 Digital Display - Display: TFT_SCK [HSPI] 24 | D 15 Digital Display - Display: TFT_CS [HSPI] 25 | T 21 Digital Touch, Connector P3 & CN1 - Touch CST820: CTP_INT / I2C SDA 26 | T 25 Digital Touch CST920 - Touch CST820: CTP_RST 27 | D 27 Digital Display - Display: TFT_BL (BackLight) 28 | T 32 Digital Touch CST820 - Touch CST820: CTP_SCL 29 | T 33 Digital Touch CST820 - Touch CST820: CTP_SDA 30 | 31 | Full pin reference. 32 | Source https://github.com/jtobinart/Micropython_CYDc_ESP32-2432S024C/blob/main/cydc.py 33 | 34 | IO Pins 35 | 0 Digital Boot Button 36 | 1 Digital Connector P1 & USB Chip - TX 37 | 2 Digital Display - TFT_RS / TFT_DC 38 | 3 Digital Connector P1 & USB Chip - RX 39 | 4 Digital RGB LED - Red 40 | 5 Digital SD Card - TF_CS [VSPI] 41 | 6 Digital Not Connected 42 | 7 Digital Not Connected 43 | 8 Digital Not Connected 44 | 9 Digital Not Connected 45 | 10 Digital Not Connected 46 | 11 Digital Not Connected 47 | 12 Digital Display - TFT_SDO / TFT_MISO [HSPI] 48 | 13 Digital Display - TFT_SDI / TFT_MOSI [HSPI] 49 | 14 Digital Display - TFT_SCK [HSPI] 50 | 15 Digital Display - TFT_CS [HSPI] 51 | 16 Digital RGB LED - Green 52 | 17 Digital RGB LED - Blue 53 | 18 Digital SD Card - SCK [VSPI] 54 | 19 Digital SD Card - MISO [VSPI] 55 | 21 Digital Touch, Connector P3 & CN1 - CTP_INT / I2C SDA 56 | 22 Digital Connector P3 & CN1 - I2C SCL 57 | 23 Digital SD Card - MOSI [VSPI] 58 | 25 Digital Touch CST920 - CTP_RST 59 | 26 Analog Speaker - !!!Speaker ONLY! Connected to Amp!!! 60 | 27 Digital Display - TFT_BL (BackLight) 61 | 32 Digital Touch CST820 - CTP_SCL 62 | 33 Digital Touch CST820 - CTP_SDA 63 | 34 Analog LDR Light Sensor - !!!Input ONLY!!! 64 | 35 Digital P3 Connector - !!!Input ONLY w/ NO pull-ups!!! 65 | 36 Digital Not Connected 66 | 39 Digital Not Connected 67 | """ 68 | 69 | from machine import Pin, SPI 70 | import gc 71 | from drivers.ili93xx.ili9341 import ILI9341 as SSD 72 | 73 | # Display setup 74 | prst = Pin(0, Pin.OUT, value=1) 75 | pdc = Pin(2, Pin.OUT, value=0) 76 | pcs = Pin(15, Pin.OUT, value=1) 77 | 78 | # Use hardSPI (bus 1) 79 | spi = SPI(1, sck=Pin(14), mosi=Pin(13), baudrate=40_000_000) 80 | # Precaution before instantiating framebuf 81 | gc.collect() 82 | ssd = SSD(spi, height=240, width=320, dc=pdc, cs=pcs, rst=prst, usd=True) # 240x320 default 83 | 84 | # Backlight 85 | tft_bl = Pin(27, Pin.OUT, value=1) # Turn on backlight 86 | -------------------------------------------------------------------------------- /setup_examples/ili9341_esp32_wroverkit.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # ili9341 240x320 displays on ESP32 Wrover KIT 4.1 8 | # See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-wrover-kit.html 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | from machine import Pin, SPI 15 | import gc 16 | 17 | # ili9341 specific driver 18 | from drivers.ili93xx.ili9341 import ILI9341 as SSD 19 | 20 | pdc = Pin(21, Pin.OUT, value=0) 21 | prst = Pin(18, Pin.OUT, value=1) 22 | pcs = Pin(22, Pin.OUT, value=1) 23 | 24 | # Kept as ssd to maintain compatability 25 | gc.collect() # Precaution before instantiating framebuf 26 | # See DRIVERS.md re overclocking the SPI bus 27 | spi = SPI(2, sck=Pin(19), mosi=Pin(23), miso=Pin(25), baudrate=30_000_000) 28 | ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst) 29 | 30 | # turn on the backlight 31 | bl = Pin(5, Pin.OUT) 32 | bl.value(0) -------------------------------------------------------------------------------- /setup_examples/ili9341_pico.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # ili9341 240x320 displays on Pi Pico 8 | # Edit the driver import for other displays. 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | # WIRING 15 | # Pico Display 16 | # GPIO Pin 17 | # 3v3 36 Vin 18 | # IO6 9 CLK Hardware SPI0 19 | # IO7 10 DATA (AKA SI MOSI) 20 | # IO8 11 DC 21 | # IO9 12 Rst 22 | # Gnd 13 Gnd 23 | # IO10 14 CS 24 | 25 | from machine import Pin, SPI 26 | import gc 27 | 28 | # *** Choose your color display driver here *** 29 | # ili9341 specific driver 30 | from drivers.ili93xx.ili9341 import ILI9341 as SSD 31 | 32 | pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins 33 | prst = Pin(9, Pin.OUT, value=1) 34 | pcs = Pin(10, Pin.OUT, value=1) 35 | 36 | # Kept as ssd to maintain compatability 37 | gc.collect() # Precaution before instantiating framebuf 38 | # See DRIVERS.md re overclocking the SPI bus 39 | spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=30_000_000) 40 | ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst) 41 | -------------------------------------------------------------------------------- /setup_examples/ili9486_pico.py: -------------------------------------------------------------------------------- 1 | # ili9486_pico.py Customise for your hardware config and rename 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | 5 | # ILI9486 on Pi Pico 6 | # See DRIVERS.md for wiring details. 7 | 8 | from machine import Pin, SPI 9 | import gc 10 | 11 | from drivers.ili94xx.ili9486 import ILI9486 as SSD 12 | 13 | pdc = Pin(8, Pin.OUT, value=0) 14 | prst = Pin(9, Pin.OUT, value=1) 15 | pcs = Pin(10, Pin.OUT, value=1) 16 | spi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=30_000_000) 17 | gc.collect() # Precaution before instantiating framebuf 18 | ssd = SSD(spi, pcs, pdc, prst) 19 | -------------------------------------------------------------------------------- /setup_examples/ili9488_esp32.py: -------------------------------------------------------------------------------- 1 | # 2 | # Setup for ThingPulse Grande Kit 3 | # 4 | # Has ILI9488 display running on an ESP32-WROVER-E 5 | # 6 | # Released under the MIT License (MIT). See LICENSE. 7 | 8 | from micropython import const 9 | 10 | # 11 | # Pin assignments here are for the Grande Kit values from schematic: 12 | # https://thingpulse.com/wp-content/uploads/2022/10/Schematic_Color-Kit-Grande_2023-01-14-pdf.jpg 13 | # You will need to customize for your board. 14 | 15 | LCD_DC = const(2) 16 | LCD_CS = const(15) 17 | LCD_CLK = const(5) 18 | LCD_MOSI = const(18) 19 | LCD_MISO = const(19) 20 | LCD_BackLight = const(32) 21 | LCD_RST = const(4) 22 | 23 | from machine import Pin, SPI, freq 24 | from drivers.ili94xx.ili9488 import ILI9488 as SSD 25 | 26 | # Screen configuration 27 | # (Create and export an SSD instance) 28 | 29 | prst = Pin(LCD_RST, Pin.OUT, value=1) 30 | pdc = Pin(LCD_DC, Pin.OUT, value=1) 31 | pcs = Pin(LCD_CS, Pin.OUT, value=1) 32 | 33 | # turn on back light 34 | backlight=Pin(LCD_BackLight, Pin.OUT, value=1) 35 | 36 | # Use SPI bus 1, 30 Mhz is maximum speed. 37 | spi = SPI(1, 30_000_000, sck=Pin(LCD_CLK), mosi=Pin(LCD_MOSI, Pin.OUT), miso=Pin(LCD_MISO, Pin.OUT)) 38 | 39 | # Precaution before instantiating framebuf 40 | ssd = SSD(spi, height=480, width=320, dc=pdc, cs=pcs, rst=prst, usd=False) 41 | -------------------------------------------------------------------------------- /setup_examples/ili9488_pico.py: -------------------------------------------------------------------------------- 1 | # 2 | # Setup for ILI9488 interfaced to Raspberry PI Pico 2 and Pico 3 | # 4 | # Released under the MIT License (MIT). See LICENSE. 5 | 6 | from micropython import const 7 | 8 | # Modify these Pin assignments to match your hardware. 9 | 10 | # Simple GPIO's 11 | LCD_DC = const(21) # PICO Pin 27 12 | LCD_RST = const(22) # PICO Pin 29 13 | LCD_CS = const(27) # PICO Pin 32 14 | LCD_BackLight = const(28) # PICO Pin 34 15 | 16 | # SPI pins 17 | 18 | LCD_CLK = const(18) # PICO Pin 24 19 | LCD_MOSI = const(19) # PICO Pin 25 20 | LCD_MISO = const(16) # PICO Pin 21 21 | 22 | from machine import Pin, SPI 23 | from drivers.ili94xx.ili9488 import ILI9488 as SSD 24 | 25 | # Screen configuration 26 | # (Create and export an SSD instance) 27 | 28 | prst = Pin(LCD_RST, Pin.OUT, value=1) 29 | pdc = Pin(LCD_DC, Pin.OUT, value=1) 30 | pcs = Pin(LCD_CS, Pin.OUT, value=1) 31 | 32 | # turn on back light 33 | backlight=Pin(LCD_BackLight, Pin.OUT, value=1) 34 | 35 | # Use SPI bus 0, 24 Mhz is maximum speed on PICO 36 | spi = SPI(0, 24_000_000, sck=Pin(LCD_CLK), mosi=Pin(LCD_MOSI, Pin.OUT), miso=Pin(LCD_MISO, Pin.OUT)) 37 | 38 | # Precaution before instantiating framebuf 39 | ssd = SSD(spi, height=480, width=320, dc=pdc, cs=pcs, rst=prst, usd=False) 40 | -------------------------------------------------------------------------------- /setup_examples/sh1106_spi_pico.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, SPI 2 | 3 | from drivers.sh1106.sh1106 import SH1106_SPI as SSD 4 | 5 | oled_width = 128 6 | oled_height = 64 7 | # Incorporating the Pico pin names into the variable names 8 | sck_clk = Pin(14) 9 | tx_mosi = Pin(15) 10 | rx_miso_dc = Pin(12) 11 | csn_cs = Pin(13) 12 | 13 | oled_spi = SPI(1, sck=sck_clk, mosi=tx_mosi, miso=rx_miso_dc) 14 | 15 | ssd = SSD(oled_width, oled_height, oled_spi, dc=rx_miso_dc, cs=csn_cs) 16 | -------------------------------------------------------------------------------- /setup_examples/sharp_pyb.py: -------------------------------------------------------------------------------- 1 | # sharp_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports Adafruit 2.7 inch 400*240 Sharp display 7 | # https://www.adafruit.com/product/4694 8 | 9 | # Demo of initialisation procedure designed to minimise risk of memory fail 10 | # when instantiating the frame buffer. The aim is to do this as early as 11 | # possible before importing other modules. 12 | 13 | # SSD1331 drivers are cross-platform. 14 | # WIRING (Adafruit pin nos and names with Pyboard pins). 15 | # Pyb SSD 16 | # Vin Vin Pyboard: Vin is a 5V output when powered by USB 17 | # Gnd Gnd 18 | # Y8 DI 19 | # Y6 CLK 20 | # Y5 CS 21 | 22 | import machine 23 | import gc 24 | 25 | from drivers.sharp.sharp import SHARP as SSD 26 | 27 | pcs = machine.Pin('Y5', machine.Pin.OUT_PP, value=0) # Active high 28 | # Baudrate ref. https://learn.adafruit.com/adafruit-sharp-memory-display-breakout/circuitpython-displayio-usage 29 | spi = machine.SPI(2, baudrate=2_000_000) 30 | gc.collect() 31 | ssd = SSD(spi, pcs) 32 | -------------------------------------------------------------------------------- /setup_examples/ssd1306_i2c_esp32.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, SoftI2C 2 | import gc 3 | 4 | from drivers.ssd1306.ssd1306 import SSD1306_I2C as SSD 5 | 6 | # ESP32 Pin assignment 7 | i2c = SoftI2C(scl=Pin(22), sda=Pin(21)) 8 | 9 | oled_width = 128 10 | oled_height = 64 11 | gc.collect() # Precaution before instantiating framebuf 12 | ssd = SSD(oled_width, oled_height, i2c) 13 | -------------------------------------------------------------------------------- /setup_examples/ssd1306_i2c_esp8266.py: -------------------------------------------------------------------------------- 1 | # esp8266_setup.py Copy to target as color_setup.py 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # OLED monochromatic display 0.66" 64*48 shield: https://www.wemos.cc/en/latest/d1_mini_shield/oled_0_66.html 8 | # Edit the driver import for other displays. 9 | 10 | # WIRING. 11 | # - no wiring required if shield placed atop Wemos D1 mini 12 | # 13 | # ESP SSD 14 | # 3.3v 3.3v 15 | # Gnd Gnd 16 | # GP5 D1 (SCL) 17 | # GP4 D2 (SDA) 18 | 19 | import machine 20 | import gc 21 | from drivers.ssd1306.ssd1306 import SSD1306_I2C as SSD 22 | 23 | gc.collect() 24 | 25 | WIDTH = const(64) 26 | HEIGHT = const(48) 27 | 28 | i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4)) 29 | gc.collect() # Precaution before instantiating framebuf 30 | ssd = SSD(WIDTH, HEIGHT, i2c) 31 | -------------------------------------------------------------------------------- /setup_examples/ssd1306_pyb.py: -------------------------------------------------------------------------------- 1 | # ssd1306_setup.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display. 2 | # ssd1306_setup.py Device initialisation. Copy to color_setup.py on host. 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2018-2021 Peter Hinch 6 | 7 | 8 | # https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display 9 | # https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html 10 | 11 | import machine 12 | from drivers.ssd1306.ssd1306 import SSD1306_SPI, SSD1306_I2C 13 | 14 | WIDTH = const(128) 15 | HEIGHT = const(64) 16 | use_spi = False # I2C or SPI 17 | soft = True # Soft or hard I2C/SPI 18 | 19 | # Export an initialised ssd display object. 20 | if use_spi: 21 | # Pyb SSD 22 | # 3v3 Vin 23 | # Gnd Gnd 24 | # X1 DC 25 | # X2 CS 26 | # X3 Rst 27 | # X6 CLK 28 | # X8 DATA 29 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP) 30 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP) 31 | prst = machine.Pin('Y3', machine.Pin.OUT_PP) 32 | if soft: 33 | spi = machine.SPI(sck=machine.Pin('Y6'), mosi=machine.Pin('Y8'), miso=machine.Pin('Y7')) 34 | else: 35 | spi = machine.SPI(2) 36 | ssd = SSD1306_SPI(WIDTH, HEIGHT, spi, pdc, prst, pcs) 37 | else: # I2C 38 | # Pyb SSD 39 | # 3v3 Vin 40 | # Gnd Gnd 41 | # Y9 CLK 42 | # Y10 DATA 43 | if soft: 44 | pscl = machine.Pin('Y9', machine.Pin.OPEN_DRAIN) 45 | psda = machine.Pin('Y10', machine.Pin.OPEN_DRAIN) 46 | i2c = machine.SoftI2C(scl=pscl, sda=psda) 47 | else: 48 | i2c = machine.I2C(2) 49 | ssd = SSD1306_I2C(WIDTH, HEIGHT, i2c) 50 | -------------------------------------------------------------------------------- /setup_examples/ssd1331_pyb.py: -------------------------------------------------------------------------------- 1 | # ssd1331_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 8 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 9 | # Edit the driver import for other displays. 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # SSD1331 drivers are cross-platform. 16 | # WIRING (Adafruit pin nos and names with Pyboard pins). 17 | # Pyb SSD 18 | # 3v3 Vin (10) 19 | # Gnd Gnd (11) 20 | # X1 DC (3 DC) 21 | # X2 CS (5 OC OLEDCS) 22 | # X3 Rst (4 R RESET) 23 | # X6 CLK (2 CL SCK) 24 | # X8 DATA (1 SI MOSI) 25 | 26 | import machine 27 | import gc 28 | 29 | from drivers.ssd1331.ssd1331 import SSD1331 as SSD 30 | 31 | pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0) 32 | pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1) 33 | prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1) 34 | spi = machine.SPI(1, baudrate=6_666_000) 35 | gc.collect() # Precaution before instantiating framebuf 36 | ssd = SSD(spi, pcs, pdc, prst) # Create a display instance 37 | -------------------------------------------------------------------------------- /setup_examples/ssd1351_esp32.py: -------------------------------------------------------------------------------- 1 | # ssd1351_esp32.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 8 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 9 | # Edit the driver import for other displays. 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING (Adafruit pin nos and names). 16 | # ESP SSD 17 | # 3v3 Vin (10) 18 | # Gnd Gnd (11) 19 | # IO27 DC (3 DC) 20 | # IO25 CS (5 OC OLEDCS) 21 | # IO26 Rst (4 R RESET) 22 | # IO14 CLK (2 CL SCK) Hardware SPI1 23 | # IO13 DATA (1 SI MOSI) 24 | 25 | from machine import Pin, SPI 26 | import gc 27 | 28 | # *** Choose your color display driver here *** 29 | # Driver supporting non-STM platforms 30 | from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD 31 | 32 | #height = 96 # 1.27 inch 96*128 (rows*cols) display 33 | height = 128 # 1.5 inch 128*128 display 34 | 35 | pdc = Pin(27, Pin.OUT, value=0) # Arbitrary pins 36 | pcs = Pin(25, Pin.OUT, value=1) 37 | prst = Pin(26, Pin.OUT, value=1) 38 | # Datasheet says 20MHz but I couldn't make that work even on a PCB 39 | spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) 40 | gc.collect() # Precaution before instantiating framebuf 41 | ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance 42 | -------------------------------------------------------------------------------- /setup_examples/ssd1351_esp8266.py: -------------------------------------------------------------------------------- 1 | # esp8266_setup.py Copy to target as color_setup.py 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 8 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 9 | # Edit the driver import for other displays. 10 | 11 | # WIRING (Adafruit pin nos and names). 12 | # Pyb SSD 13 | # 3v3 Vin (10) 14 | # Gnd Gnd (11) 15 | # IO0 DC (3 DC) 16 | # IO16 CS (5 OC OLEDCS) 17 | # IO2 Rst (4 R RESET) 18 | # IO14 CLK (2 CL SCK) Hardware SPI1 19 | # IO13 DATA (1 SI MOSI) 20 | 21 | from machine import SPI, Pin 22 | import gc 23 | from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD 24 | 25 | height = 128 # Ensure height is correct (96/128) 26 | 27 | pdc = Pin(0, Pin.OUT, value=0) # Arbitrary pins 28 | pcs = Pin(16, Pin.OUT, value=1) 29 | prst = Pin(2, Pin.OUT, value=1) 30 | # Hardware SPI on native pins for performance 31 | spi = SPI(1, baudrate=10_000_000, polarity=0, phase=0) 32 | 33 | gc.collect() 34 | ssd = SSD(spi, pcs, pdc, prst, height=height) 35 | -------------------------------------------------------------------------------- /setup_examples/ssd1351_pico.py: -------------------------------------------------------------------------------- 1 | # pico_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 8 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 9 | # Edit the driver import for other displays. 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING 16 | # Pico GPIO no (package pin no. in parens) 17 | # Adafruit package pin nos and names. 18 | # Pico SSD 19 | # Vbus(40) Vin (10) 20 | # Gnd(18) Gnd (11) 21 | # 15(20) DC (3 DC) 22 | # 13(17) CS (5 OC OLEDCS) 23 | # 14(19) Rst (4 R RESET) 24 | # 10(14) CLK (2 CL SCK) 25 | # 11(15) DATA (1 SI MOSI) 26 | 27 | import machine 28 | import gc 29 | 30 | # *** Choose your color display driver here *** 31 | # Driver supporting non-STM platforms 32 | # from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD 33 | 34 | # STM specific driver 35 | from drivers.ssd1351.ssd1351 import SSD1351 as SSD 36 | 37 | #height = 96 # 1.27 inch 96*128 (rows*cols) display 38 | height = 128 # 1.5 inch 128*128 display 39 | 40 | pdc = machine.Pin(15, machine.Pin.OUT, value=0) 41 | pcs = machine.Pin(13, machine.Pin.OUT, value=1) 42 | prst = machine.Pin(14, machine.Pin.OUT, value=1) 43 | #spi = machine.SPI(1, baudrate=1_000_000) 44 | spi = machine.SoftSPI(sck=machine.Pin(10, machine.Pin.OUT), mosi=machine.Pin(11, machine.Pin.OUT), miso=machine.Pin(8, machine.Pin.OUT)) 45 | gc.collect() # Precaution before instantiating framebuf 46 | ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance 47 | -------------------------------------------------------------------------------- /setup_examples/ssd1351_pyb.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 8 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 9 | # Edit the driver import for other displays. 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING (Adafruit pin nos and names). 16 | # Pyb SSD 17 | # 3v3 Vin (10) 18 | # Gnd Gnd (11) 19 | # Y1 DC (3 DC) 20 | # Y2 CS (5 OC OLEDCS) 21 | # Y3 Rst (4 R RESET) 22 | # Y6 CLK (2 CL SCK) 23 | # Y8 DATA (1 SI MOSI) 24 | 25 | import machine 26 | import gc 27 | 28 | # *** Choose your color display driver here *** 29 | # Driver supporting non-STM platforms 30 | # from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD 31 | 32 | # STM specific driver 33 | from drivers.ssd1351.ssd1351 import SSD1351 as SSD 34 | 35 | height = 96 # 1.27 inch 96*128 (rows*cols) display 36 | # height = 128 # 1.5 inch 128*128 display 37 | 38 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 39 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 40 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 41 | spi = machine.SPI(2) 42 | gc.collect() # Precaution before instantiating framebuf 43 | ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance 44 | -------------------------------------------------------------------------------- /setup_examples/ssd1680_esp32.py: -------------------------------------------------------------------------------- 1 | # WIRING 2 | # 15 BUSY 3 | # 4 DC 4 | # 2 Rst 5 | # 5 CS 6 | # 23 SDA 7 | # 18 SCL 8 | 9 | from machine import Pin, SPI 10 | import gc 11 | 12 | # *** Choose your color display driver here *** 13 | from drivers.epaper.epd29_ssd1680 import EPD as SSD 14 | 15 | dc = Pin(4, Pin.OUT, value=0) 16 | rst_pin = 2 # Note reset pin is specified by ID number. 17 | cs = Pin(5, Pin.OUT, value=1) 18 | busy = Pin(15, Pin.IN) 19 | 20 | spi = SPI(1, baudrate=10000000, sck=Pin(18), mosi=Pin(23)) 21 | gc.collect() # Precaution before instantiating framebuf 22 | ssd = SSD(spi, cs, dc, rst_pin, busy, landscape=True) 23 | -------------------------------------------------------------------------------- /setup_examples/st7567_i2c_esp32.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, SoftI2C 2 | import gc 3 | 4 | # *** Choose your color display driver here *** 5 | from drivers.st7567s.st7567s import ST7567 as SSD 6 | 7 | # ESP32 Pin assignment 8 | i2c = SoftI2C(scl=Pin(33), sda=Pin(32), freq=100000) 9 | 10 | lcd_width = 128 11 | lcd_height = 64 12 | gc.collect() # Precaution before instantiating framebuf 13 | ssd = SSD(lcd_width, lcd_height, i2c) 14 | -------------------------------------------------------------------------------- /setup_examples/st7735r144_pyb.py: -------------------------------------------------------------------------------- 1 | # st7735r144_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adfruit 1.44 inch Color TFT LCD display with MicroSD Card Breakout: 8 | # https://www.adafruit.com/product/2088 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | # WIRING (Adafruit pin nos and names). 15 | # Pyb SSD 16 | # Vin Vcc (1) 5V 17 | # 3v3 (2) No connection 18 | # Gnd Gnd (3) 19 | # Y6 SCK (4) 20 | # Y7 SO (5) MISO Optional - (for SD card) 21 | # Y8 SI (6) MOSI 22 | # Y2 TCS (7) 23 | # Y3 RST (8) 24 | # Y1 D/C (9) 25 | # CARD_CS (10) No connection (for SD card) 26 | # Vin Lite (11) Backlight 27 | 28 | import machine 29 | import gc 30 | 31 | from drivers.st7735r.st7735r144 import ST7735R as SSD 32 | 33 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 34 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 35 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 36 | spi = machine.SPI(2, baudrate=12_000_000) 37 | gc.collect() # Precaution before instantiating framebuf 38 | ssd = SSD(spi, pcs, pdc, prst) # Create a display instance 39 | -------------------------------------------------------------------------------- /setup_examples/st7735r_esp32.py: -------------------------------------------------------------------------------- 1 | # esp32_setup.py Copy to target as color_setup.py 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # Pin nos. match my PCB for all displays. 7 | 8 | # As written with commented-out lines, supports: 9 | # Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431 10 | # Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673 11 | # Adfruit 1.8" 128*160 Color TFT LCD display https://www.adafruit.com/product/358 12 | # Adfruit 1.44" 128*128 Color TFT LCD display https://www.adafruit.com/product/2088 13 | # Edit the driver import for other displays. 14 | 15 | # WIRING (Adafruit pin nos and names). 16 | # ESP SSD 17 | # 3v3 Vin (10) 18 | # Gnd Gnd (11) 19 | # IO27 DC (3 DC) 20 | # IO25 CS (5 OC OLEDCS) 21 | # IO26 Rst (4 R RESET) 22 | # IO14 CLK (2 CL SCK) Hardware SPI1 23 | # IO13 DATA (1 SI MOSI) 24 | 25 | from machine import SPI, Pin 26 | import gc 27 | #from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD 28 | from drivers.st7735r.st7735r import ST7735R as SSD 29 | #from drivers.st7735r.st7735r144 import ST7735R as SSD 30 | #from drivers.st7735r.st7735r_4bit import ST7735R as SSD 31 | 32 | height = 128 # Ensure height is correct 33 | 34 | pdc = Pin(27, Pin.OUT, value=0) # Arbitrary pins 35 | pcs = Pin(25, Pin.OUT, value=1) 36 | prst = Pin(26, Pin.OUT, value=1) 37 | # Hardware SPI on native pins for performance. Check DRIVERS.md for optimum baudrate. 38 | spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) 39 | gc.collect() 40 | # ssd = SSD(spi, pcs, pdc, prst, height=height) # Must specify height for SSD1351 41 | ssd = SSD(spi, pcs, pdc, prst) # The other Adafruit displays use defaults 42 | # On st7735r 1.8 inch display can exchange height and width for portrait mode. See docs. 43 | # The 1.44 inch display is symmetrical so this doesn't apply. 44 | -------------------------------------------------------------------------------- /setup_examples/st7735r_pyb.py: -------------------------------------------------------------------------------- 1 | # st7735r_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adfruit 1.8' Color TFT LCD display with MicroSD Card Breakout: 8 | # https://www.adafruit.com/product/358 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | # WIRING (Adafruit pin nos and names). 15 | # Pyb SSD 16 | # Gnd Gnd (1) 17 | # Vin VCC (2) 5V 18 | # Y3 RESET (3) 19 | # Y1 D/C (4) 20 | # CARD_CS (5) No connection (for SD card) 21 | # Y2 TFT_CS (6) 22 | # Y8 MOSI (7) 23 | # Y6 SCK (8) 24 | # Y7 MISO (9) Optional - (for SD card) 25 | # Vin LITE (10) Backlight 26 | 27 | import machine 28 | import gc 29 | 30 | from drivers.st7735r.st7735r import ST7735R as SSD 31 | 32 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 33 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 34 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 35 | spi = machine.SPI(2, baudrate=12_000_000) 36 | gc.collect() # Precaution before instantiating framebuf 37 | ssd = SSD(spi, pcs, pdc, prst) # Create a display instance 38 | -------------------------------------------------------------------------------- /setup_examples/st7789_pico.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2021 Peter Hinch 5 | 6 | # As written, supports: 7 | # Adafruit 1.3" and 1.54" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789 8 | # https://www.adafruit.com/product/4313 9 | # https://www.adafruit.com/product/3787 10 | 11 | # Demo of initialisation procedure designed to minimise risk of memory fail 12 | # when instantiating the frame buffer. The aim is to do this as early as 13 | # possible before importing other modules. 14 | 15 | # WIRING (Adafruit pin nos and names). 16 | # Pico SSD 17 | # VBUS Vin 18 | # Gnd Gnd 19 | # 13 D/C 20 | # 14 TCS 21 | # 15 RST 22 | # 10 SCK 23 | # 11 SI MOSI 24 | 25 | # No connect: Lite, CCS, SO (MISO) 26 | from machine import Pin, SPI 27 | import gc 28 | 29 | from drivers.st7789.st7789_4bit import * 30 | SSD = ST7789 31 | 32 | pdc = Pin(13, Pin.OUT, value=0) # Arbitrary pins 33 | pcs = Pin(14, Pin.OUT, value=1) 34 | prst = Pin(15, Pin.OUT, value=1) 35 | 36 | gc.collect() # Precaution before instantiating framebuf 37 | # Conservative low baudrate. Can go to 62.5MHz. Depending on wiring. 38 | spi = SPI(1, 30_000_000, sck=Pin(10), mosi=Pin(11), miso=Pin(8)) 39 | ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst) 40 | 41 | -------------------------------------------------------------------------------- /setup_examples/st7789_pico_lcd_114.py: -------------------------------------------------------------------------------- 1 | # hardware_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa 5 | 6 | # Supports: 7 | # Waveshare Pico LCD 1.14" 135*240(Pixel) based on ST7789V 8 | # https://www.waveshare.com/wiki/Pico-LCD-1.14 9 | # https://www.waveshare.com/pico-lcd-1.14.htm 10 | 11 | from machine import Pin, SPI 12 | import gc 13 | 14 | from drivers.st7789.st7789_4bit import * 15 | SSD = ST7789 16 | 17 | gc.collect() # Precaution before instantiating framebuf 18 | # Conservative low baudrate. Can go to 62.5MHz. 19 | spi = SPI(1, 30_000_000, sck=Pin(10), mosi=Pin(11), miso=None) 20 | pcs = Pin(9, Pin.OUT, value=1) 21 | prst = Pin(12, Pin.OUT, value=1) 22 | pbl = Pin(13, Pin.OUT, value=1) 23 | pdc = Pin(8, Pin.OUT, value=0) 24 | 25 | ssd = SSD(spi, height=135, width=240, dc=pdc, cs=pcs, rst=prst, disp_mode=LANDSCAPE, display=TDISPLAY) 26 | -------------------------------------------------------------------------------- /setup_examples/st7789_ttgo.py: -------------------------------------------------------------------------------- 1 | # color_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa 5 | 6 | # Supports: 7 | # TTGO T-Display 1.14" 135*240(Pixel) based on ST7789V 8 | # http://www.lilygo.cn/claprod_view.aspx?TypeId=62&Id=1274 9 | # http://www.lilygo.cn/prod_view.aspx?TypeId=50044&Id=1126 10 | # https://github.com/Xinyuan-LilyGO/TTGO-T-Display 11 | # https://github.com/Xinyuan-LilyGO/TTGO-T-Display/blob/master/image/pinmap.jpg 12 | # https://github.com/Xinyuan-LilyGO/TTGO-T-Display/blob/master/schematic/ESP32-TFT(6-26).pdf 13 | 14 | # WIRING (TTGO T-Display pin numbers and names). 15 | # Pinout of TFT Driver 16 | # ST7789 ESP32 17 | # TFT_MISO N/A 18 | TFT_MOSI = 19 # (SDA on schematic pdf) SPI interface output/input pin. 19 | TFT_SCLK = 18 # This pin is used to be serial interface clock. 20 | TFT_CS = 5 # Chip selection pin, low enable, high disable. 21 | TFT_DC = 16 # Display data/command selection pin in 4-line serial interface. 22 | TFT_RST = 23 # This signal will reset the device,Signal is active low. 23 | TFT_BL = 4 # (LEDK on schematic pdf) Display backlight control pin 24 | 25 | ADC_IN = 34 # Measuring battery or USB voltage, see comment below 26 | ADC_EN = 14 # (PWR_EN on schematic pdf) is the ADC detection enable port 27 | 28 | BUTTON1 = 35 # right of the USB connector 29 | BUTTON2 = 0 # left of the USB connector 30 | 31 | # ESP32 pins, free for use in user applications 32 | #I2C_SDA = 21 # hardware ID 0 33 | #I2C_SCL = 22 34 | 35 | #UART2TXD = 17 36 | 37 | #GPIO2 = 2 38 | #GPIO15 = 15 39 | #GPIO13 = 13 40 | #GPIO12 = 12 41 | 42 | #GPIO37 = 37 43 | #GPIO38 = 38 44 | #UART1TXD = 4 45 | #UART1RXD = 5 46 | #GPIO18 = 18 47 | #GPIO19 = 19 48 | #GPIO17 = 17 49 | 50 | #DAC1 = 25 51 | #DAC2 = 26 52 | 53 | # Input only pins 54 | #GPIO36 = 36 # input only 55 | #GPIO39 = 39 # input only 56 | 57 | # Demo of initialisation procedure designed to minimise risk of memory fail 58 | # when instantiating the frame buffer. The aim is to do this as early as 59 | # possible before importing other modules. 60 | 61 | from machine import Pin, SPI, ADC 62 | import gc 63 | 64 | from drivers.st7789.st7789_4bit import * 65 | SSD = ST7789 66 | 67 | pdc = Pin(TFT_DC, Pin.OUT, value=0) # Arbitrary pins 68 | pcs = Pin(TFT_CS, Pin.OUT, value=1) 69 | prst = Pin(TFT_RST, Pin.OUT, value=1) 70 | pbl = Pin(TFT_BL, Pin.OUT, value=1) 71 | 72 | gc.collect() # Precaution before instantiating framebuf 73 | # Conservative low baudrate. Can go to 62.5MHz. 74 | spi = SPI(1, 30_000_000, sck=Pin(TFT_SCLK), mosi=Pin(TFT_MOSI)) 75 | ''' TTGO 76 | v +----------------+ 77 | 40 | | | 78 | ^ | +------+ | pin 36 79 | | | | | | 80 | | | | | | 81 | 240 | | | | | 82 | | | | | | 83 | | | | | | 84 | v | +------+ | 85 | 40 | | | Reset button 86 | ^ +----------------+ 87 | >----<------>----< 88 | 52 135 xx 89 | BUTTON2 BUTTON1 90 | ''' 91 | # Right way up landscape: defined as top left adjacent to pin 36 92 | ssd = SSD(spi, height=135, width=240, dc=pdc, cs=pcs, rst=prst, disp_mode=LANDSCAPE, display=TDISPLAY) 93 | # Normal portrait display: consistent with TTGO logo at top 94 | # ssd = SSD(spi, height=240, width=135, dc=pdc, cs=pcs, rst=prst, disp_mode=PORTRAIT, display=TDISPLAY) 95 | 96 | # optional 97 | # b1 = Pin(BUTTON1, Pin.IN) 98 | # b2 = Pin(BUTTON2, Pin.IN) 99 | # adc_en = Pin(ADC_EN, Pin.OUT, value=1) 100 | # adc_in = ADC(Pin(ADC_IN)) 101 | # adc_en.value(0) 102 | ''' 103 | Set ADC_EN to "1" and read voltage in BAT_ADC, 104 | if this voltage more than 4.3 V device have powered from USB. 105 | If less then 4.3 V - device have power from battery. 106 | To save battery you can set ADC_EN to "0" and in this case the USB converter 107 | will be power off and do not use your battery. 108 | When you need to measure battery voltage first set ADC_EN to "1", 109 | measure voltage and then set ADC_EN back to "0" for save battery. 110 | ''' 111 | -------------------------------------------------------------------------------- /setup_examples/st7789_ws_pico_res_touch_pico.py: -------------------------------------------------------------------------------- 1 | # ws_pico_res_touch.py 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2022 Peter Hinch 5 | # With help from Tim Wermer. 6 | 7 | import gc 8 | from machine import Pin, SPI 9 | from drivers.st7789.st7789_4bit import * 10 | SSD = ST7789 11 | 12 | pdc = Pin(8, Pin.OUT, value=0) 13 | pcs = Pin(9, Pin.OUT, value=1) 14 | prst = Pin(15, Pin.OUT, value=1) 15 | pbl = Pin(13, Pin.OUT, value=1) 16 | cst = Pin(16, Pin.OUT, value=1) # Initialise all CS\ pins: XPT2046 17 | 18 | gc.collect() # Precaution before instantiating framebuf 19 | # Max baudrate produced by Pico is 31_250_000. ST7789 datasheet allows <= 62.5MHz. 20 | spi = SPI(1, 33_000_000, sck=Pin(10), mosi=Pin(11), miso=Pin(12)) 21 | 22 | # Define the display 23 | # For portrait mode: 24 | # ssd = SSD(spi, height=320, width=240, dc=pdc, cs=pcs, rst=prst) 25 | # For landscape mode: 26 | ssd = SSD(spi, height=240, width=320, disp_mode=PORTRAIT, dc=pdc, cs=pcs, rst=prst) 27 | 28 | # Optional use of SD card. Requires official driver. In my testing the 29 | # 31.25MHz baudrate works. Other SD cards may have different ideas. 30 | # from sdcard import SDCard 31 | # import os 32 | # sd = SDCard(spi, Pin(22, Pin.OUT), 33_000_000) 33 | # vfs = os.VfsFat(sd) 34 | # os.mount(vfs, "/fc") 35 | -------------------------------------------------------------------------------- /setup_examples/waveshare_demo.py: -------------------------------------------------------------------------------- 1 | # waveshare_demo.py Allow standard demos to run on ePaper. Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Waveshare 2.7" 264h*176w monochrome ePaper display: 8 | # https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | # WIRING Pin numbers refer to RPI connector. 15 | # Pyb ePaper 16 | # Vin Vcc (2) 17 | # Gnd Gnd (9) 18 | # Y8 DIN MOSI (19) 19 | # Y6 CLK SCK (23) 20 | # Y4 BUSY (18) (Low = Busy) 21 | # Y3 RST (11) 22 | # Y2 CS (24) 23 | # Y1 DC (22) 24 | import machine 25 | import gc 26 | 27 | from drivers.epaper.epaper2in7_fb import EPD as SSD 28 | 29 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 30 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 31 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 32 | pbusy = machine.Pin('Y4', machine.Pin.IN) 33 | spi = machine.SPI(2, baudrate=4_000_000) # From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py 34 | gc.collect() # Precaution before instantiating framebuf 35 | ssd = SSD(spi, pcs, pdc, prst, pbusy, landscape=True) # Create a display instance 36 | ssd.demo_mode = True 37 | -------------------------------------------------------------------------------- /setup_examples/waveshare_pyb.py: -------------------------------------------------------------------------------- 1 | # waveshare_setup.py Customise for your hardware config 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2020 Peter Hinch 5 | 6 | # As written, supports: 7 | # Waveshare 2.7" 264h*176w monochrome ePaper display: 8 | # https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT 9 | 10 | # Demo of initialisation procedure designed to minimise risk of memory fail 11 | # when instantiating the frame buffer. The aim is to do this as early as 12 | # possible before importing other modules. 13 | 14 | # WIRING Pin numbers refer to RPI connector. 15 | # Pyb ePaper 16 | # Vin Vcc (2) 17 | # Gnd Gnd (9) 18 | # Y8 DIN MOSI (19) 19 | # Y6 CLK SCK (23) 20 | # Y4 BUSY (18) (Low = Busy) 21 | # Y3 RST (11) 22 | # Y2 CS (24) 23 | # Y1 DC (22) 24 | import machine 25 | import gc 26 | 27 | from drivers.epaper.epaper2in7_fb import EPD as SSD 28 | 29 | pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) 30 | pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) 31 | prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) 32 | pbusy = machine.Pin('Y4', machine.Pin.IN) 33 | spi = machine.SPI(2, baudrate=4_000_000) # From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py 34 | gc.collect() # Precaution before instantiating framebuf 35 | ssd = SSD(spi, pcs, pdc, prst, pbusy, landscape=False) # Create a display instance 36 | #ssd.demo_mode = True 37 | --------------------------------------------------------------------------------