wasp: st7789: Add a simple proof-of-concept display driver

This commit is contained in:
Daniel Thompson 2020-01-21 22:10:50 +00:00
parent a8d8d92481
commit 2805a719f2
5 changed files with 344 additions and 2 deletions

View file

@ -29,7 +29,9 @@ softdevice:
micropython:
$(MAKE) -C micropython/mpy-cross
$(MAKE) -C micropython/ports/nrf BOARD=$(BOARD) SD=s132
$(MAKE) -C micropython/ports/nrf \
BOARD=$(BOARD) SD=s132 \
FROZEN_MPY_DIR=$(PWD)/wasp
python3 -m nordicsemi dfu genpkg \
--dev-type 0x0052 \
--application micropython/ports/nrf/build-$(BOARD)-s132/firmware.hex \

View file

@ -20,3 +20,14 @@ Then use nRFConnect (for Android) to program micropython.zip.
At the end of this process your watch may *look* dead but, if it works,
you will be able to use the Nordic UART Service to access the
MicroPython REPL.
Drivers are, for the most part, an exercise for the reader but
there is a proof-of-concept display driver. To experiment try:
~~~
import pinetime, time
tft = pinetime.st7789()
tft.white()
time.sleep(2)
tft.black()
~~~

@ -1 +1 @@
Subproject commit 5457f42e7e128447e50a82720f7c321d4133dd44
Subproject commit ee1899688b4d3d3f277f5cad61e06e5df496bc39

310
wasp/drivers/st7789.py Normal file
View file

