kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
Untested ili9341 driver. 4-bit drivers for other displays.
rodzic
5dbcc65828
commit
227e614413
|
@ -66,7 +66,9 @@ This is necessary because the display drivers use a high baudrate unsupported
|
|||
by SD cards. Ensure applications do this before the first SD card access and
|
||||
before subsequent ones if the display has been refreshed.
|
||||
|
||||
# Hardware note: SPI clock rate
|
||||
# Notes on OLED displays
|
||||
|
||||
## Hardware note: SPI clock rate
|
||||
|
||||
For performance reasons the drivers for the Adafruit color displays run the SPI
|
||||
bus at a high rate (currently 10.5MHz). Leads should be short and direct. An
|
||||
|
@ -77,8 +79,6 @@ work. Note that the Pyboard hardware SPI supports only 10.5MHz and 21MHz.
|
|||
In practice the 41ms update time is visually fast for most purposes except some
|
||||
games.
|
||||
|
||||
# Notes on OLED displays
|
||||
|
||||
## Power consumption
|
||||
|
||||
The power consumption of OLED displays is roughly proportional to the number
|
||||
|
|
|
@ -0,0 +1,470 @@
|
|||
# Display drivers for nano-gui
|
||||
|
||||
The nano-gui project currently supports three display technologies: OLED (color
|
||||
and monochrome), color TFT, and monochrome Sharp displays.
|
||||
|
||||
# Contents
|
||||
|
||||
1. [Introduction](./DRIVERS.md#1-introduction)
|
||||
1.1 [Color handling](./DRIVERS.md#11-color-handling)
|
||||
2. [Drivers for SSD1351](./DRIVERS.md#2-drivers-for-ssd1351) Color OLEDs
|
||||
3. [Drivers for SSD1331](./DRIVERS.md#3-drivers-for-ssd1331) Small color OLEDs
|
||||
4. [Drivers for ST7735R](./DRIVERS.md#4-drivers-for-st7735r) Small color TFTs
|
||||
5. [Drivers for ILI9341](./DRIVERS.md#5-drivers-for-ili9341) Large color TFTs
|
||||
6. [Drivers for sharp displays](./DRIVERS.md#6-drivers-for-sharp-displays) Large low power monochrome displays
|
||||
6.1 [Display characteristics](./DRIVERS.md#61-display-characteristics)
|
||||
6.1.1 [The VCOM bit](./DRIVERS.md#611-the-vcom-bit)
|
||||
6.1.2 [Refresh rate](./DRIVERS.md#612-refresh-rate)
|
||||
6.2 [Test scripts](./DRIVERS.md#62-test-scripts)
|
||||
6.3 [Device driver constructor](./DRIVERS.md#63-device-driver-constructor)
|
||||
6.3.1 [Device driver methods](./DRIVERS.md#631-device-driver-methods)
|
||||
6.3.2 [The vcom arg](./DRIVERS.md#632-the-vcom-arg)
|
||||
6.4 [Application design](./DRIVERS.md#64-application-design)
|
||||
6.4.1 [Micropower applications](./DRIVERS.md#641-micropower-applications)
|
||||
6.5 [Resources](./DRIVERS.md#65-resources)
|
||||
7. [Writing device drivers](./DRIVERS.md#7-writing-device-drivers)
|
||||
|
||||
###### [Main README](./README.md)
|
||||
|
||||
# 1. Introduction
|
||||
|
||||
With the exception of the Sharp displays use of these drivers is very simple:
|
||||
the main reason to consult this doc is to select the right driver for your
|
||||
display, platform and application.
|
||||
|
||||
An application specifies a driver by means of `color_setup.py` or
|
||||
`ssd1306_setup.py` located in the root directory of the target. This typically
|
||||
contains code along these lines:
|
||||
```python
|
||||
import machine
|
||||
import gc
|
||||
from drivers.ssd1351.ssd1351 import SSD1351 as SSD # Choose device driver
|
||||
pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
|
||||
pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
|
||||
prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
|
||||
spi = machine.SPI(2)
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
ssd = SSD(spi, pcs, pdc, prst, 96) # Create a display instance
|
||||
```
|
||||
In the interests of conserving RAM, supplied drivers support only the
|
||||
functionality required by the GUI. More fully featured drivers may better suit
|
||||
other applications.
|
||||
|
||||
## 1.1 Color handling
|
||||
|
||||
Most color displays support colors specified as 16-bit quantities. Storing two
|
||||
bytes for every pixel results in large frame buffers. Most of the drivers
|
||||
reduce this to 1 byte (the default) or 4 bits per pixel, with the data being
|
||||
expanded at runtime when a line is displayed. This trades a large saving in RAM
|
||||
for a small increase in refresh time. Minimising this increase while keeping
|
||||
the driver cross-platform involves the use of the `viper` decorator.
|
||||
|
||||
Eight bit drivers store colors in `rrrgggbb`. This results in a loss of
|
||||
precision in specifying a color. Four bit drivers store a color as the index
|
||||
into a 16 bit lookup table. There is no loss of precision but only 16 distinct
|
||||
colors can be supported.
|
||||
|
||||
The choice of 16, 8 or 4 bit drivers is largely transparent: all demo scripts
|
||||
run in a visually identical manner under all drivers. This will apply to any
|
||||
application which uses the predefined colors. Differences become apparent when
|
||||
specifying custom colors. For detail see the main README
|
||||
[User defined colors](./README.md#311-user-defined-colors).
|
||||
|
||||
# 2. Drivers for SSD1351
|
||||
|
||||
See [Adafruit 1.5" 128*128 OLED display](https://www.adafruit.com/product/1431)
|
||||
and [Adafruit 1.27" 128*96 display](https://www.adafruit.com/product/1673).
|
||||
|
||||
There are four versions.
|
||||
* `ssd1351.py` This is optimised for STM (e.g. Pyboard) platforms.
|
||||
* `ssd1351_generic.py` Cross-platform version. Tested on ESP32 and ESP8266.
|
||||
* `ssd1351_16bit.py` Cross-platform. Uses 16 bit RGB565 color.
|
||||
* `ssd1351_4bit.py` Cross-platform. Uses 4 bit color.
|
||||
|
||||
All these drivers work with the provided demo scripts.
|
||||
To conserve RAM the first two use 8 bit (rrrgggbb) color. This works well with
|
||||
the GUI if saturated colors are used to render text and controls.
|
||||
|
||||
The `ssd1351_generic.py` and 4 bit versions use the `micropython.viper`
|
||||
decorator. If your platform does not support this, comment it out and remove
|
||||
the type annotations. You may be able to use the `micropython.native`
|
||||
decorator.
|
||||
|
||||
If the platform supports the viper emitter performance should still be good: on
|
||||
a Pyboard V1 the generic driver perorms a refresh of a 128*128 color display in
|
||||
47ms. The STM version is faster but not by a large margin: a refresh takes
|
||||
41ms. 32ms of these figures is consumed by the data transfer over the SPI
|
||||
interface. The 4-bit version with Viper takes 44ms.
|
||||
|
||||
If the viper and native decorators are unsupported a screen redraw takes 272ms
|
||||
(on Pyboard 1.0) which is visibly slow.
|
||||
|
||||
The `ssd1351_16bit` version on a 128x128 display requires 32KiB for the frame
|
||||
buffer; this means it is only usable on platforms with plenty of RAM. Testing
|
||||
was done on a Pyboard D SF2W. With the GUI this version offers no benefit, but
|
||||
it delivers major advantages in applications such as rendering images.
|
||||
|
||||
For further information see the GUI README
|
||||
[User defined colors](./README.md#311-user-defined-colors).
|
||||
|
||||
This driver was tested on Adafruit 1.5 and 1.27 inch displays.
|
||||
|
||||
#### SSD1351 Constructor args:
|
||||
* `spi` An SPI bus instance.
|
||||
* `pincs` An initialised output pin. Initial value should be 1.
|
||||
* `pindc` An initialised output pin. Initial value should be 0.
|
||||
* `pinrs` An initialised output pin. Initial value should be 1.
|
||||
* `height=128` Display dimensions in pixels. Height must be 96 or 128.
|
||||
* `width=128`
|
||||
* `init_spi=spi_init` This optional arg enables flexible options in
|
||||
configuring the SPI bus. The default preserves existing behaviour: the SPI bus
|
||||
is initialised to 20MHz before each use. Other `spi.init` args are default.
|
||||
This facilitates bus sharing. Passing `False` disables this: `color_setup.py`
|
||||
must initialise the bus. Those settings will be left in place. If a callback
|
||||
function is passed, it will be called prior to each SPI bus write: this is for
|
||||
shared bus applications where a non-standard `init` is required. The callback
|
||||
will receive a single arg being the SPI bus instance. In normal use it will be
|
||||
a one-liner or lambda initialising the bus. The default is this function:
|
||||
```python
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=20_000_000) # Data sheet: should support 20MHz
|
||||
```
|
||||
|
||||
#### A "gotcha" in the datasheet
|
||||
|
||||
For anyone seeking to understand or modify the code, the datasheet para 8.3.2
|
||||
is confusing. They use the colors red, green and blue to represent colors C, B
|
||||
and A. With the setup used in these drivers, C is blue and A is red. The 16 bit
|
||||
color streams sent to the display are:
|
||||
s[x] 1st byte sent b7 b6 b5 b4 b3 g7 g6 g5
|
||||
s[x + 1] 2nd byte sent g4 g3 g2 r7 r6 r5 r4 r3
|
||||
|
||||
###### [Contents](./DRIVERS.md#contents)
|
||||
|
||||
# 3. Drivers for SSD1331
|
||||
|
||||
See [Adafruit 0.96" OLED display](https://www.adafruit.com/product/684).
|
||||
|
||||
There are two versions. Both are cross-platform.
|
||||
* `ssd1331.py` Uses 8 bit rrrgggbb color.
|
||||
* `ssd1331_16bit.py` Uses 16 bit RGB565 color.
|
||||
|
||||
The `ssd1331_16bit` version requires 12KiB of RAM for the frame buffer, while
|
||||
the standard version needs only 6KiB. For the GUI the standard version works
|
||||
well because text and controls are normally drawn with saturated colors.
|
||||
|
||||
The 16 bit version provides greatly improved results when rendering images.
|
||||
|
||||
#### SSD1331 Constructor args:
|
||||
* `spi` An SPI bus instance.
|
||||
* `pincs` An initialised output pin. Initial value should be 1.
|
||||
* `pindc` An initialised output pin. Initial value should be 0.
|
||||
* `pinrs` An initialised output pin. Initial value should be 1.
|
||||
* `height=64` Display dimensions in pixels.
|
||||
* `width=96`
|
||||
|
||||
This driver initialises the SPI clock rate and polarity as required by the
|
||||
device. The device can support clock rates of upto 6.66MHz.
|
||||
|
||||
# 4. Drivers for ST7735R
|
||||
|
||||
These are cross-platform but assume `micropython.viper` capability. They use
|
||||
8-bit color to minimise the RAM used by the frame buffer.
|
||||
* `st7735r.py` Supports [Adafruit 1.8" display](https://www.adafruit.com/product/358).
|
||||
* `st7735r144.py` Supports [Adafruit 1.44" display](https://www.adafruit.com/product/2088).
|
||||
|
||||
Users of other ST7735R based displays should beware: there are many variants
|
||||
with differing setup requirements.
|
||||
[This driver](https://github.com/boochow/MicroPython-ST7735/blob/master/ST7735.py)
|
||||
has four different initialisation routines for various display versions. Even
|
||||
the supported Adafruit displays differ in their initialisation settings.
|
||||
|
||||
If your Chinese display doesn't work with my drivers you are on your own: I
|
||||
can't support hardware I don't possess.
|
||||
|
||||
#### ST7735R Constructor args:
|
||||
* `spi` An initialised SPI bus instance. The device can support clock rates of
|
||||
upto 15MHz.
|
||||
* `cs` An initialised output pin. Initial value should be 1.
|
||||
* `dc` An initialised output pin. Initial value should be 0.
|
||||
* `rst` An initialised output pin. Initial value should be 1.
|
||||
* `height=128` Display dimensions in pixels. For portrait mode exchange
|
||||
`height` and `width` values.
|
||||
* `width=160`
|
||||
* `usd=False` Upside down: set `True` to invert display.
|
||||
* `init_spi=False` This optional arg enables flexible options in configuring
|
||||
the SPI bus. The default assumes exclusive access to the bus with
|
||||
`color_setup.py` initialising it. Those settings will be left in place. If a
|
||||
callback function is passed, it will be called prior to each SPI bus write:
|
||||
this is for shared bus applications. The callback will receive a single arg
|
||||
being the SPI bus instance. In normal use it will be a one-liner or lambda
|
||||
initialising the bus. A minimal example is this function:
|
||||
```python
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=12_000_000) # Data sheet: max is 12MHz
|
||||
```
|
||||
|
||||
# 5. Drivers for ILI9341
|
||||
|
||||
Adafruit make several displays using this chip, for example
|
||||
[this 3.2 inch unit](https://www.adafruit.com/product/1743).
|
||||
|
||||
#### ILI9341 Constructor args:
|
||||
* `spi` An initialised SPI bus instance. The device can support clock rates of
|
||||
upto 10MHz.
|
||||
* `cs` An initialised output pin. Initial value should be 1.
|
||||
* `dc` An initialised output pin. Initial value should be 0.
|
||||
* `rst` An initialised output pin. Initial value should be 1.
|
||||
* `height=240` Display dimensions in pixels. For portrait mode exchange
|
||||
`height` and `width` values.
|
||||
* `width=320`
|
||||
* `usd=False` Upside down: set `True` to invert display.
|
||||
* `split=False` By default the entire display is refreshed by the `show`
|
||||
method. A partial update may be specified for use with `uasyncio`. See below.
|
||||
* `init_spi=False` This optional arg enables flexible options in configuring
|
||||
the SPI bus. The default assumes exclusive access to the bus with
|
||||
`color_setup.py` initialising it. Those settings will be left in place. If a
|
||||
callback function is passed, it will be called prior to each SPI bus write:
|
||||
this is for shared bus applications. The callback will receive a single arg
|
||||
being the SPI bus instance. In normal use it will be a one-liner or lambda
|
||||
initialising the bus. A minimal example is this function:
|
||||
```python
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=10_000_000) # Data sheet: max is 10MHz
|
||||
```
|
||||
|
||||
The ILI9341 class uses 4-bit color to conserve RAM. Even with this adaptation
|
||||
the buffer size is 37.5KiB. See [Color handling](./DRIVERS.md#11-color-handling)
|
||||
for details of the implications of 4-bit color.
|
||||
|
||||
The driver uses the `micropython.viper` decorator. If your platform does not
|
||||
support this, comment it out and remove the type annotations. You may be able
|
||||
to use the `micropython.native` decorator.
|
||||
|
||||
#### Use with uasyncio
|
||||
|
||||
A full refresh blocks for ~200ms. This may be unacceptable for some `uasyncio`
|
||||
applications. The `split` constructor arg limits the number of display lines
|
||||
which are updated at one time, reducing the blocking time. To use this, an
|
||||
integer value of 2, 4, or 8 should be passed. For example to reduce blocking by
|
||||
a factor of ~4 to 50ms the `split` constructor arg is set to 4.
|
||||
|
||||
For any value the following keeps the display updated:
|
||||
```python
|
||||
import uasyncio as asyncio
|
||||
from gui.core.nanogui import refresh
|
||||
|
||||
async def keep_refreshed(ssd):
|
||||
while True:
|
||||
refresh(ssd) # Blocks for a period defined by split
|
||||
await asyncio.sleep_ms(0)
|
||||
```
|
||||
|
||||
###### [Contents](./DRIVERS.md#contents)
|
||||
|
||||
# 6. Drivers for sharp displays
|
||||
|
||||
These monochrome SPI displays exist in three variants from Adafruit.
|
||||
1. [2.7 inch 400x240 pixels](https://www.adafruit.com/product/4694)
|
||||
2. [1.3 inch 144x168](https://www.adafruit.com/product/3502)
|
||||
3. [1.3 inch 96x96](https://www.adafruit.com/product/1393) - Discontinued.
|
||||
|
||||
I have tested on the first of these. However the
|
||||
[Adfruit driver](https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay)
|
||||
supports all of these and I would expect this one also to do so.
|
||||
|
||||
## 6.1. Display characteristics
|
||||
|
||||
These displays have extremely low current consumption: I measured ~90μA on the
|
||||
2.7" board when in use. Refresh is fast, visually excellent and can run at up
|
||||
to 20Hz. This contrasts with ePaper (eInk) displays where refresh is slow
|
||||
(seconds) and visually intrusive; an alternative fast mode overcomes this, but
|
||||
at the expense of ghosting.
|
||||
|
||||
On the other hand the power consumption of ePaper can be zero (you can switch
|
||||
them off and the display is retained). If you power down a Sharp display the
|
||||
image is retained, but only for a few seconds. In a Pyboard context 90μA is low
|
||||
in comparison to stop mode and battery powered applications should be easily
|
||||
realised.
|
||||
|
||||
The 2.7" display has excellent resolution and can display fine lines and small
|
||||
fonts. In other respects the display quality is not as good as ePaper. For good
|
||||
contrast best results are achieved if the viewing angle and the direction of
|
||||
the light source are positioned to achieve reflection.
|
||||
|
||||
### 6.1.1 The VCOM bit
|
||||
|
||||
The significance of this is somewhat glossed-over in the Adafruit docs, and a
|
||||
study of the datasheet is confusing in the absence of prior knowledge of LCD
|
||||
technology.
|
||||
|
||||
The signals applied to an LCD display should have no DC component. This is
|
||||
because DC can cause gradual electrolysis and deterioration of of the liquid
|
||||
crystal material. Display driver hardware typically has an oscillator driving
|
||||
exclusive-or gates such that antiphase signals are applied for ON pixels, and
|
||||
in-phase for OFF pixels. The oscillator typically drives a D-type flip-flop to
|
||||
ensure an accurate 1:1 mark space ratio and hence zero DC component.
|
||||
|
||||
These displays offer two ways of achieving this, in the device driver or using
|
||||
an external 1:1 mark space logic signal. The bit controlling this is known as
|
||||
`VCOM` and the external pins supporting it are `EXTMODE` and `EXTCOMIN`.
|
||||
`EXTMODE` determines whether a hardware input is used (`Vcc`) or software
|
||||
control is required (`Gnd`). It is pulled low.
|
||||
|
||||
The driver supports software control, in that `VCOM` is complemented each time
|
||||
the display is refreshed. The Adafruit driver also does this.
|
||||
|
||||
Sofware control implies that, in long running applications, the display should
|
||||
regularly be refreshed. The datasheet incicates that the maximum rate is 20Hz,
|
||||
but a 1Hz rate is sufficient.
|
||||
|
||||
If hardware control is to be used, `EXTMODE` should be linked to `Vcc` and a
|
||||
1:1 logic signal applied to `EXTCOMIN`. A frequency range of 0.5-10Hz is
|
||||
specified, and the datasheet also specifies "`EXTCOMIN` frequency should be
|
||||
made lower than frame frequency".
|
||||
|
||||
In my opinion the easiest way to deal with this is usually to use software
|
||||
control, ensuring that the driver's `show` method is called at regular
|
||||
intervals of at least 1Hz.
|
||||
|
||||
### 6.1.2 Refresh rate
|
||||
|
||||
The datasheet specifies a minimum refresh rate of 1Hz.
|
||||
|
||||
## 6.2. Test scripts
|
||||
|
||||
1. `sharptest.py` Basic functionality test.
|
||||
2. `clocktest.py` Digital and analog clock display.
|
||||
3. `clock_batt.py` As above but designed for low power operation. Pyboard
|
||||
specific.
|
||||
|
||||
Tests assume that `nanogui` is installed as per the instructions. `sharptest`
|
||||
should not be run for long periods as it does not regularly refresh the
|
||||
display. It tests `writer.py` and some `framebuffer` graphics primitives.
|
||||
`clocktest` demostrates use with `nanogui`.
|
||||
|
||||
The `clock_batt.py` demo needs `upower.py` from
|
||||
[micropython-micropower](https://github.com/peterhinch/micropython-micropower).
|
||||
|
||||
Testing was done on a Pyboard D SF6W: frozen bytecode was not required. I
|
||||
suspect a Pyboard 1.x would require it to prevent memory errors. Fonts in
|
||||
particular benefit from freezing as their RAM usage is radically reduced.
|
||||
|
||||
## 6.3. Device driver constructor
|
||||
|
||||
Positional args:
|
||||
1. `spi` An SPI bus instance. The constructor initialises this to the baudrate
|
||||
and bit order required by the hardware.
|
||||
2. `pincs` A `Pin` instance. The caller should initialise this as an output
|
||||
with value 0 (unusually the hardware CS line is active high).
|
||||
3. `height=240` Dimensions in pixels. Defaults are for 2.7" display.
|
||||
4. `width=400`
|
||||
5. `vcom=False` Accept the default unless using `pyb.standby`. See 3.2.
|
||||
|
||||
### 6.3.1 Device driver methods
|
||||
|
||||
1. `show` No args. Transfers the framebuffer contents to the device, updating
|
||||
the display.
|
||||
2. `update` Toggles the `VCOM` bit without transferring the framebuffer. This
|
||||
is a power saving method for cases where the application calls `show` at a
|
||||
rate of < 1Hz. In such cases `update` should be called at a 1Hz rate.
|
||||
|
||||
### 6.3.2 The vcom arg
|
||||
|
||||
It purpose is to support micropower applications which use `pyb.standby`.
|
||||
Wakeup from standby is similar to a reboot in that program execution starts
|
||||
from scratch. In the case where the board wakes up, writes to the display, and
|
||||
returns to standby, the `VCOM` bit would never change. In this case the
|
||||
application should store a `bool` in peristent storage, toggling it on each
|
||||
restart, and pass that to the constructor.
|
||||
|
||||
Persistent storage exists in the RTC registers and backup RAM. See
|
||||
[micopython-micropower](https://github.com/peterhinch/micropython-micropower)
|
||||
for details of how to acces these resources.
|
||||
|
||||
## 6.4. Application design
|
||||
|
||||
In all cases the frame buffer is located on the target hardware. In the case of
|
||||
the 2.7 inch display this is 400*240//8 = 12000 bytes in size. This should be
|
||||
instantiated as soon as possible in the application to ensure that sufficient
|
||||
contiguous RAM is available.
|
||||
|
||||
### 6.4.1 Micropower applications
|
||||
|
||||
These comments largely assume a Pyboard host. The application should import
|
||||
`upower` from
|
||||
[micropython-micropower](https://github.com/peterhinch/micropython-micropower).
|
||||
This turns the USB interface off if not in use to conserve power. It also
|
||||
provides an `lpdelay` function to implement a delay using `pyb.stop()` to
|
||||
conserve power.
|
||||
|
||||
In tests the `clock_batt` demo consumed 700μA between updates. A full refresh
|
||||
every 30s consumed about 48mA for 128ms. These figures correspond to a mean
|
||||
current consumption of 904μA implying about 46 days operation per AH of
|
||||
battery capacity. LiPo cells of 2AH capacity are widely available offering a
|
||||
theoretical runtime of 92 days between charges.
|
||||
|
||||
Lower currents might be achieved using standby but I have major doubts. This is
|
||||
because it is necessary to toggle the VCOM bit at a minimum of 1Hz. Waking from
|
||||
standby uses significan amounts of power as the modules are compiled. Even if
|
||||
frozen bytecode is used, there is still significant power usage importing
|
||||
modules and instantiating classes; this usage is not incurred in the loop in
|
||||
the demo.
|
||||
|
||||
## 6.5. Resources
|
||||
|
||||
[Schematic for 2.7" unit](https://learn.adafruit.com/assets/94077)
|
||||
|
||||
[Datasheet 2.7"](https://cdn-learn.adafruit.com/assets/assets/000/094/215/original/LS027B7DH01_Rev_Jun_2010.pdf?1597872422)
|
||||
|
||||
[Datasheet 1.3"](http://www.adafruit.com/datasheets/LS013B4DN04-3V_FPC-204284.pdf)
|
||||
|
||||
###### [Contents](./DRIVERS.md#contents)
|
||||
|
||||
# 7. Writing device drivers
|
||||
|
||||
Device drivers capable of supporting `nanogui` can be extremely simple: see
|
||||
`drivers/sharp/sharp.py` for a minimal example. It should be noted that the
|
||||
supplied device drivers are designed purely to support nanogui. To conserve RAM
|
||||
they provide no functionality beyond the transfer of an external frame buffer
|
||||
to the device. This transfer typically takes a few tens of milliseconds. While
|
||||
visually instant, this period constitutes latency between an event occurring
|
||||
and a consequent display update. This may be unacceptable in applications such
|
||||
as games. In such cases the `FrameBuffer` approach is inappropriate. Many
|
||||
driver chips support graphics primitives in hardware; drivers using these
|
||||
capabilities will be faster than those provided here and may often be found
|
||||
using a forum search.
|
||||
|
||||
For a driver to support `nanogui` it must be subclassed from
|
||||
`framebuf.FrameBuffer` and provide `height` and `width` bound variables being
|
||||
the display size in pixels. This, and a `show` method, are all that is required
|
||||
for monochrome drivers.
|
||||
|
||||
Refresh must be handled by a `show` method taking no arguments; when called,
|
||||
the contents of the buffer underlying the `FrameBuffer` must be copied to the
|
||||
hardware.
|
||||
|
||||
For color drivers, to conserve RAM it is suggested that 8-bit color is used
|
||||
for the `FrameBuffer`. If the hardware does not support this, conversion to the
|
||||
supported color space needs to be done "on the fly" as per the SSD1351 driver.
|
||||
This uses `framebuf.GS8` to stand in for 8 bit color in `rrrgggbb` format. To
|
||||
maximise update speed consider using native, viper or assembler for the
|
||||
conversion, typically to RGB565 format.
|
||||
|
||||
Color drivers should have a static method converting rgb(255, 255, 255) to a
|
||||
form acceptable to the driver. For 8-bit rrrgggbb this can be:
|
||||
```python
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
|
||||
```
|
||||
This should be amended if the hardware uses a different 8-bit format.
|
||||
|
||||
The `Writer` (monochrome) or `CWriter` (color) classes and the `nanogui` module
|
||||
should then work automatically.
|
||||
|
||||
Drivers for displays using I2C may need to use
|
||||
[I2C.writevto](http://docs.micropython.org/en/latest/library/machine.I2C.html?highlight=writevto#machine.I2C.writevto)
|
||||
depending on the chip requirements.
|
||||
|
||||
###### [Contents](./DRIVERS.md#contents)
|
285
README.md
285
README.md
|
@ -2,9 +2,10 @@ A lightweight and minimal MicroPython GUI library for display drivers based on
|
|||
the `FrameBuffer` class. Various display technologies are supported, including
|
||||
small color and monochrome OLED's and color TFT's. The GUI is cross-platform.
|
||||
|
||||
These images, most from OLED displays, are poor. OLEDs are visually impressive
|
||||
displays with bright colors, wide viewing angle and extreme contrast. For some
|
||||
reason I find them hard to photograph well.
|
||||
These images, most from OLED displays, fail to reproduce the quality of these
|
||||
displays. OLEDs are visually impressive displays with bright colors, wide
|
||||
viewing angle and extreme contrast. For some reason I find them hard to
|
||||
photograph.
|
||||
![Image](images/clock.png) The aclock.py demo.
|
||||
|
||||
![Image](images/fonts.png) Label objects in two fonts.
|
||||
|
@ -22,9 +23,9 @@ Cartesian and polar plots, also realtime plotting including time series.
|
|||
|
||||
![Image](images/sine.png) A sample image from the plot module.
|
||||
|
||||
The following images are from a different display but illustrate the widgets.
|
||||
These images are from a TFT display. They illustrate the widgets.
|
||||
![Image](images/scale.JPG) The Scale widget. Capable of precision display of
|
||||
floats.
|
||||
floats as the scale moves behind its small window.
|
||||
|
||||
![Image](images/textbox1.JPG) The Textbox widget for scrolling text.
|
||||
|
||||
|
@ -34,7 +35,7 @@ wiring details, pin names and hardware issues.
|
|||
# Contents
|
||||
|
||||
1. [Introduction](./README.md#1-introduction)
|
||||
1.1 [Update](./README.md#11-update)
|
||||
1.1 [Change log](./README.md#11-change-log)
|
||||
1.2 [Description](./README.md#12-description)
|
||||
1.3 [Quick start](./README.md#13-quick-start)
|
||||
2. [Files and Dependencies](./README.md#2-files-and-dependencies)
|
||||
|
@ -48,7 +49,7 @@ wiring details, pin names and hardware issues.
|
|||
2.2.2 [Color use](./README.md#222-color-use)
|
||||
3. [The nanogui module](./README.md#3-the-nanogui-module)
|
||||
3.1 [Application Initialisation](./README.md#31-application-initialisation) Initial setup and refresh method.
|
||||
3.1.1 [Setup file internals](./README.md#311-setup-file-internals)
|
||||
3.1.1 [User defined colors](./README.md#311-user-defined-colors)
|
||||
3.2 [Label class](./README.md#32-label-class) Dynamic text at any screen location.
|
||||
3.3 [Meter class](./README.md#33-meter-class) A vertical panel meter.
|
||||
3.4 [LED class](./README.md#34-led-class) Virtual LED of any color.
|
||||
|
@ -56,55 +57,59 @@ wiring details, pin names and hardware issues.
|
|||
or compass style display of one or more pointers.
|
||||
3.6 [Scale class](./README.md#36-scale-class) Linear display with wide dynamic range.
|
||||
3.7 [Class Textbox](./README.md#37-class-textbox) Scrolling text display.
|
||||
4. [Device drivers](./README.md#4-device-drivers) Device driver compatibility
|
||||
requirements (these are minimal).
|
||||
5. [ESP8266](./README.md#5-esp8266) This can work. Contains information on
|
||||
4. [ESP8266](./README.md#4-esp8266) This can work. Contains information on
|
||||
minimising the RAM and flash footprints of the GUI.
|
||||
5. [Hardware configuration](./README.md#5-hardware-configuration) How to write
|
||||
color_setup.py
|
||||
|
||||
# 1. Introduction
|
||||
|
||||
This library provides a limited set of GUI objects (widgets) for displays whose
|
||||
display driver is subclassed from the `FrameBuffer` class. Such drivers can be
|
||||
tiny as the graphics primitives are supplied by the `FrameBuffer` class.
|
||||
|
||||
The GUI is display-only and lacks provision for user input. Displays with touch
|
||||
overlays are physically large, with correspondingly high pixel counts. Such
|
||||
displays would require large frame buffers. These would consume RAM and be slow
|
||||
to copy to the display. A `FrameBuffer` based driver is ill-suited to large
|
||||
displays. Drivers should use graphics primitives hosted on the display
|
||||
controller chip.
|
||||
tiny as the graphics primitives are supplied by the `FrameBuffer` class. A
|
||||
range of device drivers is provided: [this doc](./DRIVERS.md) provides guidance
|
||||
on selecting the right driver for your display, platform and application.
|
||||
|
||||
The GUI is cross-platform. By default it is configured for a Pyboard (1.x or D).
|
||||
This doc explains how to configure for other platforms by adapting a single
|
||||
small file. The GUI supports multiple displays attached to a single target, but
|
||||
bear in mind the RAM requirements for multiple frame buffers. It is tested on
|
||||
the ESP32 reference board without SPIRAM. Running on ESP8266 is possible but
|
||||
frozen bytecode should be used owing to its restricted RAM.
|
||||
frozen bytecode must be used owing to its restricted RAM.
|
||||
|
||||
Authors of applications requiring touch should consider my touch GUI's for the
|
||||
following displays. These have internal buffers:
|
||||
It uses synchronous code but is compatible with `uasyncio`. Some demo programs
|
||||
illustrate this. Code is standard MicroPython, but some device drivers use the
|
||||
`native` and `viper` decorators.
|
||||
|
||||
The GUI is display-only and lacks provision for user input. Authors of
|
||||
applications requiring touch should consider the touch GUI's for the following
|
||||
displays:
|
||||
* [Official lcd160cr](https://github.com/peterhinch/micropython-lcd160cr-gui)
|
||||
* [RA8875 large displays](https://github.com/peterhinch/micropython_ra8875)
|
||||
* [SSD1963 large displays](https://github.com/peterhinch/micropython-tft-gui)
|
||||
|
||||
## 1.1 Update
|
||||
For historical reasons and to ensure consistency, code and documentation for
|
||||
my GUI's employ the American spelling of `color`.
|
||||
|
||||
29 Nov 2020 Add ST7735R TFT drivers.
|
||||
## 1.1 Change log
|
||||
|
||||
15 Dec 2020 Add ILI9341 driver, 4-bit drivers and SPI bus sharing improvements.
|
||||
29 Nov 2020 Add ST7735R TFT drivers.
|
||||
17 Nov 2020 Add `Textbox` widget. `Scale` constructor arg `border` replaced by
|
||||
`bdcolor` as per other widgets.
|
||||
|
||||
5 Nov 2020
|
||||
This library has been refactored as a Python package. The aim is to reduce RAM
|
||||
usage: widgets are imported on demand rather than unconditionally. This enabled
|
||||
5 Nov 2020 - breaking change
|
||||
This library has been refactored as a Python package. This reduces RAM usage:
|
||||
widgets are imported on demand rather than unconditionally. This has enabled
|
||||
the addition of new widgets with zero impact on existsing applications. Another
|
||||
aim was to simplify installation with dependencies such as `writer` included in
|
||||
the tree. Finally hardware configuration is contained in a single file: details
|
||||
only need to be edited in one place to run all demo scripts.
|
||||
the tree. Finally hardware configuration is contained in a single script: only
|
||||
this file needs to be customised to run all demo scripts or to port an
|
||||
application to different hardware.
|
||||
|
||||
Existing users should re-install from scratch. In existing applications, import
|
||||
statements will need to be adapted as per the demos. The GUI API is otherwise
|
||||
unchanged.
|
||||
Users of versions prior to this refactor should re-install from scratch. In
|
||||
existing applications, import statements will need to be adapted as per the
|
||||
demos. The GUI API is otherwise unchanged.
|
||||
|
||||
## 1.2 Description
|
||||
|
||||
|
@ -113,16 +118,18 @@ Compatible and tested display drivers include:
|
|||
* The official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
|
||||
* The [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git).
|
||||
* The [Adafruit 0.96 inch color OLED](https://www.adafruit.com/product/684)
|
||||
with [this driver](https://github.com/peterhinch/micropython-nano-gui/tree/master/drivers/ssd1331).
|
||||
with [this driver](./DRIVERS.md#3-drivers-for-ssd1331).
|
||||
* A driver for [Adafruit 1.5 inch OLED](https://www.adafruit.com/product/1431)
|
||||
and [Adafruit 1.27 inch OLED](https://www.adafruit.com/product/1673) may be
|
||||
found [here](./drivers/ssd1351/README.md).
|
||||
and [Adafruit 1.27 inch OLED](https://www.adafruit.com/product/1673) is
|
||||
documented [here](./DRIVERS.md#2-drivers-for-ssd1351).
|
||||
* A driver for Sharp ultra low power consumption monochrome displays such as
|
||||
[2.7 inch 400x240 pixels](https://www.adafruit.com/product/4694)
|
||||
is [here](./drivers/sharp/README.md).
|
||||
is documented [here](./DRIVERS.md#6-drivers-for-sharp-displays).
|
||||
* Drivers for Adafruit ST7735R based TFT's:
|
||||
[1.8 inch](https://www.adafruit.com/product/358) and
|
||||
[1.44 inch](https://www.adafruit.com/product/2088).
|
||||
[1.44 inch](https://www.adafruit.com/product/2088) documented [here](./DRIVERS.md#4-drivers-for-st7735r).
|
||||
* Drivers for ILI9341 such as [Adafruit 3.2 inch](https://www.adafruit.com/product/1743)
|
||||
documented [here](./DRIVERS.md#5-drivers-for-ili9341).
|
||||
|
||||
Widgets are intended for the display of data from physical devices such as
|
||||
sensors. They are drawn using graphics primitives rather than icons to minimise
|
||||
|
@ -131,11 +138,6 @@ by hosts with restricted processing power. The approach also enables widgets to
|
|||
maximise information in ways that are difficult with icons, in particular using
|
||||
dynamic color changes in conjunction with moving elements.
|
||||
|
||||
Owing to RAM requirements and limitations on communication speed, `FrameBuffer`
|
||||
based display drivers are intended for physically small displays with limited
|
||||
numbers of pixels. The widgets are designed for displays as small as 0.96
|
||||
inches: this involves some compromises.
|
||||
|
||||
Copying the contents of the frame buffer to the display is relatively slow. The
|
||||
time depends on the size of the frame buffer and the interface speed, but the
|
||||
latency may be too high for applications such as games. For example the time to
|
||||
|
@ -165,22 +167,21 @@ OLED as per `color_setup.py`, move to the root directory of the repo and run
|
|||
Note also that the `gui.demos.aclock.py` demo comprises 38 lines of actual
|
||||
code. This stuff is easier than you might think.
|
||||
|
||||
###### [Contents](./README.md#contents)
|
||||
|
||||
# 2. Files and Dependencies
|
||||
|
||||
Firmware should be V1.13 or later.
|
||||
|
||||
Installation comprises copying the `gui` and `drivers` directories, with their
|
||||
contents, plus a hardware configuration file, to the target. The directory
|
||||
structure on the target must match that in the repo.
|
||||
|
||||
In the interests of conserving RAM, supplied drivers support only the
|
||||
functionality required by the GUI. More fully featured drivers may better suit
|
||||
other applications. See [section 4](./README.md#4-device-drivers).
|
||||
structure on the target must match that in the repo. This consumes about 300KiB
|
||||
of flash.
|
||||
|
||||
Filesystem space may be conserved by copying only the required driver from
|
||||
`drivers`, but the directory path to that file must be retained. For example,
|
||||
for SSD1351 displays only the following are actually required:
|
||||
`drivers/ssd1351/ssd1351.py`, `drivers/ssd1351/__init__.py`
|
||||
`drivers/ssd1351/ssd1351.py`, `drivers/ssd1351/__init__.py`.
|
||||
|
||||
## 2.1 Files
|
||||
|
||||
|
@ -189,7 +190,7 @@ for SSD1351 displays only the following are actually required:
|
|||
The root directory contains two example setup files, for monochrome and color
|
||||
displays respectively. Other examples may be found in the `color_setup`
|
||||
directory. These are templates for adaptation: only one file is copied to the
|
||||
target. On the target a color files should be named `color_setup.py`. The
|
||||
target. On the target a color file should be named `color_setup.py`. The
|
||||
monochrome `ssd1306_setup.py` retains its own name.
|
||||
|
||||
The chosen template will need to be edited to match the display in use, the
|
||||
|
@ -237,8 +238,8 @@ which occurs during transfer of the framebuffer to the display may affect more
|
|||
demanding `uasyncio` applications. More generally the GUI works well with it.
|
||||
|
||||
Demo scripts for Sharp displays are in `drivers/sharp`. Check source code for
|
||||
wiring details. See [the README](./drivers/sharp/README.md). They may be run as
|
||||
follows:
|
||||
wiring details. See [the docs](./DRIVERS.md#6-drivers-for-sharp-displays). They
|
||||
may be run as follows:
|
||||
```python
|
||||
import drivers.sharp.sharptest
|
||||
# or
|
||||
|
@ -249,7 +250,11 @@ import drivers.sharp.clocktest
|
|||
|
||||
Python font files are in the `gui/fonts` directory. The easiest way to conserve
|
||||
RAM is to freeze them which is highly recommended. In doing so the directory
|
||||
structure must be maintained. Python fonts may be created using
|
||||
structure must be maintained: the [ESP8266](./README.md#4-esp8266) provides an
|
||||
illustration.
|
||||
|
||||
To create alternatives, Python fonts may be generated from industry standard
|
||||
font files with
|
||||
[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git). The
|
||||
`-x` option for horizontal mapping must be specified. If fixed pitch rendering
|
||||
is required `-f` is also required. Supplied examples are:
|
||||
|
@ -271,20 +276,21 @@ copied to the hardware root as `color_setup.py`.
|
|||
* `esp32_setup.py` As written supports an ESP32 connected to a 128x128 SSD1351
|
||||
display. After editing to match the display and wiring, it should be copied to
|
||||
the target as `/pyboard/color_setup.py`.
|
||||
* `esp8266_setup.py` Similar for [ESP8266](./README.md#5-esp8266). Usage is
|
||||
* `esp8266_setup.py` Similar for [ESP8266](./README.md#4-esp8266). Usage is
|
||||
somewhat experimental.
|
||||
* `st7735r_setup.py` Assumes a Pyboard with an
|
||||
[Adafruit 1.8 inch TFT display](https://www.adafruit.com/product/358).
|
||||
* `st7735r144_setup.py` For a Pyboard with an
|
||||
[Adafruit 1.44 inch TFT display](https://www.adafruit.com/product/2088).
|
||||
* `ili9341_setup.py` A 240*320 ILI9341 display on ESP32.
|
||||
|
||||
## 2.2 Dependencies
|
||||
|
||||
The source tree now includes all dependencies. These are listed to enable users
|
||||
to check for newer versions.
|
||||
to check for newer versions:
|
||||
|
||||
* [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py)
|
||||
Provides text rendering.
|
||||
Provides text rendering of Python font files.
|
||||
|
||||
Optional feature:
|
||||
* An STM32 implementation of
|
||||
|
@ -301,46 +307,21 @@ in this repo but may be found here:
|
|||
* [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git)
|
||||
|
||||
The Sharp display is supported in `drivers/sharp`. See
|
||||
[README](/drivers/sharp/README.md) and demos.
|
||||
[README](./DRIVERS.md#6-drivers-for-sharp-displays) and demos.
|
||||
|
||||
### 2.2.2 Color use
|
||||
|
||||
Drivers for Adafruit 0.96", 1.27" and 1.5" OLEDS are included in the source
|
||||
tree. Each driver has its own small `README.md`. The default driver for the
|
||||
larger OLEDs is Pyboard specific, but there are slightly slower cross platform
|
||||
alternatives in the directory - see the code below for usage on ESP32.
|
||||
|
||||
If using the Adafruit 1.5 or 1.27 inch color OLED displays it is suggested that
|
||||
after installing the GUI the following script is pasted at the REPL. This will
|
||||
verify the hardware. Please change `height` to 128 if using the 1.5 inch
|
||||
display.
|
||||
This script performs a basic check that the `color_setup.py` file matches the
|
||||
hardware, that all three primary colors can be displayed and that pixels up to
|
||||
the edges of the display can be accessed.
|
||||
```python
|
||||
from machine import Pin, SPI
|
||||
from drivers.ssd1351.ssd1351 import SSD1351 as SSD # Pyboard-specific driver
|
||||
height = 96 # Ensure height is correct (96/128)
|
||||
pdc = Pin('Y1', Pin.OUT_PP, value=0)
|
||||
pcs = Pin('Y2', Pin.OUT_PP, value=1)
|
||||
prst = Pin('Y3', Pin.OUT_PP, value=1)
|
||||
spi = SPI(2)
|
||||
ssd = SSD(spi, pcs, pdc, prst, height=height)
|
||||
from color_setup import ssd # Create a display instance
|
||||
from gui.core.nanogui import refresh
|
||||
refresh(ssd, True) # Initialise and clear display.
|
||||
ssd.fill(0)
|
||||
ssd.line(0, 0, 127, height - 1, ssd.rgb(0, 255, 0)) # Green diagonal corner-to-corner
|
||||
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) # Red square at top left
|
||||
ssd.show()
|
||||
```
|
||||
On ESP32 the following may be used:
|
||||
```python
|
||||
from machine import Pin, SPI
|
||||
from drivers.ssd1351.ssd1351_generic import SSD1351 as SSD # Note generic driver
|
||||
height = 128 # Ensure height is correct (96/128)
|
||||
pdc = Pin(25, Pin.OUT, value=0)
|
||||
pcs = Pin(26, Pin.OUT, value=1)
|
||||
prst = Pin(27, Pin.OUT, value=1)
|
||||
spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
|
||||
ssd = SSD(spi, pcs, pdc, prst, height=height)
|
||||
ssd.fill(0)
|
||||
ssd.line(0, 0, 127, height - 1, ssd.rgb(0, 255, 0)) # Green diagonal corner-to-corner
|
||||
ssd.line(0, 0, ssd.width - 1, ssd.height - 1, ssd.rgb(0, 255, 0)) # Green diagonal corner-to-corner
|
||||
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) # Red square at top left
|
||||
ssd.rect(ssd.width -15, ssd.height -15, 15, 15, ssd.rgb(0, 0, 255)) # Blue square at bottm right
|
||||
ssd.show()
|
||||
```
|
||||
|
||||
|
@ -367,21 +348,19 @@ Text components of widgets are rendered using the `Writer` (monochrome) or
|
|||
|
||||
The GUI is initialised for color display by issuing:
|
||||
```python
|
||||
from color_setup import ssd, height
|
||||
from color_setup import ssd
|
||||
```
|
||||
This works as described [below](./README.md#311-setup-file-internals).
|
||||
This defines the hardware as described in [Hardware configuration](./README.md#5-hardware-configuration).
|
||||
|
||||
A typical application then imports `nanogui` modules and clears the display:
|
||||
```python
|
||||
from gui.core.nanogui import refresh
|
||||
from gui.widgets.label import Label # Import any widgets you plan to use
|
||||
from gui.widgets.dial import Dial, Pointer
|
||||
|
||||
refresh(ssd) # Initialise and clear display.
|
||||
refresh(ssd, True) # Initialise and clear display.
|
||||
```
|
||||
This is followed by Python fonts. A `CWriter` instance is created for each
|
||||
font (for monochrome displays a `Writer` is used). Upside down rendering is not
|
||||
supported. Only the `Textbox` widget supports scrolling text.
|
||||
Initialisation of text display follows. For each font a `CWriter` instance
|
||||
is created (for monochrome displays a `Writer` is used):
|
||||
```python
|
||||
from gui.core.writer import CWriter # Renders color text
|
||||
import gui.fonts.arial10 # A Python Font
|
||||
|
@ -392,35 +371,39 @@ CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it
|
|||
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False) # Colors are defaults
|
||||
wri.set_clip(True, True, False)
|
||||
```
|
||||
The application calls `nanogui.refresh` on initialisation to clear the display,
|
||||
then subsequently whenever a refresh is required. The method takes two args:
|
||||
Calling `nanogui.refresh` on startup sets up and clears the display. The method
|
||||
will subsequently be called whenever a refresh is required. It takes two args:
|
||||
1. `device` The display instance (the GUI supports multiple displays).
|
||||
2. `clear=False` If set `True` the display will be blanked; it is also
|
||||
blanked when a device is refreshed for the first time.
|
||||
|
||||
### 3.1.1 Setup file internals
|
||||
### 3.1.1 User defined colors
|
||||
|
||||
The file `color_setup.py` contains the hardware dependent code. It works as
|
||||
described below, with the aim of allocating the `FrameBuffer` before importing
|
||||
other modules. This is intended to reduce the risk of memory failures.
|
||||
The file `gui/core/colors.py` defines standard color constants which may be
|
||||
used with any display driver. This section describes how to change these or
|
||||
to create additional colors.
|
||||
|
||||
Firstly the file sets the display height and imports the driver:
|
||||
Most of the color display drivers define colors as 8-bit or larger values.
|
||||
In such cases colors may be created and assigned to variables as follows:
|
||||
```python
|
||||
height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch
|
||||
import machine
|
||||
import gc
|
||||
from drivers.ssd1351.ssd1351 import SSD1351 as SSD # Import the display driver
|
||||
from color_setup import SSD
|
||||
PALE_YELLOW = SSD.rgb(150, 150, 0)
|
||||
```
|
||||
It then sets up the bus (SPI or I2C) and instantiates the display. At this
|
||||
point the framebuffer is created:
|
||||
The GUI also provides drivers with 4-bit color to minimise RAM use. Colors are
|
||||
assigned to a lookup table having 16 entries. The frame buffer stores 4-bit
|
||||
color values, which are converted to the correct color depth for the hardware
|
||||
when the display is refreshed.
|
||||
|
||||
Of the possible 16 colors 13 are assigned in `gui/core/colors.py`, leaving
|
||||
color numbers 12, 13 and 14 free. Any color can be assigned as follows:
|
||||
```python
|
||||
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
|
||||
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
|
||||
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
|
||||
spi = machine.SPI(1)
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
|
||||
from gui.core.colors import * # Imports the create_color function
|
||||
PALE_YELLOW = create_color(12, 150, 150, 0)
|
||||
```
|
||||
This creates a color `rgb(150, 150, 0)` assigns it to "spare" color number 12
|
||||
then sets `PALE_YELLOW` to 12. Any color number in range `0 <= n <= 15` may be
|
||||
used (implying that predefined colors may be reassigned). It is recommended
|
||||
that `BLACK` (0) and `WHITE` (15) are not changed.
|
||||
|
||||
###### [Contents](./README.md#contents)
|
||||
|
||||
|
@ -436,7 +419,8 @@ Labels can be displayed with an optional single pixel border.
|
|||
|
||||
Colors are handled flexibly. By default the colors used are those of the
|
||||
`Writer` instance, however they can be changed dynamically; this might be used
|
||||
to warn of overrange or underrange values.
|
||||
to warn of overrange or underrange values. The `color15.py` demo illustrates
|
||||
this.
|
||||
|
||||
Constructor args:
|
||||
1. `writer` The `Writer` instance (font and screen) to use.
|
||||
|
@ -803,55 +787,10 @@ the oldest (topmost) being discarded as required.
|
|||
|
||||
###### [Contents](./README.md#contents)
|
||||
|
||||
# 4. Device drivers
|
||||
|
||||
Device drivers capable of supporting `nanogui` can be extremely simple: see
|
||||
`drivers/sharp/sharp.py` for a minimal example. It should be noted that the
|
||||
supplied device drivers are designed purely to support nanogui. To conserve RAM
|
||||
they provide no functionality beyond the transfer of an external frame buffer
|
||||
to the device. This transfer typically takes a few tens of milliseconds. While
|
||||
visually instant, this period constitutes latency between an event occurring
|
||||
and a consequent display update. This may be unacceptable in applications such
|
||||
as games. In such cases the `FrameBuffer` approach is inappropriate. Many
|
||||
driver chips support graphics primitives in hardware; drivers using these
|
||||
capabilities will be faster than those provided here and may often be found
|
||||
using a forum search.
|
||||
|
||||
For a driver to support `nanogui` it must be subclassed from
|
||||
`framebuf.FrameBuffer` and provide `height` and `width` bound variables being
|
||||
the display size in pixels. This, and a `show` method, are all that is required
|
||||
for monochrome drivers.
|
||||
|
||||
Refresh must be handled by a `show` method taking no arguments; when called,
|
||||
the contents of the buffer underlying the `FrameBuffer` must be copied to the
|
||||
hardware.
|
||||
|
||||
For color drivers, to conserve RAM it is suggested that 8-bit color is used
|
||||
for the `FrameBuffer`. If the hardware does not support this, conversion to the
|
||||
supported color space needs to be done "on the fly" as per the SSD1351 driver.
|
||||
This uses `framebuf.GS8` to stand in for 8 bit color in `rrrgggbb` format. To
|
||||
maximise update speed consider using native, viper or assembler for the
|
||||
conversion, typically to RGB565 format.
|
||||
|
||||
Color drivers should have a static method converting rgb(255, 255, 255) to a
|
||||
form acceptable to the driver. For 8-bit rrrgggbb this can be:
|
||||
```python
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
|
||||
```
|
||||
This should be amended if the hardware uses a different 8-bit format.
|
||||
|
||||
The `Writer` (monochrome) or `CWriter` (color) classes and the `nanogui` module
|
||||
should then work automatically.
|
||||
|
||||
Drivers for displays using I2C may need to use
|
||||
[I2C.writevto](http://docs.micropython.org/en/latest/library/machine.I2C.html?highlight=writevto#machine.I2C.writevto)
|
||||
depending on the chip requirements.
|
||||
|
||||
###### [Contents](./README.md#contents)
|
||||
|
||||
# 5. ESP8266
|
||||
# 4. ESP8266
|
||||
|
||||
Some personal observations on successful use with an ESP8266.
|
||||
|
||||
|
@ -890,3 +829,27 @@ In conclusion I think that applications of moderate complexity should be
|
|||
feasible.
|
||||
|
||||
###### [Contents](./README.md#contents)
|
||||
|
||||
# 5. Hardware configuration
|
||||
|
||||
The file `color_setup.py` contains the hardware dependent code. It works as
|
||||
described below, with the aim of allocating the `FrameBuffer` before importing
|
||||
other modules. This is intended to reduce the risk of memory failures.
|
||||
|
||||
Firstly the file sets the display height and imports the driver:
|
||||
```python
|
||||
height = 96 # 1.27 inch 96*128 (rows*cols) display. Set to 128 for 1.5 inch
|
||||
import machine
|
||||
import gc
|
||||
from drivers.ssd1351.ssd1351 import SSD1351 as SSD # Import the display driver
|
||||
```
|
||||
It then sets up the bus (SPI or I2C) and instantiates the display. At this
|
||||
point the framebuffer is created:
|
||||
```python
|
||||
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
|
||||
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
|
||||
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
|
||||
spi = machine.SPI(1)
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
ssd = SSD(spi, pcs, pdc, prst, height) # Create a display instance
|
||||
```
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# Edit the driver import for other displays.
|
||||
|
||||
# WIRING (Adafruit pin nos and names).
|
||||
# Pyb SSD
|
||||
# ESP SSD
|
||||
# 3v3 Vin (10)
|
||||
# Gnd Gnd (11)
|
||||
# IO25 DC (3 DC)
|
||||
|
@ -28,6 +28,6 @@ pdc = Pin(25, Pin.OUT, value=0) # Arbitrary pins
|
|||
pcs = Pin(26, Pin.OUT, value=1)
|
||||
prst = Pin(27, Pin.OUT, value=1)
|
||||
# Hardware SPI on native pins for performance
|
||||
spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
|
||||
spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12), init_spi=False)
|
||||
gc.collect()
|
||||
ssd = SSD(spi, pcs, pdc, prst, height=height)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# color_setup.py Customise for your hardware config
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2020 Peter Hinch
|
||||
|
||||
# As written, supports:
|
||||
# ili9341 240x320 displays on ESP32
|
||||
# Edit the driver import for other displays.
|
||||
|
||||
# Demo of initialisation procedure designed to minimise risk of memory fail
|
||||
# when instantiating the frame buffer. The aim is to do this as early as
|
||||
# possible before importing other modules.
|
||||
|
||||
# WIRING
|
||||
# ESP SSD
|
||||
# 3v3 Vin
|
||||
# Gnd Gnd
|
||||
# IO25 DC
|
||||
# IO26 CS
|
||||
# IO27 Rst
|
||||
# IO14 CLK Hardware SPI1
|
||||
# IO13 DATA (AKA SI MOSI)
|
||||
|
||||
from machine import Pin, SPI
|
||||
import gc
|
||||
|
||||
# *** Choose your color display driver here ***
|
||||
# ili9341 specific driver
|
||||
from drivers.ili93xx.ili9341 import ILI9341 as SSD
|
||||
|
||||
pdc = Pin(25, Pin.OUT, value=0) # Arbitrary pins
|
||||
pcs = Pin(26, Pin.OUT, value=1)
|
||||
prst = Pin(27, Pin.OUT, value=1)
|
||||
|
||||
# Kept as ssd to maintain compatability
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
|
||||
ssd = SSD(spi, dc=pdc, cs=pcs, rst=prst)
|
|
@ -0,0 +1,150 @@
|
|||
# ILI9341 nano-gui driver for ili9341 displays
|
||||
# As with all nano-gui displays, touch is not supported.
|
||||
|
||||
# Copyright (c) Peter Hinch 2020
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
# This work is based on the following sources.
|
||||
# https://github.com/rdagger/micropython-ili9341
|
||||
# Also this forum thread with ideas from @minyiky:
|
||||
# https://forum.micropython.org/viewtopic.php?f=18&t=9368
|
||||
|
||||
from time import sleep_ms
|
||||
import gc
|
||||
import framebuf
|
||||
|
||||
@micropython.viper
|
||||
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
|
||||
n = 0
|
||||
for x in range(length):
|
||||
c = source[x]
|
||||
d = (c & 0xf0) >> 3 # 2* pointers (lut is 16 bit color)
|
||||
e = (c & 0x0f) << 1
|
||||
dest[n] = lut[d]
|
||||
n += 1
|
||||
dest[n] = lut[d + 1]
|
||||
n += 1
|
||||
dest[n] = lut[e]
|
||||
n += 1
|
||||
dest[n] = lut[e + 1]
|
||||
n += 1
|
||||
|
||||
|
||||
class ILI9341(framebuf.FrameBuffer):
|
||||
|
||||
lut = bytearray(32)
|
||||
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3
|
||||
|
||||
# Transpose width & height for landscape mode
|
||||
def __init__(self, spi, cs, dc, rst, height=240, width=320,
|
||||
usd=False, split=False, init_spi=False):
|
||||
self._spi = spi
|
||||
self._cs = cs
|
||||
self._dc = dc
|
||||
self._rst = rst
|
||||
self.height = height
|
||||
self.width = width
|
||||
if split and split not in (2, 4, 8):
|
||||
raise ValueError('split must be 2, 4 or 8')
|
||||
self._spi_init = init_spi
|
||||
self._lines = 0 if not split else height // split # For uasyncio use: show n lines only
|
||||
self._line = 0 # Current line
|
||||
mode = framebuf.GS4_HMSB
|
||||
gc.collect()
|
||||
buf = bytearray(self.height * self.width // 2)
|
||||
self._mvb = memoryview(buf)
|
||||
super().__init__(buf, self.width, self.height, mode)
|
||||
self._linebuf = bytearray(self.width * 2)
|
||||
# Hardware reset
|
||||
self._rst(0)
|
||||
sleep_ms(50)
|
||||
self._rst(1)
|
||||
sleep_ms(50)
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(spi) # Bus may be shared
|
||||
# Send initialization commands
|
||||
self._wcmd(b'\x01') # SWRESET Software reset
|
||||
sleep_ms(100)
|
||||
self._wcd(b'\xcf', b'\x00\xC1\x30') # PWCTRB Pwr ctrl B
|
||||
self._wcd(b'\xed', b'\x64\x03\x12\x81') # POSC Pwr on seq. ctrl
|
||||
self._wcd(b'\xe8', b'\x85\x00\x78') # DTCA Driver timing ctrl A
|
||||
self._wcd(b'\xcb', b'\x39\x2C\x00\x34\x02') # PWCTRA Pwr ctrl A
|
||||
self._wcd(b'\xf7', b'\x20') # PUMPRC Pump ratio control
|
||||
self._wcd(b'\xea', b'\x00\x00') # DTCB Driver timing ctrl B
|
||||
self._wcd(b'\xc0', b'\x23') # PWCTR1 Pwr ctrl 1
|
||||
self._wcd(b'\xc1', b'\x10') # PWCTR2 Pwr ctrl 2
|
||||
self._wcd(b'\xc5', b'\x3E\x28') # VMCTR1 VCOM ctrl 1
|
||||
self._wcd(b'\xc7', b'\x86') # VMCTR2 VCOM ctrl 2
|
||||
# (b'\x88', b'\xe8', b'\x48', b'\x28')[rotation // 90]
|
||||
if self.height > self.width:
|
||||
self._wcd(b'\x36', b'\x48' if usd else b'\x88') # MADCTL: RGB portrait mode
|
||||
else:
|
||||
self._wcd(b'\x36', b'\x28' if usd else b'\xe8') # MADCTL: RGB landscape mode
|
||||
self._wcd(b'\x37', b'\x00') # VSCRSADD Vertical scrolling start address
|
||||
self._wcd(b'\x3a', b'\x55') # PIXFMT COLMOD: Pixel format
|
||||
self._wcd(b'\xb1', b'\x00\x18') # FRMCTR1 Frame rate ctrl
|
||||
self._wcd(b'\xb6', b'\x08\x82\x27') # DFUNCTR
|
||||
self._wcd(b'\xf2', b'\x00') # ENABLE3G Enable 3 gamma ctrl
|
||||
self._wcd(b'\x26', b'\x01') # GAMMASET Gamma curve selected
|
||||
self._wcd(b'\xe0', b'\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00') # GMCTRP1
|
||||
self._wcd(b'\xe1', b'\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F') # GMCTRN1
|
||||
self._wcmd(b'\x11') # SLPOUT Exit sleep
|
||||
sleep_ms(100)
|
||||
self._wcmd(b'\x29') # DISPLAY_ON Display on
|
||||
sleep_ms(100)
|
||||
|
||||
# Write a command.
|
||||
def _wcmd(self, buf):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(buf)
|
||||
self._cs(1)
|
||||
|
||||
# Write a command followed by a data arg.
|
||||
def _wcd(self, command, data):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(command)
|
||||
self._cs(1)
|
||||
self._dc(1)
|
||||
self._cs(0)
|
||||
self._spi.write(data)
|
||||
self._cs(1)
|
||||
|
||||
# No. of lines buffered vs time. Tested in portrait mode 240 pixels/line.
|
||||
# ESP32 at stock freq.
|
||||
# 24 lines 171ms
|
||||
# 2 lines 180ms
|
||||
# 1 line 196ms
|
||||
@micropython.native
|
||||
def show(self):
|
||||
clut = ILI9341.lut
|
||||
wd = self.width // 2
|
||||
ht = self.height
|
||||
lb = self._linebuf
|
||||
buf = self._mvb
|
||||
# Commands needed to start data write
|
||||
#self._wcd(b'\x2a', *ustruct.pack(">HH", 0, self.width)) # SET_COLUMN
|
||||
#self._wcd(b'\x2b', *ustruct.pack(">HH", 0, ht)) # SET_PAGE
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN
|
||||
self._wcd(b'\x2b', int.to_bytes(ht, 4, 'big')) # SET_PAGE
|
||||
self._wcmd(b'\x2c') # WRITE_RAM
|
||||
self._dc(1)
|
||||
self._cs(0)
|
||||
if self._lines:
|
||||
end = self._line + wd * self._lines
|
||||
for start in range(self._line, end, wd): # For each line
|
||||
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
|
||||
self._spi.write(lb)
|
||||
nxt = start + wd
|
||||
self._line = nxt % wd*ht
|
||||
else:
|
||||
for start in range(0, wd*ht, wd): # For each line
|
||||
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
|
||||
self._spi.write(lb)
|
||||
self._cs(1)
|
|
@ -1,157 +0,0 @@
|
|||
# A MICROPYTHON DRIVER FOR SHARP DISPLAYS
|
||||
|
||||
These monochrome SPI displays exist in three variants from Adafruit.
|
||||
1. [2.7 inch 400x240 pixels](https://www.adafruit.com/product/4694)
|
||||
2. [1.3 inch 144x168](https://www.adafruit.com/product/3502)
|
||||
3. [1.3 inch 96x96](https://www.adafruit.com/product/1393) - Discontinued.
|
||||
|
||||
I have tested on the first of these. However the
|
||||
[Adfruit driver](https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay)
|
||||
supports all of these and I would expect this one also to do so.
|
||||
|
||||
# 1. Display characteristics
|
||||
|
||||
These displays have extremely low current consumption: I measured ~90μA on the
|
||||
2.7" board when in use. Refresh is fast, visually excellent and can run at up
|
||||
to 20Hz. This contrasts with ePaper (eInk) displays where refresh is slow
|
||||
(seconds) and visually intrusive; an alternative fast mode overcomes this, but
|
||||
at the expense of ghosting.
|
||||
|
||||
On the other hand the power consumption of ePaper can be zero (you can switch
|
||||
them off and the display is retained). If you power down a Sharp display the
|
||||
image is retained, but only for a few seconds. In a Pyboard context 90μA is low
|
||||
in comparison to stop mode and battery powered applications should be easily
|
||||
realised.
|
||||
|
||||
The 2.7" display has excellent resolution and can display fine lines and small
|
||||
fonts. In other respects the display quality is not as good as ePaper. For good
|
||||
contrast best results are achieved if the viewing angle and the direction of
|
||||
the light source are positioned to achieve reflection.
|
||||
|
||||
## 1.1 The VCOM bit
|
||||
|
||||
The significance of this is somewhat glossed-over in the Adafruit docs, and a
|
||||
study of the datasheet is confusing in the absence of prior knowledge of LCD
|
||||
technology.
|
||||
|
||||
The signals applied to an LCD display should have no DC component. This is
|
||||
because DC can cause gradual electrolysis and deterioration of of the liquid
|
||||
crystal material. Display driver hardware typically has an oscillator driving
|
||||
exclusive-or gates such that antiphase signals are applied for ON pixels, and
|
||||
in-phase for OFF pixels. The oscillator typically drives a D-type flip-flop to
|
||||
ensure an accurate 1:1 mark space ratio and hence zero DC component.
|
||||
|
||||
These displays offer two ways of achieving this, in the device driver or using
|
||||
an external 1:1 mark space logic signal. The bit controlling this is known as
|
||||
`VCOM` and the external pins supporting it are `EXTMODE` and `EXTCOMIN`.
|
||||
`EXTMODE` determines whether a hardware input is used (`Vcc`) or software
|
||||
control is required (`Gnd`). It is pulled low.
|
||||
|
||||
The driver supports software control, in that `VCOM` is complemented each time
|
||||
the display is refreshed. The Adafruit driver also does this.
|
||||
|
||||
Sofware control implies that, in long running applications, the display should
|
||||
regularly be refreshed. The datasheet incicates that the maximum rate is 20Hz,
|
||||
but a 1Hz rate is sufficient.
|
||||
|
||||
If hardware control is to be used, `EXTMODE` should be linked to `Vcc` and a
|
||||
1:1 logic signal applied to `EXTCOMIN`. A frequency range of 0.5-10Hz is
|
||||
specified, and the datasheet also specifies "`EXTCOMIN` frequency should be
|
||||
made lower than frame frequency".
|
||||
|
||||
In my opinion the easiest way to deal with this is usually to use software
|
||||
control, ensuring that the driver's `show` method is called at regular
|
||||
intervals of at least 1Hz.
|
||||
|
||||
## 1.2 Refresh rate
|
||||
|
||||
The datasheet specifies a minimum refresh rate of 1Hz.
|
||||
|
||||
# 2. Test scripts
|
||||
|
||||
1. `sharptest.py` Basic functionality test.
|
||||
2. `clocktest.py` Digital and analog clock display.
|
||||
3. `clock_batt.py` As above but designed for low power operation. Pyboard
|
||||
specific.
|
||||
|
||||
Tests assume that `nanogui` is installed as per the instructions. `sharptest`
|
||||
should not be run for long periods as it does not regularly refresh the
|
||||
display. It tests `writer.py` and some `framebuffer` graphics primitives.
|
||||
`clocktest` demostrates use with `nanogui`.
|
||||
|
||||
The `clock_batt.py` demo needs `upower.py` from
|
||||
[micropython-micropower](https://github.com/peterhinch/micropython-micropower).
|
||||
|
||||
Testing was done on a Pyboard D SF6W: frozen bytecode was not required. I
|
||||
suspect a Pyboard 1.x would require it to prevent memory errors. Fonts in
|
||||
particular benefit from freezing as their RAM usage is radically reduced.
|
||||
|
||||
# 3. Device driver constructor
|
||||
|
||||
Positional args:
|
||||
1. `spi` An SPI bus instance. The constructor initialises this to the baudrate
|
||||
and bit order required by the hardware.
|
||||
2. `pincs` A `Pin` instance. The caller should initialise this as an output
|
||||
with value 0 (unusually the hardware CS line is active high).
|
||||
3. `height=240` Dimensions in pixels. Defaults are for 2.7" display.
|
||||
4. `width=400`
|
||||
5. `vcom=False` Accept the default unless using `pyb.standby`. See 3.2.
|
||||
|
||||
# 3.1 Device driver methods
|
||||
|
||||
1. `show` No args. Transfers the framebuffer contents to the device, updating
|
||||
the display.
|
||||
2. `update` Toggles the `VCOM` bit without transferring the framebuffer. This
|
||||
is a power saving method for cases where the application calls `show` at a
|
||||
rate of < 1Hz. In such cases `update` should be called at a 1Hz rate.
|
||||
|
||||
# 3.2 The vcom arg
|
||||
|
||||
It purpose is to support micropower applications which use `pyb.standby`.
|
||||
Wakeup from standby is similar to a reboot in that program execution starts
|
||||
from scratch. In the case where the board wakes up, writes to the display, and
|
||||
returns to standby, the `VCOM` bit would never change. In this case the
|
||||
application should store a `bool` in peristent storage, toggling it on each
|
||||
restart, and pass that to the constructor.
|
||||
|
||||
Persistent storage exists in the RTC registers and backup RAM. See
|
||||
[micopython-micropower](https://github.com/peterhinch/micropython-micropower)
|
||||
for details of how to acces these resources.
|
||||
|
||||
# 4. Application design
|
||||
|
||||
In all cases the frame buffer is located on the target hardware. In the case of
|
||||
the 2.7 inch display this is 400*240//8 = 12000 bytes in size. This should be
|
||||
instantiated as soon as possible in the application to ensure that sufficient
|
||||
contiguous RAM is available.
|
||||
|
||||
## 4.1 Micropower applications
|
||||
|
||||
These comments largely assume a Pyboard host. The application should import
|
||||
`upower` from
|
||||
[micropython-micropower](https://github.com/peterhinch/micropython-micropower).
|
||||
This turns the USB interface off if not in use to conserve power. It also
|
||||
provides an `lpdelay` function to implement a delay using `pyb.stop()` to
|
||||
conserve power.
|
||||
|
||||
In tests the `clock_batt` demo consumed 700μA between updates. A full refresh
|
||||
every 30s consumed about 48mA for 128ms. These figures correspond to a mean
|
||||
current consumption of 904μA implying about 46 days operation per AH of
|
||||
battery capacity. LiPo cells of 2AH capacity are widely available offering a
|
||||
theoretical runtime of 92 days between charges.
|
||||
|
||||
Lower currents might be achieved using standby but I have major doubts. This is
|
||||
because it is necessary to toggle the VCOM bit at a minimum of 1Hz. Waking from
|
||||
standby uses significan amounts of power as the modules are compiled. Even if
|
||||
frozen bytecode is used, there is still significant power usage importing
|
||||
modules and instantiating classes; this usage is not incurred in the loop in
|
||||
the demo.
|
||||
|
||||
# 5. Resources
|
||||
|
||||
[Schematic for 2.7" unit](https://learn.adafruit.com/assets/94077)
|
||||
|
||||
[Datasheet 2.7"](https://cdn-learn.adafruit.com/assets/assets/000/094/215/original/LS027B7DH01_Rev_Jun_2010.pdf?1597872422)
|
||||
|
||||
[Datasheet 1.3"](http://www.adafruit.com/datasheets/LS013B4DN04-3V_FPC-204284.pdf)
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# Drivers for SSD1331 (Adafruit 0.96" OLED display)
|
||||
|
||||
There are two versions. Both are designed to be cross-platform.
|
||||
* `ssd1331.py` Uses 8 bit rrrgggbb color.
|
||||
* `ssd1331_16bit.py` Uses 16 bit RGB565 color.
|
||||
|
||||
The `ssd1331_16bit` version requires 12KiB of RAM for the frame buffer, while
|
||||
the standard version needs only 6KiB. For the GUI the standard version works
|
||||
well because text and controls are normally drawn with saturated colors.
|
||||
|
||||
The 16 bit version provides greatly improved results when rendering images.
|
|
@ -1,29 +0,0 @@
|
|||
# Drivers for SSD1351
|
||||
|
||||
There are three versions.
|
||||
* `ssd1351.py` This is optimised for STM (e.g. Pyboard) platforms.
|
||||
* `ssd1351_generic.py` Cross-platform version. Tested on ESP32 and ESP8266.
|
||||
* `ssd1351_16bit.py` Cross-platform. Uses 16 bit RGB565 color.
|
||||
|
||||
To conserve RAM the first two use 8 bit (rrrgggbb) color. This works well with
|
||||
the GUI if saturated colors are used to render text and controls.
|
||||
|
||||
The `ssd1351_generic.py` version includes the `micropython.viper` decorator. If
|
||||
your platform does not support this, comment it out and remove the type
|
||||
annotations. You may be able to use the `micropython.native` decorator.
|
||||
|
||||
If the platform supports the viper emitter performance should still be good: on
|
||||
a Pyboard V1 this driver perorms a refresh of a 128*128 color display in 47ms.
|
||||
The STM version is faster but not by a large margin: a refresh takes 41ms. 32ms
|
||||
of these figures is consumed by the data transfer over the SPI interface.
|
||||
|
||||
If the viper and native decorators are unsupported a screen redraw takes 272ms
|
||||
(on Pyboard 1.0) which is visibly slow.
|
||||
|
||||
The `ssd1351_16bit` version on a 128x128 display requires 32KiB for the frame
|
||||
buffer; this means it is only usable on platforms with plenty of RAM. Testing
|
||||
was done on a Pyboard D SF2W. With the GUI this version offers little benefit,
|
||||
but it delivers major advantages in applications such as rendering images.
|
||||
|
||||
This driver was tested on official Adafruit 1.5 and 1.27 inch displays, also a
|
||||
Chinese 1.5 inch unit.
|
|
@ -8,27 +8,8 @@
|
|||
# This driver is based on the Adafruit C++ library for Arduino
|
||||
# https://github.com/adafruit/Adafruit-SSD1351-library.git
|
||||
|
||||
# The MIT License (MIT)
|
||||
|
||||
# Copyright (c) 2018 Peter Hinch
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# Copyright (c) Peter Hinch 2018-2020
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
import framebuf
|
||||
import utime
|
||||
|
@ -36,6 +17,11 @@ import gc
|
|||
import micropython
|
||||
from uctypes import addressof
|
||||
|
||||
# ESP32 produces 20MHz, Pyboard D SF2W: 15MHz, SF6W: 18MHz, Pyboard 1.1: 10.5MHz
|
||||
# OLED datasheet: should support 20MHz
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=20_000_000)
|
||||
|
||||
# Timings with standard emitter
|
||||
# 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms
|
||||
# Buffer transfer time = 272-240 = 32ms which accords with expected:
|
||||
|
@ -99,25 +85,26 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
def rgb(r, g, b):
|
||||
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
|
||||
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128):
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=spi_init):
|
||||
if height not in (96, 128):
|
||||
raise ValueError('Unsupported height {}'.format(height))
|
||||
self.spi = spi
|
||||
self.rate = 11000000 # See baudrate note above.
|
||||
self.spi_init = init_spi
|
||||
self.pincs = pincs
|
||||
self.pindc = pindc # 1 = data 0 = cmd
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
gc.collect()
|
||||
self.buffer = bytearray(self.height * self.width)
|
||||
super().__init__(self.buffer, self.width, self.height, self.mode)
|
||||
super().__init__(self.buffer, self.width, self.height, mode)
|
||||
self.linebuf = bytearray(self.width * 2)
|
||||
pinrs(0) # Pulse the reset line
|
||||
utime.sleep_ms(1)
|
||||
pinrs(1)
|
||||
utime.sleep_ms(1)
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(spi) # Bus may be shared
|
||||
# See above comment to explain this allocation-saving gibberish.
|
||||
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
|
||||
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
|
||||
|
@ -127,7 +114,6 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
gc.collect()
|
||||
|
||||
def _write(self, buf, dc):
|
||||
self.spi.init(baudrate=self.rate, polarity=1, phase=1)
|
||||
self.pincs(1)
|
||||
self.pindc(dc)
|
||||
self.pincs(0)
|
||||
|
@ -139,6 +125,8 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
def show(self):
|
||||
lb = self.linebuf
|
||||
buf = self.buffer
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(self.spi) # Bus may be shared
|
||||
self._write(b'\x5c', 0) # Enable data write
|
||||
if self.height == 128:
|
||||
for l in range(128):
|
||||
|
|
|
@ -7,38 +7,23 @@
|
|||
# This driver is based on the Adafruit C++ library for Arduino
|
||||
# https://github.com/adafruit/Adafruit-SSD1351-library.git
|
||||
|
||||
# The MIT License (MIT)
|
||||
|
||||
# Copyright (c) 2019 Peter Hinch
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# Copyright (c) Peter Hinch 2019-2020
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
import framebuf
|
||||
import utime
|
||||
import gc
|
||||
import micropython
|
||||
from uctypes import addressof
|
||||
import sys
|
||||
|
||||
# https://github.com/peterhinch/micropython-nano-gui/issues/2
|
||||
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
|
||||
# Keep 0,0 on STM as testing was done in that mode.
|
||||
_bs = 0 if sys.platform == 'esp32' else 1 # SPI bus state
|
||||
# Now using 0,0 on STM and ESP32
|
||||
|
||||
# ESP32 produces 20MHz, Pyboard D SF2W: 15MHz, SF6W: 18MHz, Pyboard 1.1: 10.5MHz
|
||||
# OLED datasheet: should support 20MHz
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=20_000_000) # Data sheet: should support 20MHz
|
||||
|
||||
# Initialisation commands in cmd_init:
|
||||
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
|
||||
|
@ -61,8 +46,7 @@ _bs = 0 if sys.platform == 'esp32' else 1 # SPI bus state
|
|||
# 0xb6, 1, # Precharge 2
|
||||
# 0xaf, # Display on
|
||||
|
||||
# SPI baudrate: Pyboard can produce 10.5MHz or 21MHz. Datasheet gives max of 20MHz.
|
||||
# Attempt to use 21MHz failed but might work on a PCB or with very short leads.
|
||||
|
||||
class SSD1351(framebuf.FrameBuffer):
|
||||
# Convert r, g, b in range 0-255 to a 16 bit colour value RGB565
|
||||
# acceptable to hardware: rrrrrggggggbbbbb
|
||||
|
@ -70,25 +54,26 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
def rgb(r, g, b):
|
||||
return ((r & 0xf8) << 5) | ((g & 0x1c) << 11) | (b & 0xf8) | ((g & 0xe0) >> 5)
|
||||
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128):
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=spi_init):
|
||||
if height not in (96, 128):
|
||||
raise ValueError('Unsupported height {}'.format(height))
|
||||
self.spi = spi
|
||||
self.rate = 11000000 # See baudrate note above.
|
||||
self.spi_init = init_spi
|
||||
self.pincs = pincs
|
||||
self.pindc = pindc # 1 = data 0 = cmd
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
self.mode = framebuf.RGB565
|
||||
mode = framebuf.RGB565
|
||||
gc.collect()
|
||||
self.buffer = bytearray(self.height * self.width * 2)
|
||||
super().__init__(self.buffer, self.width, self.height, self.mode)
|
||||
super().__init__(self.buffer, self.width, self.height, mode)
|
||||
self.mvb = memoryview(self.buffer)
|
||||
pinrs(0) # Pulse the reset line
|
||||
utime.sleep_ms(1)
|
||||
pinrs(1)
|
||||
utime.sleep_ms(1)
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(spi) # Bus may be shared
|
||||
# See above comment to explain this allocation-saving gibberish.
|
||||
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
|
||||
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
|
||||
|
@ -98,7 +83,6 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
gc.collect()
|
||||
|
||||
def _write(self, mv, dc):
|
||||
self.spi.init(baudrate=self.rate, polarity=_bs, phase=_bs)
|
||||
self.pincs(1)
|
||||
self.pindc(dc)
|
||||
self.pincs(0)
|
||||
|
@ -110,6 +94,8 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
def show(self):
|
||||
mvb = self.mvb
|
||||
bw = self.width * 2 # Width in bytes
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(self.spi) # Bus may be shared
|
||||
self._write(b'\x5c', 0) # Enable data write
|
||||
if self.height == 128:
|
||||
for l in range(128):
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
# SSD1351_4bit.py MicroPython driver for Adafruit color OLED displays.
|
||||
# This is cross-platform and uses 4 bit color for minimum RAM usage.
|
||||
|
||||
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431
|
||||
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
|
||||
# For wiring details see drivers/ADAFRUIT.md in this repo.
|
||||
|
||||
# This driver is based on the Adafruit C++ library for Arduino
|
||||
# https://github.com/adafruit/Adafruit-SSD1351-library.git
|
||||
|
||||
# Copyright (c) Peter Hinch 2020
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
import framebuf
|
||||
import utime
|
||||
import gc
|
||||
import micropython
|
||||
from uctypes import addressof
|
||||
|
||||
# https://github.com/peterhinch/micropython-nano-gui/issues/2
|
||||
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
|
||||
# Now using 0,0 on STM and ESP32
|
||||
|
||||
# ESP32 produces 20MHz, Pyboard D SF2W: 15MHz, SF6W: 18MHz, Pyboard 1.1: 10.5MHz
|
||||
# OLED datasheet: should support 20MHz
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=20_000_000) # Data sheet: should support 20MHz
|
||||
|
||||
@micropython.viper
|
||||
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
|
||||
n = 0
|
||||
for x in range(length):
|
||||
c = source[x]
|
||||
d = (c & 0xf0) >> 3 # 2* LUT indices (LUT is 16 bit color)
|
||||
e = (c & 0x0f) << 1
|
||||
dest[n] = lut[d]
|
||||
n += 1
|
||||
dest[n] = lut[d + 1]
|
||||
n += 1
|
||||
dest[n] = lut[e]
|
||||
n += 1
|
||||
dest[n] = lut[e + 1]
|
||||
n += 1
|
||||
|
||||
|
||||
# Initialisation commands in cmd_init:
|
||||
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
|
||||
# 0xae, # display off (sleep mode)
|
||||
# 0xb3, 0xf1, # clock div
|
||||
# 0xca, 0x7f, # mux ratio
|
||||
# 0xa0, 0x74, # setremap 0x74
|
||||
# 0x15, 0, 0x7f, # setcolumn
|
||||
# 0x75, 0, 0x7f, # setrow
|
||||
# 0xa1, 0, # set display start line
|
||||
# 0xa2, 0, # displayoffset
|
||||
# 0xb5, 0, # setgpio
|
||||
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
|
||||
# 0xb1, 0x32, # Precharge
|
||||
# 0xbe, 0x05, # vcommh
|
||||
# 0xa6, # normaldisplay
|
||||
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
|
||||
# 0xc7, 0x0f, # Master contrast
|
||||
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
|
||||
# 0xb6, 1, # Precharge 2
|
||||
# 0xaf, # Display on
|
||||
|
||||
|
||||
class SSD1351(framebuf.FrameBuffer):
|
||||
|
||||
lut = bytearray(32)
|
||||
|
||||
# Convert r, g, b in range 0-255 to a 16 bit colour value
|
||||
# LS byte goes into LUT offset 0, MS byte into offset 1
|
||||
# Same mapping in linebuf so LS byte is shifted out 1st
|
||||
# Note pretty colors in datasheet don't match actual colors. See doc.
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return (r & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (b & 0xf8)
|
||||
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=spi_init):
|
||||
if height not in (96, 128):
|
||||
raise ValueError('Unsupported height {}'.format(height))
|
||||
self.spi = spi
|
||||
self.pincs = pincs
|
||||
self.pindc = pindc # 1 = data 0 = cmd
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
self.spi_init = init_spi
|
||||
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
|
||||
gc.collect()
|
||||
self.buffer = bytearray(self.height * self.width // 2)
|
||||
super().__init__(self.buffer, self.width, self.height, mode)
|
||||
self.linebuf = bytearray(self.width * 2)
|
||||
pinrs(0) # Pulse the reset line
|
||||
utime.sleep_ms(1)
|
||||
pinrs(1)
|
||||
utime.sleep_ms(1)
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(spi) # Bus may be shared
|
||||
# See above comment to explain this allocation-saving gibberish.
|
||||
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
|
||||
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
|
||||
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
|
||||
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
|
||||
gc.collect()
|
||||
self.show()
|
||||
|
||||
def _write(self, buf, dc):
|
||||
self.pincs(1)
|
||||
self.pindc(dc)
|
||||
self.pincs(0)
|
||||
self.spi.write(buf)
|
||||
self.pincs(1)
|
||||
|
||||
# Write lines from the framebuf out of order to match the mapping of the
|
||||
# SSD1351 RAM to the OLED device.
|
||||
def show(self): # 44ms on Pyboard 1.x
|
||||
clut = SSD1351.lut
|
||||
lb = self.linebuf
|
||||
wd = self.width // 2
|
||||
buf = memoryview(self.buffer)
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(self.spi) # Bus may be shared
|
||||
self._write(b'\x5c', 0) # Enable data write
|
||||
if self.height == 128:
|
||||
for l in range(128):
|
||||
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126...
|
||||
start = l0 * wd
|
||||
_lcopy(lb, buf[start : start + wd], clut, wd)
|
||||
self._write(lb, 1) # Send a line
|
||||
else:
|
||||
for l in range(128):
|
||||
if l < 64:
|
||||
start = (63 -l) * wd
|
||||
_lcopy(lb, buf[start : start + wd], clut, wd)
|
||||
elif l < 96: # This is daft but I can't get setrow to work
|
||||
pass # Let RAM counter increase
|
||||
else:
|
||||
start = (191 - l) * wd
|
||||
_lcopy(lb, buf[start : start + wd], clut, wd)
|
||||
self._write(lb, 1) # Send a line
|
||||
|
|
@ -8,27 +8,9 @@
|
|||
|
||||
# This driver is based on the Adafruit C++ library for Arduino
|
||||
# https://github.com/adafruit/Adafruit-SSD1351-library.git
|
||||
# The MIT License (MIT)
|
||||
|
||||
# Copyright (c) 2018 Peter Hinch
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
# Copyright (c) Peter Hinch 2018-2020
|
||||
# Released under the MIT license see LICENSE
|
||||
|
||||
import framebuf
|
||||
import utime
|
||||
|
@ -39,8 +21,12 @@ from uctypes import addressof
|
|||
import sys
|
||||
# https://github.com/peterhinch/micropython-nano-gui/issues/2
|
||||
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
|
||||
# Keep 0,0 on STM as testing was done in that mode.
|
||||
_bs = 0 if sys.platform == 'esp32' else 1 # SPI bus state
|
||||
# Now using 0,0 on STM and ESP32
|
||||
|
||||
# ESP32 produces 20MHz, Pyboard D SF2W: 15MHz, SF6W: 18MHz, Pyboard 1.1: 10.5MHz
|
||||
# OLED datasheet: should support 20MHz
|
||||
def spi_init(spi):
|
||||
spi.init(baudrate=20_000_000) # Data sheet: should support 20MHz
|
||||
|
||||
# Timings with standard emitter
|
||||
# 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms
|
||||
|
@ -86,25 +72,26 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
def rgb(r, g, b):
|
||||
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
|
||||
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128):
|
||||
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=spi_init):
|
||||
if height not in (96, 128):
|
||||
raise ValueError('Unsupported height {}'.format(height))
|
||||
self.spi = spi
|
||||
self.rate = 20000000 # Data sheet: should support 20MHz
|
||||
self.spi_init = init_spi
|
||||
self.pincs = pincs
|
||||
self.pindc = pindc # 1 = data 0 = cmd
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
gc.collect()
|
||||
self.buffer = bytearray(self.height * self.width)
|
||||
super().__init__(self.buffer, self.width, self.height, self.mode)
|
||||
super().__init__(self.buffer, self.width, self.height, mode)
|
||||
self.linebuf = bytearray(self.width * 2)
|
||||
pinrs(0) # Pulse the reset line
|
||||
utime.sleep_ms(1)
|
||||
pinrs(1)
|
||||
utime.sleep_ms(1)
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(spi) # Bus may be shared
|
||||
# See above comment to explain this allocation-saving gibberish.
|
||||
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
|
||||
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
|
||||
|
@ -114,7 +101,6 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
self.show()
|
||||
|
||||
def _write(self, buf, dc):
|
||||
self.spi.init(baudrate=self.rate, polarity=_bs, phase=_bs)
|
||||
self.pincs(1)
|
||||
self.pindc(dc)
|
||||
self.pincs(0)
|
||||
|
@ -126,6 +112,8 @@ class SSD1351(framebuf.FrameBuffer):
|
|||
def show(self):
|
||||
lb = self.linebuf
|
||||
buf = memoryview(self.buffer)
|
||||
if self.spi_init: # A callback was passed
|
||||
self.spi_init(self.spi) # Bus may be shared
|
||||
self._write(b'\x5c', 0) # Enable data write
|
||||
if self.height == 128:
|
||||
for l in range(128):
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# Drivers for ST7735R
|
||||
|
||||
These are cross-platform but assume `micropython.viper` capability. They use
|
||||
8-bit color to minimise the RAM used by the frame buffer.
|
||||
* `st7735r.py` Supports [Adafruit 1.8" display](https://www.adafruit.com/product/358).
|
||||
* `st7735r144.py` Supports [Adafruit 1.44" display](https://www.adafruit.com/product/2088).
|
||||
|
||||
Users of other ST7735R based displays should beware: there are many variants
|
||||
with differing setup requirements.
|
||||
[This driver](https://github.com/boochow/MicroPython-ST7735/blob/master/ST7735.py)
|
||||
has four different initialisation routines for various display versions. Even
|
||||
the supported Adafruit displays differ in their initialisation settings.
|
||||
|
||||
If your Chinese display doesn't work with my drivers you are on your own: I
|
||||
can't support hardware I don't possess.
|
|
@ -51,21 +51,22 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
|
||||
|
||||
# rst and cs are active low, SPI is mode 0
|
||||
def __init__(self, spi, cs, dc, rst, height=128, width=160):
|
||||
def __init__(self, spi, cs, dc, rst, height=128, width=160, usd=False, init_spi=False):
|
||||
self._spi = spi
|
||||
self._rst = rst # Pins
|
||||
self._dc = dc
|
||||
self._cs = cs
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
self._spi_init = init_spi
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
gc.collect()
|
||||
self.buffer = bytearray(height * width)
|
||||
self._mvb = memoryview(self.buffer)
|
||||
super().__init__(self.buffer, width, height, self.mode)
|
||||
buf = bytearray(height * width)
|
||||
self._mvb = memoryview(buf)
|
||||
super().__init__(buf, width, height, mode)
|
||||
self._linebuf = bytearray(int(width * 3 // 2)) # 12 bit color out
|
||||
self._init()
|
||||
self._init(usd)
|
||||
self.show()
|
||||
|
||||
# Hardware reset
|
||||
|
@ -97,8 +98,10 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
self._cs(1)
|
||||
|
||||
# Initialise the hardware. Blocks 500ms.
|
||||
def _init(self):
|
||||
def _init(self, usd):
|
||||
self._hwreset() # Hardware reset. Blocks 3ms
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
cmd = self._wcmd
|
||||
wcd = self._wcd
|
||||
cmd(b'\x01') # SW reset datasheet specifies > 120ms
|
||||
|
@ -119,7 +122,10 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
|
||||
cmd(b'\x20') # INVOFF
|
||||
# d7..d5 of MADCTL determine rotation/orientation
|
||||
wcd(b'\x36', b'\x20') # MADCTL: RGB landscape mode
|
||||
if self.height > self.width:
|
||||
wcd(b'\x36', b'\x80' if usd else b'\x40') # MADCTL: RGB portrait mode
|
||||
else:
|
||||
wcd(b'\x36', b'\xe0' if usd else b'\x20') # MADCTL: RGB landscape mode
|
||||
wcd(b'\x3a', b'\x03') # COLMOD 12 bit
|
||||
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
|
||||
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
|
||||
|
@ -139,6 +145,8 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
buf = self._mvb
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
self._spi.write(b'\x2c') # RAMWR
|
||||
self._dc(1)
|
||||
for start in range(wd * (ht - 1), -1, - wd): # For each line
|
||||
|
|
|
@ -49,19 +49,20 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
|
||||
|
||||
# rst and cs are active low, SPI is mode 0
|
||||
def __init__(self, spi, cs, dc, rst, height=128, width=128):
|
||||
def __init__(self, spi, cs, dc, rst, height=128, width=128, init_spi=False):
|
||||
self._spi = spi
|
||||
self._rst = rst # Pins
|
||||
self._dc = dc
|
||||
self._cs = cs
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
self._spi_init = init_spi
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
self.mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
|
||||
gc.collect()
|
||||
self.buffer = bytearray(self.height * self.width)
|
||||
self._mvb = memoryview(self.buffer)
|
||||
super().__init__(self.buffer, self.width, self.height, self.mode)
|
||||
buf = bytearray(self.height * self.width)
|
||||
self._mvb = memoryview(buf)
|
||||
super().__init__(buf, self.width, self.height, mode)
|
||||
self._linebuf = bytearray(self.width * 2) # 16 bit color out
|
||||
self._init()
|
||||
self.show()
|
||||
|
@ -97,6 +98,8 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
# Initialise the hardware. Blocks 500ms.
|
||||
def _init(self):
|
||||
self._hwreset() # Hardware reset. Blocks 3ms
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
cmd = self._wcmd
|
||||
wcd = self._wcd
|
||||
cmd(b'\x01') # SW reset datasheet specifies > 120ms
|
||||
|
@ -137,6 +140,8 @@ class ST7735R(framebuf.FrameBuffer):
|
|||
buf = self._mvb
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
self._spi.write(b'\x2c') # RAMWR
|
||||
self._dc(1)
|
||||
for start in range(wd * (ht - 1), -1, - wd): # For each line
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
# st7735r144.py Driver for ST7735R 1.44" LCD display for nano-gui
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2018-2020 Peter Hinch
|
||||
|
||||
# Supported display
|
||||
# Adafruit 1.44' Color TFT LCD Display with MicroSD Card breakout:
|
||||
# https://www.adafruit.com/product/2088
|
||||
|
||||
# Based on
|
||||
# https://github.com/adafruit/Adafruit_CircuitPython_ST7735R/blob/master/adafruit_st7735r.py
|
||||
# https://github.com/GuyCarver/MicroPython/blob/master/lib/ST7735.py
|
||||
# https://github.com/boochow/MicroPython-ST7735
|
||||
|
||||
# https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
|
||||
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
|
||||
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
|
||||
|
||||
from time import sleep_ms
|
||||
import framebuf
|
||||
import gc
|
||||
import micropython
|
||||
|
||||
# Datasheet para 8.4 scl write cycle 66ns == 15MHz
|
||||
|
||||
@micropython.viper
|
||||
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
|
||||
n = 0
|
||||
for x in range(length):
|
||||
c = source[x]
|
||||
d = (c & 0xf0) >> 3 # 2* LUT indices (LUT is 16 bit color)
|
||||
e = (c & 0x0f) << 1
|
||||
dest[n] = lut[d]
|
||||
n += 1
|
||||
dest[n] = lut[d + 1]
|
||||
n += 1
|
||||
dest[n] = lut[e]
|
||||
n += 1
|
||||
dest[n] = lut[e + 1]
|
||||
n += 1
|
||||
|
||||
|
||||
class ST7735R(framebuf.FrameBuffer):
|
||||
|
||||
lut = bytearray(32)
|
||||
|
||||
# Convert r, g, b in range 0-255 to a 16 bit colour value
|
||||
# LS byte goes into LUT offset 0, MS byte into offset 1
|
||||
# Same mapping in linebuf so LS byte is shifted out 1st
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return (b & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (r & 0xf8)
|
||||
|
||||
# rst and cs are active low, SPI is mode 0
|
||||
def __init__(self, spi, cs, dc, rst, height=128, width=128, init_spi=False):
|
||||
self._spi = spi
|
||||
self._rst = rst # Pins
|
||||
self._dc = dc
|
||||
self._cs = cs
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
self._spi_init = init_spi
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
|
||||
gc.collect()
|
||||
buf = bytearray(self.height * self.width // 2)
|
||||
self._mvb = memoryview(buf)
|
||||
super().__init__(buf, self.width, self.height, mode)
|
||||
self._linebuf = bytearray(self.width * 2) # 16 bit color out
|
||||
self._init()
|
||||
self.show()
|
||||
|
||||
# Hardware reset
|
||||
def _hwreset(self):
|
||||
self._dc(0)
|
||||
self._rst(1)
|
||||
sleep_ms(1)
|
||||
self._rst(0)
|
||||
sleep_ms(1)
|
||||
self._rst(1)
|
||||
sleep_ms(1)
|
||||
|
||||
# Write a command, a bytes instance (in practice 1 byte).
|
||||
def _wcmd(self, buf):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(buf)
|
||||
self._cs(1)
|
||||
|
||||
# Write a command followed by a data arg.
|
||||
def _wcd(self, c, d):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(c)
|
||||
self._cs(1)
|
||||
self._dc(1)
|
||||
self._cs(0)
|
||||
self._spi.write(d)
|
||||
self._cs(1)
|
||||
|
||||
# Initialise the hardware. Blocks 500ms.
|
||||
def _init(self):
|
||||
self._hwreset() # Hardware reset. Blocks 3ms
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
cmd = self._wcmd
|
||||
wcd = self._wcd
|
||||
cmd(b'\x01') # SW reset datasheet specifies > 120ms
|
||||
sleep_ms(150)
|
||||
cmd(b'\x11') # SLPOUT
|
||||
sleep_ms(256) # Adafruit delay (datsheet 120ms)
|
||||
wcd(b'\xb1', b'\x01\x2C\x2D') # FRMCTRL1
|
||||
wcd(b'\xb2', b'\x01\x2C\x2D') # FRMCTRL2
|
||||
wcd(b'\xb3', b'\x01\x2C\x2D\x01\x2C\x2D') # FRMCTRL3
|
||||
wcd(b'\xb4', b'\x07') # INVCTR line inversion
|
||||
|
||||
wcd(b'\xc0', b'\xa2\x02\x84') # PWCTR1 GVDD = 4.7V, 1.0uA
|
||||
wcd(b'\xc1', b'\xc5') # PWCTR2 VGH=14.7V, VGL=-7.35V
|
||||
wcd(b'\xc2', b'\x0a\x00') # PWCTR3 Opamp current small, Boost frequency
|
||||
wcd(b'\xc3', b'\x8a\x2a') # PWCTR4
|
||||
wcd(b'\xc4', b'\x8a\xee') # PWCTR5
|
||||
wcd(b'\xc5', b'\x0e') # VMCTR1 VCOMH = 4V, VOML = -1.1V NOTE I make VCOM == -0.775V
|
||||
|
||||
cmd(b'\x20') # INVOFF
|
||||
# d7..d5 of MADCTL determine rotation/orientation
|
||||
wcd(b'\x36', b'\xe0') # MADCTL: RGB landscape mode for 1.4" display
|
||||
wcd(b'\x3a', b'\x05') # COLMOD 16 bit
|
||||
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
|
||||
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
|
||||
|
||||
wcd(b'\x2a', int.to_bytes((3 << 16) + self.width + 2, 4, 'big')) # CASET
|
||||
wcd(b'\x2b', int.to_bytes((2 << 16) + self.height + 2, 4, 'big')) # RASET
|
||||
|
||||
cmd(b'\x13') # NORON
|
||||
sleep_ms(10)
|
||||
cmd(b'\x29') # DISPON
|
||||
sleep_ms(100)
|
||||
|
||||
def show(self): # Blocks 38.6ms on Pyboard D at stock frequency
|
||||
clut = ST7735R.lut
|
||||
wd = self.width // 2
|
||||
ht = self.height
|
||||
lb = self._linebuf
|
||||
buf = self._mvb
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
self._spi.write(b'\x2c') # RAMWR
|
||||
self._dc(1)
|
||||
for start in range(wd * (ht - 1), -1, - wd): # For each line
|
||||
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors (68us)
|
||||
self._spi.write(lb)
|
||||
self._cs(1)
|
|
@ -0,0 +1,157 @@
|
|||
# st7735r.py Driver for 1.8" 128*160 ST7735R LCD displays for nano-gui
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2018-2020 Peter Hinch
|
||||
|
||||
# Supported display
|
||||
# Adfruit 1.8' Color TFT LCD display with MicroSD Card Breakout:
|
||||
# https://www.adafruit.com/product/358
|
||||
|
||||
# Based on
|
||||
# https://github.com/adafruit/Adafruit_CircuitPython_ST7735R/blob/master/adafruit_st7735r.py
|
||||
# https://github.com/GuyCarver/MicroPython/blob/master/lib/ST7735.py
|
||||
# https://github.com/boochow/MicroPython-ST7735
|
||||
|
||||
# https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
|
||||
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
|
||||
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
|
||||
|
||||
from time import sleep_ms
|
||||
import framebuf
|
||||
import gc
|
||||
import micropython
|
||||
|
||||
# Datasheet para 8.4 scl write cycle 66ns == 15MHz
|
||||
|
||||
|
||||
@micropython.viper
|
||||
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
|
||||
n = 0
|
||||
for x in range(length):
|
||||
c = source[x]
|
||||
d = (c & 0xf0) >> 3 # 2* LUT indices (LUT is 16 bit color)
|
||||
e = (c & 0x0f) << 1
|
||||
dest[n] = lut[d]
|
||||
n += 1
|
||||
dest[n] = lut[d + 1]
|
||||
n += 1
|
||||
dest[n] = lut[e]
|
||||
n += 1
|
||||
dest[n] = lut[e + 1]
|
||||
n += 1
|
||||
|
||||
class ST7735R(framebuf.FrameBuffer):
|
||||
|
||||
lut = bytearray(32)
|
||||
|
||||
# Convert r, g, b in range 0-255 to a 16 bit colour value
|
||||
# LS byte goes into LUT offset 0, MS byte into offset 1
|
||||
# Same mapping in linebuf so LS byte is shifted out 1st
|
||||
@staticmethod
|
||||
def rgb(r, g, b):
|
||||
return (b & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (r & 0xf8)
|
||||
|
||||
# rst and cs are active low, SPI is mode 0
|
||||
def __init__(self, spi, cs, dc, rst, height=128, width=160, usd=False, init_spi=False):
|
||||
self._spi = spi
|
||||
self._rst = rst # Pins
|
||||
self._dc = dc
|
||||
self._cs = cs
|
||||
self.height = height # Required by Writer class
|
||||
self.width = width
|
||||
self._spi_init = init_spi
|
||||
# Save color mode for use by writer_gui (blit)
|
||||
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
|
||||
gc.collect()
|
||||
buf = bytearray(height * width // 2)
|
||||
self._mvb = memoryview(buf)
|
||||
super().__init__(buf, width, height, mode)
|
||||
self._linebuf = bytearray(self.width * 2) # 16 bit color out
|
||||
self._init(usd)
|
||||
self.show()
|
||||
|
||||
# Hardware reset
|
||||
def _hwreset(self):
|
||||
self._dc(0)
|
||||
self._rst(1)
|
||||
sleep_ms(1)
|
||||
self._rst(0)
|
||||
sleep_ms(1)
|
||||
self._rst(1)
|
||||
sleep_ms(1)
|
||||
|
||||
# Write a command, a bytes instance (in practice 1 byte).
|
||||
def _wcmd(self, buf):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(buf)
|
||||
self._cs(1)
|
||||
|
||||
# Write a command followed by a data arg.
|
||||
def _wcd(self, c, d):
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
self._spi.write(c)
|
||||
self._cs(1)
|
||||
self._dc(1)
|
||||
self._cs(0)
|
||||
self._spi.write(d)
|
||||
self._cs(1)
|
||||
|
||||
# Initialise the hardware. Blocks 500ms.
|
||||
def _init(self, usd):
|
||||
self._hwreset() # Hardware reset. Blocks 3ms
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
cmd = self._wcmd
|
||||
wcd = self._wcd
|
||||
cmd(b'\x01') # SW reset datasheet specifies > 120ms
|
||||
sleep_ms(150)
|
||||
cmd(b'\x11') # SLPOUT
|
||||
sleep_ms(256) # Adafruit delay (datsheet 120ms)
|
||||
wcd(b'\xb1', b'\x01\x2C\x2D') # FRMCTRL1
|
||||
wcd(b'\xb2', b'\x01\x2C\x2D') # FRMCTRL2
|
||||
wcd(b'\xb3', b'\x01\x2C\x2D\x01\x2C\x2D') # FRMCTRL3
|
||||
wcd(b'\xb4', b'\x07') # INVCTR line inversion
|
||||
|
||||
wcd(b'\xc0', b'\xa2\x02\x84') # PWCTR1 GVDD = 4.7V, 1.0uA
|
||||
wcd(b'\xc1', b'\xc5') # PWCTR2 VGH=14.7V, VGL=-7.35V
|
||||
wcd(b'\xc2', b'\x0a\x00') # PWCTR3 Opamp current small, Boost frequency
|
||||
wcd(b'\xc3', b'\x8a\x2a') # PWCTR4
|
||||
wcd(b'\xc4', b'\x8a\xee') # PWCTR5
|
||||
wcd(b'\xc5', b'\x0e') # VMCTR1 VCOMH = 4V, VOML = -1.1V NOTE I make VCOM == -0.775V
|
||||
|
||||
cmd(b'\x20') # INVOFF
|
||||
# d7..d5 of MADCTL determine rotation/orientation
|
||||
if self.height > self.width:
|
||||
wcd(b'\x36', b'\x80' if usd else b'\x40') # MADCTL: RGB portrait mode
|
||||
else:
|
||||
wcd(b'\x36', b'\xe0' if usd else b'\x20') # MADCTL: RGB landscape mode
|
||||
wcd(b'\x3a', b'\x05') # COLMOD 16 bit
|
||||
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
|
||||
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
|
||||
|
||||
wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # CASET column address 0 start, 160 end
|
||||
wcd(b'\x2b', int.to_bytes(self.height, 4, 'big')) # RASET
|
||||
|
||||
cmd(b'\x13') # NORON
|
||||
sleep_ms(10)
|
||||
cmd(b'\x29') # DISPON
|
||||
sleep_ms(100)
|
||||
|
||||
def show(self): # Blocks 36ms on Pyboard D at stock frequency (160*128)
|
||||
clut = ST7735R.lut
|
||||
wd = self.width // 2
|
||||
ht = self.height
|
||||
lb = self._linebuf
|
||||
buf = self._mvb
|
||||
self._dc(0)
|
||||
self._cs(0)
|
||||
if self._spi_init: # A callback was passed
|
||||
self._spi_init(self._spi) # Bus may be shared
|
||||
self._spi.write(b'\x2c') # RAMWR
|
||||
self._dc(1)
|
||||
for start in range(wd * (ht - 1), -1, - wd): # For each line
|
||||
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
|
||||
self._spi.write(lb)
|
||||
self._cs(1)
|
|
@ -4,17 +4,41 @@
|
|||
# Copyright (c) 2020 Peter Hinch
|
||||
|
||||
from color_setup import SSD
|
||||
if hasattr(SSD, 'lut'): # Colors defined by LUT
|
||||
def create_color(idx, r, g, b):
|
||||
if not 0 <= idx <= 15:
|
||||
raise ValueError('Color nos must be 0..15')
|
||||
x = idx << 1
|
||||
c = SSD.rgb(r, g, b)
|
||||
SSD.lut[x] = c & 0xff
|
||||
SSD.lut[x + 1] = c >> 8
|
||||
return idx
|
||||
|
||||
GREEN = SSD.rgb(0, 255, 0)
|
||||
RED = SSD.rgb(255, 0, 0)
|
||||
LIGHTRED = SSD.rgb(140, 0, 0)
|
||||
BLUE = SSD.rgb(0, 0, 255)
|
||||
YELLOW = SSD.rgb(255, 255, 0)
|
||||
BLACK = 0
|
||||
WHITE = SSD.rgb(255, 255, 255)
|
||||
GREY = SSD.rgb(100, 100, 100)
|
||||
MAGENTA = SSD.rgb(255, 0, 255)
|
||||
CYAN = SSD.rgb(0, 255, 255)
|
||||
LIGHTGREEN = SSD.rgb(0, 100, 0)
|
||||
DARKGREEN = SSD.rgb(0, 80, 0)
|
||||
DARKBLUE = SSD.rgb(0, 0, 90)
|
||||
BLACK = create_color(0, 0, 0, 0)
|
||||
GREEN = create_color(1, 0, 255, 0)
|
||||
RED = create_color(2, 255, 0, 0)
|
||||
LIGHTRED = create_color(3, 140, 0, 0)
|
||||
BLUE = create_color(4, 0, 0, 255)
|
||||
YELLOW = create_color(5, 255, 255, 0)
|
||||
GREY = create_color(6, 100, 100, 100)
|
||||
MAGENTA = create_color(7, 255, 0, 255)
|
||||
CYAN = create_color(8, 0, 255, 255)
|
||||
LIGHTGREEN = create_color(9, 0, 100, 0)
|
||||
DARKGREEN = create_color(10, 0, 80, 0)
|
||||
DARKBLUE = create_color(11, 0, 0, 90)
|
||||
# 12, 13, 14 free for user definition
|
||||
WHITE = create_color(15, 255, 255, 255)
|
||||
else:
|
||||
BLACK = SSD.rgb(0, 0, 0)
|
||||
GREEN = SSD.rgb(0, 255, 0)
|
||||
RED = SSD.rgb(255, 0, 0)
|
||||
LIGHTRED = SSD.rgb(140, 0, 0)
|
||||
BLUE = SSD.rgb(0, 0, 255)
|
||||
YELLOW = SSD.rgb(255, 255, 0)
|
||||
GREY = SSD.rgb(100, 100, 100)
|
||||
MAGENTA = SSD.rgb(255, 0, 255)
|
||||
CYAN = SSD.rgb(0, 255, 255)
|
||||
LIGHTGREEN = SSD.rgb(0, 100, 0)
|
||||
DARKGREEN = SSD.rgb(0, 80, 0)
|
||||
DARKBLUE = SSD.rgb(0, 0, 90)
|
||||
WHITE = SSD.rgb(255, 255, 255)
|
||||
|
|
|
@ -10,7 +10,7 @@ from color_setup import ssd # Create a display instance
|
|||
from gui.core.nanogui import refresh
|
||||
from gui.widgets.label import Label
|
||||
from gui.widgets.dial import Dial, Pointer
|
||||
refresh(ssd) # Initialise and clear display.
|
||||
refresh(ssd, True) # Initialise and clear display.
|
||||
|
||||
# Now import other modules
|
||||
import cmath
|
||||
|
|
|
@ -10,7 +10,7 @@ from color_setup import ssd # Create a display instance
|
|||
|
||||
from gui.core.nanogui import refresh
|
||||
from gui.widgets.dial import Dial, Pointer
|
||||
refresh(ssd) # Initialise and clear display.
|
||||
refresh(ssd, True) # Initialise and clear display.
|
||||
|
||||
# Now import other modules
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ from gui.core.nanogui import refresh
|
|||
from gui.widgets.led import LED
|
||||
from gui.widgets.meter import Meter
|
||||
|
||||
refresh(ssd)
|
||||
refresh(ssd, True)
|
||||
|
||||
# Fonts
|
||||
import gui.fonts.arial10 as arial10
|
||||
|
|
|
@ -155,7 +155,7 @@ def compass(x):
|
|||
refresh(ssd)
|
||||
|
||||
print('Color display test is running.')
|
||||
print('Test runs to completion.')
|
||||
print('Test runs to completion: ~65 secs.')
|
||||
clock(70)
|
||||
compass(70)
|
||||
meter()
|
||||
|
|
|
@ -15,7 +15,7 @@ from gui.widgets.led import LED
|
|||
from gui.widgets.meter import Meter
|
||||
from gui.widgets.label import Label
|
||||
|
||||
refresh(ssd)
|
||||
refresh(ssd, True)
|
||||
# Fonts
|
||||
import gui.fonts.arial10 as arial10
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ from gui.core.fplot import PolarGraph, PolarCurve, CartesianGraph, Curve, TSeque
|
|||
from gui.core.nanogui import refresh
|
||||
from gui.widgets.label import Label
|
||||
|
||||
refresh(ssd)
|
||||
refresh(ssd, True)
|
||||
|
||||
# Fonts
|
||||
import gui.fonts.arial10 as arial10
|
||||
|
|
|
@ -58,7 +58,7 @@ def test():
|
|||
return c
|
||||
def legendcb(f):
|
||||
return '{:2.0f}'.format(88 + ((f + 1) / 2) * (108 - 88))
|
||||
refresh(ssd) # Initialise and clear display.
|
||||
refresh(ssd, True) # Initialise and clear display.
|
||||
CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it
|
||||
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
|
||||
wri.set_clip(True, True, False)
|
||||
|
|
|
@ -58,7 +58,7 @@ async def main(wri):
|
|||
await clip(wri)
|
||||
|
||||
def test():
|
||||
refresh(ssd) # Initialise and clear display.
|
||||
refresh(ssd, True) # Initialise and clear display.
|
||||
CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it
|
||||
wri = CWriter(ssd, arial10, verbose=False)
|
||||
wri.set_clip(True, True, False)
|
||||
|
|
Ładowanie…
Reference in New Issue