@ -0,0 +1,310 @@
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
# Extensive further hacking by Pimoroni and Daniel Thompson
#
# 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.
import time
__version__ = '0.0.2'
BG_SPI_CS_BACK = 0
BG_SPI_CS_FRONT = 1
ST7789_NOP = 0x00
ST7789_SWRESET = 0x01
ST7789_RDDID = 0x04
ST7789_RDDST = 0x09
ST7789_SLPIN = 0x10
ST7789_SLPOUT = 0x11
ST7789_PTLON = 0x12
ST7789_NORON = 0x13
ST7789_INVOFF = 0x20
ST7789_INVON = 0x21
ST7789_DISPOFF = 0x28
ST7789_DISPON = 0x29
ST7789_CASET = 0x2A
ST7789_RASET = 0x2B
ST7789_RAMWR = 0x2C
ST7789_RAMRD = 0x2E
ST7789_PTLAR = 0x30
ST7789_MADCTL = 0x36
ST7789_COLMOD = 0x3A
ST7789_FRMCTR1 = 0xB1
ST7789_FRMCTR2 = 0xB2
ST7789_FRMCTR3 = 0xB3
ST7789_INVCTR = 0xB4
# ILI9341_DFUNCTR = 0xB6
ST7789_DISSET5 = 0xB6
ST7789_GCTRL = 0xB7
ST7789_GTADJ = 0xB8
ST7789_VCOMS = 0xBB
ST7789_LCMCTRL = 0xC0
ST7789_IDSET = 0xC1
ST7789_VDVVRHEN = 0xC2
ST7789_VRHS = 0xC3
ST7789_VDVS = 0xC4
ST7789_VMCTR1 = 0xC5
ST7789_FRCTRL2 = 0xC6
ST7789_CABCCTRL = 0xC7
ST7789_RDID1 = 0xDA
ST7789_RDID2 = 0xDB
ST7789_RDID3 = 0xDC
ST7789_RDID4 = 0xDD
ST7789_GMCTRP1 = 0xE0
ST7789_GMCTRN1 = 0xE1
ST7789_PWCTR6 = 0xFC
class ST7789(object):
"""Representation of an ST7789 TFT LCD."""
def __init__(self, spi, cs, dc, backlight=None, rst=None, width=240,
height=240, rotation=90, invert=True, spi_speed_hz=4000000):
"""Create an instance of the display using SPI communication.
Must provide the GPIO pin number for the D/C pin and the SPI driver.
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
:param spi: SPI instance to work with
:param cs: SPI chip-select Pin
:param backlight: Pin for controlling backlight
:param rst: reset Pin for ST7789
:param width: Width of display connected to ST7789
:param height: Height of display connected to ST7789
:param rotation: Rotation of display connected to ST7789
:param invert: Invert display
:param spi_speed_hz: SPI speed (in Hz)
"""
self._spi = spi
self._cs = cs
self._dc = dc
self._rst = rst
self._width = width
self._height = height
self._rotation = rotation
self._invert = invert
self._offset_left = 0
self._offset_top = 0
# Initialize cs
self._cs.on()
# Setup backlight as output (if provided).
self._backlight = backlight
if backlight is not None:
backlight.off()
time.sleep(0.1)
backlight.on()
self.reset()
self._init()
def send(self, data, is_data=True, chunk_size=4096):
"""Write a byte or array of bytes to the display. Is_data parameter
controls if byte should be interpreted as display data (True) or command
data (False). Chunk_size is an optional size of bytes to write in a
single SPI transaction, with a default of 4096.
"""
# Set DC low for command, high for data.
self._dc.value(is_data)
# Convert scalar argument to list so either can be passed as parameter.
try:
data[0]
except:
data = [data & 0xFF]
data = bytearray(data)
# Write data a chunk at a time.
for start in range(0, len(data), chunk_size):
end = min(start + chunk_size, len(data))
self._cs.off()
self._spi.write(data[start:end])
self._cs.on()
def set_backlight(self, value):
"""Set the backlight on/off."""
if self._backlight is not None:
self._backlight.value(value)
@property
def width(self):
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
@property
def height(self):
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
def command(self, data):
"""Write a byte or array of bytes to the display as command data."""
self.send(data, False)
def data(self, data):
"""Write a byte or array of bytes to the display as display data."""
self.send(data, True)
def reset(self):
"""Reset the display, if reset pin is connected."""
if self._rst is not None:
self._rst.on()
time.sleep(0.500)
self._rst.off()
time.sleep(0.500)
self._rst.on()
time.sleep(0.500)
def _init(self):
# Initialize the display.
self.command(ST7789_SWRESET) # Software reset
time.sleep(0.150) # delay 150 ms
self.command(ST7789_MADCTL)
self.data(0x70)
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
self.data(0x0C)
self.data(0x0C)
self.data(0x00)
self.data(0x33)
self.data(0x33)
self.command(ST7789_COLMOD)
self.data(0x05)
self.command(ST7789_GCTRL)
self.data(0x14)
self.command(ST7789_VCOMS)
self.data(0x37)
self.command(ST7789_LCMCTRL) # Power control
self.data(0x2C)
self.command(ST7789_VDVVRHEN) # Power control
self.data(0x01)
self.command(ST7789_VRHS) # Power control
self.data(0x12)
self.command(ST7789_VDVS) # Power control
self.data(0x20)
self.command(0xD0)
self.data(0xA4)
self.data(0xA1)
self.command(ST7789_FRCTRL2)
self.data(0x0F)
self.command(ST7789_GMCTRP1) # Set Gamma
self.data(0xD0)
self.data(0x04)
self.data(0x0D)
self.data(0x11)
self.data(0x13)
self.data(0x2B)
self.data(0x3F)
self.data(0x54)
self.data(0x4C)
self.data(0x18)
self.data(0x0D)
self.data(0x0B)
self.data(0x1F)
self.data(0x23)
self.command(ST7789_GMCTRN1) # Set Gamma
self.data(0xD0)
self.data(0x04)
self.data(0x0C)
self.data(0x11)
self.data(0x13)
self.data(0x2C)
self.data(0x3F)
self.data(0x44)
self.data(0x51)
self.data(0x2F)
self.data(0x1F)
self.data(0x1F)
self.data(0x20)
self.data(0x23)
if self._invert:
self.command(ST7789_INVON) # Invert display
else:
self.command(ST7789_INVOFF) # Don't invert display
self.command(ST7789_SLPOUT)
self.command(ST7789_DISPON) # Display on
time.sleep(0.100) # 100 ms
def set_window(self, x0=0, y0=0, x1=None, y1=None):
"""Set the pixel address window for proceeding drawing commands. x0 and
x1 should define the minimum and maximum x pixel bounds. y0 and y1
should define the minimum and maximum y pixel bound. If no parameters
are specified the default will be to update the entire display from 0,0
to width-1,height-1.
"""
if x1 is None:
x1 = self._width - 1
if y1 is None:
y1 = self._height - 1
y0 += self._offset_top
y1 += self._offset_top
x0 += self._offset_left
x1 += self._offset_left
self.command(ST7789_CASET) # Column addr set
self.data(x0 >> 8)
self.data(x0 & 0xFF) # XSTART
self.data(x1 >> 8)
self.data(x1 & 0xFF) # XEND
self.command(ST7789_RASET) # Row addr set
self.data(y0 >> 8)
self.data(y0 & 0xFF) # YSTART
self.data(y1 >> 8)
self.data(y1 & 0xFF) # YEND
self.command(ST7789_RAMWR) # write to RAM
def white(self):
self.set_window()
for i in range(self._height):
self.data([255] * self._width * 2)
def black(self):
self.set_window()
for i in range(self._height):
self.data([0] * self._width * 2)

19
wasp/pinetime.py Normal file
View file

@ -0,0 +1,19 @@
from machine import Pin
from machine import SPI
from drivers.st7789 import ST7789
def st7789():
spi = SPI(0)
# Mode 3, maximum clock speed!
spi.init(polarity=1, phase=1, baudrate=8000000)
# Extra pins required by the driver
cs = Pin("SPI_SS2", Pin.OUT)
dc = Pin("P18", Pin.OUT)
rst = Pin("P26", Pin.OUT)
bl = Pin("P22", Pin.OUT)
tft = ST7789(spi, cs=cs, dc=dc, rst=rst)
bl.off() # active low
return tft