I used ili9xxx.py and st77xx.py from the lv_bindings/driver folder and tried a library for st7796. It has a lot of unnecessary code in it. I tried the same example as above, its printing Hello World inside a button but like this.
main.py
import lvgl as lv
import ili9xxx
import time
from machine import SPI, Pin
spi = SPI(1, baudrate=27000000, sck=Pin(10), mosi=Pin(11), miso=Pin(8))
drv = ili9xxx.St7796(spi=spi, dc=23, cs=29, rst=28)
scr = lv.obj()
btn = lv.btn(scr)
btn.set_style_bg_color(lv.color_hex(0xFF0000), lv.PART.MAIN | lv.STATE.DEFAULT)
scr.set_style_bg_color(lv.color_hex(0x000000), lv.PART.MAIN | lv.STATE.DEFAULT)
label = lv.label(btn)
label.set_text("Hello World!")
btn.set_style_width(120, lv.PART.MAIN | lv.STATE.DEFAULT)
btn.set_style_height(35, lv.PART.MAIN | lv.STATE.DEFAULT)
bh = 18
bw = 60
by = 160 - bh
bx = 240 - bw
btn.set_x(bx)
btn.set_y(by)
lv.scr_load(scr)
start_time = time.ticks_ms()
while True:
end_time = time.ticks_ms()
diff = time.ticks_diff(end_time, start_time)
if diff >= 1:
start_time = end_time
lv.tick_inc(diff)
lv.task_handler()
ili9xxx.py
"""Generic ILI9xxx drivers.
This code is licensed under MIT license.
Adapted from:
https://github.com/rdagger/micropython-ili9341
The following code snippet will instantiate the driver and
automatically register it to lvgl. Adjust the SPI bus and
pin configurations to match your hardware setup::
import ili9xxx
from machine import SPI, Pin
spi = SPI(0, baudrate=24_000_000, sck=Pin(18), mosi=Pin(19), miso=Pin(16))
drv = ili9xxx.Ili9341(spi=spi, dc=15, cs=17, rst=14)
"""
from micropython import const
import lvgl as lv
import st77xx
# Command constants from ILI9341 datasheet
_NOP = const(0x00) # No-op
_SWRESET = const(0x01) # Software reset
_RDDID = const(0x04) # Read display ID info
_RDDST = const(0x09) # Read display status
_SLPIN = const(0x10) # Enter sleep mode
_SLPOUT = const(0x11) # Exit sleep mode
_PTLON = const(0x12) # Partial mode on
_NORON = const(0x13) # Normal display mode on
_RDMODE = const(0x0A) # Read display power mode
_RDMADCTL = const(0x0B) # Read display MADCTL
_RDPIXFMT = const(0x0C) # Read display pixel format
_RDIMGFMT = const(0x0D) # Read display image format
_RDSELFDIAG = const(0x0F) # Read display self-diagnostic
_INVOFF = const(0x20) # Display inversion off
_INVON = const(0x21) # Display inversion on
_GAMMASET = const(0x26) # Gamma set
_DISPLAY_OFF = const(0x28) # Display off
_DISPLAY_ON = const(0x29) # Display on
_SET_COLUMN = const(0x2A) # Column address set
_SET_PAGE = const(0x2B) # Page address set
_WRITE_RAM = const(0x2C) # Memory write
_READ_RAM = const(0x2E) # Memory read
_PTLAR = const(0x30) # Partial area
_VSCRDEF = const(0x33) # Vertical scrolling definition
_MADCTL = const(0x36) # Memory access control
_VSCRSADD = const(0x37) # Vertical scrolling start address
_PIXFMT = const(0x3A) # COLMOD: Pixel format set
_WRITE_DISPLAY_BRIGHTNESS = const(0x51) # Brightness hardware dependent!
_READ_DISPLAY_BRIGHTNESS = const(0x52)
_WRITE_CTRL_DISPLAY = const(0x53)
_READ_CTRL_DISPLAY = const(0x54)
_WRITE_CABC = const(0x55) # Write Content Adaptive Brightness Control
_READ_CABC = const(0x56) # Read Content Adaptive Brightness Control
_WRITE_CABC_MINIMUM = const(0x5E) # Write CABC Minimum Brightness
_READ_CABC_MINIMUM = const(0x5F) # Read CABC Minimum Brightness
_FRMCTR1 = const(0xB1) # Frame rate control (In normal mode/full colors)
_FRMCTR2 = const(0xB2) # Frame rate control (In idle mode/8 colors)
_FRMCTR3 = const(0xB3) # Frame rate control (In partial mode/full colors)
_INVCTR = const(0xB4) # Display inversion control
_DFUNCTR = const(0xB6) # Display function control
_PWCTR1 = const(0xC0) # Power control 1
_PWCTR2 = const(0xC1) # Power control 2
_PWCTRA = const(0xCB) # Power control A
_PWCTRB = const(0xCF) # Power control B
_VMCTR1 = const(0xC5) # VCOM control 1
_VMCTR2 = const(0xC7) # VCOM control 2
_RDID1 = const(0xDA) # Read ID 1
_RDID2 = const(0xDB) # Read ID 2
_RDID3 = const(0xDC) # Read ID 3
_RDID4 = const(0xDD) # Read ID 4
_GMCTRP1 = const(0xE0) # Positive gamma correction
_GMCTRN1 = const(0xE1) # Negative gamma correction
_DTCA = const(0xE8) # Driver timing control A
_DTCB = const(0xEA) # Driver timing control B
_POSC = const(0xED) # Power on sequence control
_ENABLE3G = const(0xF2) # Enable 3 gamma control
_PUMPRC = const(0xF7) # Pump ratio control
_MADCTL_MY = const(0x80) # page address order (0: top to bottom; 1: bottom to top)
_MADCTL_MX = const(0x40) # column address order (0: left to right; 1: right to left)
_MADCTL_MV = const(0x20) # page/column order (0: normal mode 1; reverse mode)
_MADCTL_ML = const(
0x10
) # line address order (0: refresh to to bottom; 1: refresh bottom to top)
_MADCTL_BGR = const(0x08) # colors are BGR (not RGB)
_MADCTL_RTL = const(0x04) # refresh right to left
_MADCTL_ROTS = (
const(_MADCTL_MX), # 0 = portrait
const(_MADCTL_MV), # 1 = landscape
const(_MADCTL_MY), # 2 = inverted portrait
const(_MADCTL_MX | _MADCTL_MY | _MADCTL_MV), # 3 = inverted landscape
)
DISPLAY_TYPE_ST7789 = const(4)
DISPLAY_TYPE_ILI9488 = const(2)
MADCTL_MH = const(0x04) # Refresh 0=Left to Right, 1=Right to Left
MADCTL_ML = const(0x10) # Refresh 0=Top to Bottom, 1=Bottom to Top
MADCTL_MV = const(0x20) # 0=Normal, 1=Row/column exchange
MADCTL_MX = const(0x40) # 0=Left to Right, 1=Right to Left
MADCTL_MY = const(0x80) # 0=Top to Bottom, 1=Bottom to Top
ORIENTATION_TABLE = (MADCTL_MX, MADCTL_MV, MADCTL_MY, MADCTL_MY | MADCTL_MX | MADCTL_MV)
ILI9XXX_PORTRAIT = st77xx.ST77XX_PORTRAIT
ILI9XXX_LANDSCAPE = st77xx.ST77XX_LANDSCAPE
ILI9XXX_INV_PORTRAIT = st77xx.ST77XX_INV_PORTRAIT
ILI9XXX_INV_LANDSCAPE = st77xx.ST77XX_INV_LANDSCAPE
COLOR_MODE_RGB = const(0x00)
class Ili9341_hw(st77xx.St77xx_hw):
def __init__(self, **kw):
"""ILI9341 TFT Display Driver.
Requires ``LV_COLOR_DEPTH=16`` when building lv_micropython to function.
"""
super().__init__(
res=(240, 320),
suppRes=[
(240, 320),
],
model=None,
suppModel=None,
bgr=False,
**kw,
)
def config_hw(self):
self._run_seq(
[
(_SLPOUT, None, 100),
(_PWCTRB, b"\x00\xC1\x30"), # Pwr ctrl B
(_POSC, b"\x64\x03\x12\x81"), # Pwr on seq. ctrl
(_DTCA, b"\x85\x00\x78"), # Driver timing ctrl A
(_PWCTRA, b"\x39\x2C\x00\x34\x02"), # Pwr ctrl A
(_PUMPRC, b"\x20"), # Pump ratio control
(_DTCB, b"\x00\x00"), # Driver timing ctrl B
(_PWCTR1, b"\x23"), # Pwr ctrl 1
(_PWCTR2, b"\x10"), # Pwr ctrl 2
(_VMCTR1, b"\x3E\x28"), # VCOM ctrl 1
(_VMCTR2, b"\x86"), # VCOM ctrl 2
(_VSCRSADD, b"\x00"), # Vertical scrolling start address
(_PIXFMT, b"\x55"), # COLMOD: Pixel format
(_FRMCTR1, b"\x00\x18"), # Frame rate ctrl
(_DFUNCTR, b"\x08\x82\x27"),
(_ENABLE3G, b"\x00"), # Enable 3 gamma ctrl
(_GAMMASET, b"\x01"), # Gamma curve selected
(
_GMCTRP1,
b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00",
),
(
_GMCTRN1,
b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F",
),
(_SLPOUT, None, 100),
(_DISPLAY_ON, None),
]
)
def apply_rotation(self, rot):
self.rot = rot
if (self.rot % 2) == 0:
self.width, self.height = self.res
else:
self.height, self.width = self.res
self.write_register(
_MADCTL,
bytes([_MADCTL_BGR | _MADCTL_ROTS[self.rot % 4]]),
)
class St7796_hw(st77xx.St77xx_hw):
def __init__(self, **kw):
"""ST7796 TFT Display Driver.
Requires ``LV_COLOR_DEPTH=16`` when building lv_micropython to function.
"""
super().__init__(
res=(480, 320),
suppRes=[
(480, 320),
],
model=None,
suppModel=None,
bgr=False,
**kw,
)
def display_config(self):
if lv.color_t.__SIZE__ == 4:
display_type = DISPLAY_TYPE_ILI9488
pixel_format = 0x06 # 262K-Colors
elif lv.color_t.__SIZE__ == 2:
pixel_format = 0x05 # 65K-Colors 55??
display_type = DISPLAY_TYPE_ST7789
else:
raise RuntimeError('ST7796 micropython driver requires defining LV_COLOR_DEPTH=32 or LV_COLOR_DEPTH=16')
return pixel_format
def config_hw(self):
self._run_seq(
[
(0x01,None, 120),
(0x11, None, 120),
(0xF0,b"\xc3"),
(0xF0,b"\x96"),
(0x36,bytes([self.madctl(COLOR_MODE_RGB, ILI9XXX_LANDSCAPE, ORIENTATION_TABLE)])),
#(0X36,b"\x0"),
(0x3A,bytes([self.display_config()])),
(0xB4,b"\x01"),
(0xB6,b"\x80\x02\x3B"),
(0xE8,b"\x40\x8A\x00\x00\x29\x19\xA5\x33"),
(0xC1,b"\x06"),
(0xC2,b"\xA7"),
(0xC5,b"\x18", 120),
(0xE0,b"\xF0\x09\x0B\x06\x04\x15\x2F\x54\x42\x3C\x17\x14\x18\x1B"),
(0xE1,b"\xE0\x09\x0B\x06\x04\x03\x2B\x43\x42\x3B\x16\x14\x17\x1B", 120),
(0xF0,b"\x3C"),
(0xF0,b"\x69",120),
(0x29,b""),
]
)
def apply_rotation(self, rot):
self.rot = rot
if (self.rot % 2) == 0:
self.width, self.height = self.res
else:
self.height, self.width = self.res
self.write_register(_MADCTL,bytes([_MADCTL_BGR | ORIENTATION_TABLE[self.rot % 4]]),)
def madctl(self, colormode, rotation, rotations):
# if rotation is 0 or positive use the value as is.
if rotation >= 0:
return rotation | colormode
# otherwise use abs(rotation)-1 as index to retreive value from rotations set
index = abs(rotation) - 1
if index > len(rotations):
RuntimeError('Invalid display rot value specified during init.')
return rotations[index] | colormode
class Ili9341(Ili9341_hw, st77xx.St77xx_lvgl):
def __init__(self, doublebuffer=True, factor=4, **kw):
"""See :obj:`Ili9341_hw` for the meaning of the parameters."""
import lvgl as lv
Ili9341_hw.__init__(self, **kw)
st77xx.St77xx_lvgl.__init__(self, doublebuffer, factor)
class St7796(St7796_hw, st77xx.St77xx_lvgl):
def __init__(self, doublebuffer=True, factor=8, **kw):
"""See :obj:`Ili9341_hw` for the meaning of the parameters."""
import lvgl as lv
St7796_hw.__init__(self, **kw)
st77xx.St77xx_lvgl.__init__(self, doublebuffer, factor)
st77xx.py
# © 2022 VĂĄclav Ć milauer <eu@doxos.eu>
# MIT-licensed
import time
import machine
import struct
import uctypes
from micropython import const
# This driver was written from scratch using datasheets and looking at other drivers listed here.
# Required copyright notices of those drivers are included below as necessary.
# This is Pimoroni driver, with Adafruit header (MIT, notice included below):
# https://github.com/pimoroni/st7789-python/blob/master/library/ST7789/__init__.py
# This is c++ Adafruit driver (MIT, notice included below):
# https://github.com/adafruit/Adafruit-ST7735-Library/blob/master/Adafruit_ST7789.cpp
# independent (?) micropython implementation (license unspecified):
# https://techatronic.com/st7789-display-pi-pico/
# st77xx c driver (for uPy), with simplified init sequence (MIT, notice included below):
# https://github.com/szampardi/st77xx_mpy
#
# This is a library for several Adafruit displays based on ST77* drivers.
#
# Works with the Adafruit 1.8" TFT Breakout w/SD card
# ----> http://www.adafruit.com/products/358
# The 1.8" TFT shield
# ----> https://www.adafruit.com/product/802
# The 1.44" TFT breakout
# ----> https://www.adafruit.com/product/2088
# as well as Adafruit raw 1.8" TFT display
# ----> http://www.adafruit.com/products/618
#
# Check out the links above for our tutorials and wiring diagrams.
# These displays use SPI to communicate, 4 or 5 pins are required to
# interface (RST is optional).
#
# Adafruit invests time and resources providing this open source code,
# please support Adafruit and open-source hardware by purchasing
# products from Adafruit!
#
# Written by Limor Fried/Ladyada for Adafruit Industries.
# MIT license, all text above must be included in any redistribution.
#
#
# Copyright (c) 2019 Ivan Belokobylskiy
#
#
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
#
# 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.
ST77XX_NOP = const(0x00)
ST77XX_SWRESET = const(0x01)
ST77XX_RDDID = const(0x04)
ST77XX_RDDST = const(0x09)
ST77XX_SLPIN = const(0x10)
ST77XX_SLPOUT = const(0x11)
ST77XX_PTLON = const(0x12)
ST77XX_NORON = const(0x13)
ST77XX_INVOFF = const(0x20)
ST77XX_INVON = const(0x21)
ST77XX_DISPOFF = const(0x28)
ST77XX_DISPON = const(0x29)
ST77XX_CASET = const(0x2A)
ST77XX_RASET = const(0x2B)
ST77XX_RAMWR = const(0x2C)
ST77XX_RAMRD = const(0x2E)
ST77XX_PTLAR = const(0x30)
ST77XX_MADCTL = const(0x36)
ST77XX_COLMOD = const(0x3A)
ST7789_WRCACE = const(0x55)
ST77XX_FRMCTR1 = const(0xB1)
ST77XX_FRMCTR2 = ST7789_PORCTRL = const(0xB2)
ST77XX_FRMCTR3 = const(0xB3)
ST77XX_INVCTR = const(0xB4)
ST7789_DISSET5 = const(0xB6)
ST7789_GCTRL = const(0xB7)
ST7789_GTADJ = const(0xB8)
ST7789_VCOMS = const(0xBB)
ST7735_PWCTR1 = ST7789_LCMCTRL = const(0xC0)
ST7735_PWCTR2 = ST7789_IDSET = const(0xC1)
ST7735_PWCTR3 = ST7789_VDVVRHEN = const(0xC2)
ST7735_PWCTR4 = ST7789_VRHS = const(0xC3)
ST7735_PWCTR5 = ST7789_VDVS = const(0xC4)
ST7735_VMCTR1 = ST7789_VMCTR1 = const(0xC5)
ST7789_FRCTRL2 = const(0xC6)
ST7789_CABCCTRL = const(0xC7)
ST7789_PWCTRL1 = const(0xD0)
ST77XX_RDID1 = const(0xDA)
ST77XX_RDID2 = const(0xDB)
ST77XX_RDID3 = const(0xDC)
ST77XX_RDID4 = const(0xDD)
ST7789_GMCTRP1 = ST7789_PVGAMCTRL = const(0xE0)
ST7789_GMCTRN1 = ST7789_NVGAMCTRL = const(0xE1)
ST7735_PWCTR6 = ST7789_PWCTR6 = const(0xFC)
ST77XX_MADCTL_MY = const(0x80) # page address order (0: top to bottom; 1: bottom to top)
ST77XX_MADCTL_MX = const(0x40) # column address order (0: left to right; 1: right to left)
ST77XX_MADCTL_MV = const(0x20) # page/column order (0: normal mode 1; reverse mode)
ST77XX_MADCTL_ML = const(0x10) # line address order (0: refresh to to bottom; 1: refresh bottom to top)
ST77XX_MADCTL_BGR = const(0x08) # colors are BGR (not RGB)
ST77XX_MADCTL_RTL = const(0x04) # refresh right to left
ST77XX_MADCTL_ROTS=(
const(0x00), # 0 = portrait
const(ST77XX_MADCTL_MX | ST77XX_MADCTL_MV), # 1 = landscape
const(ST77XX_MADCTL_MY | ST77XX_MADCTL_MX), # 2 = inverted portrait
const(ST77XX_MADCTL_MY | ST77XX_MADCTL_MV), # 3 = inverted landscape
)
ST77XX_COLOR_MODE_65K = const(0x50)
ST77XX_COLOR_MODE_262K = const(0x60)
ST77XX_COLOR_MODE_12BIT = const(0x03)
ST77XX_COLOR_MODE_16BIT = const(0x05)
ST77XX_COLOR_MODE_18BIT = const(0x06)
ST77XX_COLOR_MODE_16M = const(0x07)
ST77XX_COL_ROW_MODEL_START_ROTMAP={
# ST7789
(240,320,None):[(0,0),(0,0),(0,0),(0,0)],
(240,240,None):[(0,0),(0,0),(0,80),(80,0)],
(135,240,None):[(52,40),(40,53),(53,40),(40,52)],
# ST7735
(128,160,'blacktab'):[(0,0),(0,0),(0,0),(0,0)],
(128,160,'redtab'):[(2,1),(1,2),(2,1),(1,2)],
}
ST77XX_PORTRAIT = const(0)
ST77XX_LANDSCAPE = const(1)
ST77XX_INV_PORTRAIT = const(2)
ST77XX_INV_LANDSCAPE = const(3)
class St77xx_hw(object):
def __init__(self, *, cs, dc, spi, res, suppRes, bl=None, model=None, suppModel=[], rst=None, rot=ST77XX_LANDSCAPE, bgr=False, rp2_dma=None):
'''
This is an abstract low-level driver the ST77xx controllers, not to be instantiated directly.
Derived classes implement chip-specific bits. THe following parameters are recognized:
* *cs*: chip select pin (= slave select, SS)
* *dc*: data/command pin
* *bl*: backlight PWM pin (optional)
* *model*: display model, to account for variations in products
* *rst*: optional reset pin
* *res*: resolution tuple; (width,height) with zero rotation
* *rot*: display orientation (0: portrait, 1: landscape, 2: inverted portrait, 3: inverted landscape); the constants ST77XX_PORTRAIT, ST77XX_LANDSCAPE, ST77XX_INV_POTRAIT, ST77XX_INV_LANDSCAPE may be used.
* *bgr*: color order if BGR (not RGB)
* *rp2_dma*: optional DMA object for the rp2 port
Subclass constructors (implementing concrete chip) set in addition the following, not to be used directly:
* *suppModel*: models supported by the hardware driver
* *suppRes*: resolutions supported by the hardware driver, as list of (width,height) tuples
'''
self.buf1 = bytearray(1)
self.buf2 = bytearray(2)
self.buf4 = bytearray(4)
self.cs,self.dc,self.rst=[(machine.Pin(p,machine.Pin.OUT) if isinstance(p,int) else p) for p in (cs,dc,rst)]
self.bl=bl
if isinstance(self.bl,int): self.bl=machine.PWM(machine.Pin(self.bl,machine.Pin.OUT))
elif isinstance(self.bl,machine.Pin): self.bl=machine.PWM(self.bl)
assert isinstance(self.bl,(machine.PWM,type(None)))
self.set_backlight(10) # set some backlight
self.rot=rot
self.bgr=bgr
self.width,self.height=(0,0) # this is set later in hard_reset->config->apply_rotation
if res not in suppRes: raise ValueError('Unsupported resolution %s; the driver supports: %s.'%(str(res),', '.join(str(r) for r in suppRes)))
if suppModel and model not in suppModel: raise ValueError('Unsupported model %s; the driver supports: %s.'%(str(model),', '.join(str(r) for r in suppModel)))
self.res=res
self.model=model
self.rp2_dma=rp2_dma
self.spi=spi
self.hard_reset()
def off(self): self.set_backlight(0)
def hard_reset(self):
if self.rst:
for v in (1,0,1):
self.rst.value(v)
time.sleep(.2)
time.sleep(.2)
self.config()
def config(self):
self.config_hw() # defined in child classes
self.apply_rotation(self.rot)
def set_backlight(self,percent):
if self.bl is None: return
self.bl.duty_u16(percent*655)
def set_window(self, x, y, w, h):
c0,r0=ST77XX_COL_ROW_MODEL_START_ROTMAP[self.res[0],self.res[1],self.model][self.rot%4]
struct.pack_into('>hh', self.buf4, 0, c0+x, c0+x+w-1)
self.write_register(ST77XX_CASET, self.buf4)
struct.pack_into('>hh', self.buf4, 0, r0+y, r0+y+h-1)
self.write_register(ST77XX_RASET, self.buf4)
def apply_rotation(self,rot):
self.rot=rot
if (self.rot%2)==0: self.width,self.height=self.res
else: self.height,self.width=self.res
self.write_register(ST77XX_MADCTL,bytes([(ST77XX_MADCTL_BGR if self.bgr else 0)|ST77XX_MADCTL_ROTS[self.rot%4]]))
def blit(self, x, y, w, h, buf, is_blocking=True):
self.set_window(x, y, w, h)
if self.rp2_dma: self._rp2_write_register_dma(ST77XX_RAMWR, buf, is_blocking)
else: self.write_register(ST77XX_RAMWR, buf)
def clear(self, color):
bs=128 # write pixels in chunks; makes the fill much faster
struct.pack_into('>h',self.buf2,0,color)
buf=bs*bytes(self.buf2)
npx=self.width*self.height
self.set_window(0, 0, self.width, self.height)
self.write_register(ST77XX_RAMWR, None)
self.cs.value(0)
self.dc.value(1)
for _ in range(npx//bs): self.spi.write(buf)
for _ in range(npx%bs): self.spi.write(self.buf2)
self.cs.value(1)
def write_register(self, reg, buf=None):
struct.pack_into('B', self.buf1, 0, reg)
self.cs.value(0)
self.dc.value(0)
self.spi.write(self.buf1)
if buf is not None:
self.dc.value(1)
self.spi.write(buf)
self.cs.value(1)
def _rp2_write_register_dma(self, reg, buf, is_blocking=True):
'If *is_blocking* is False, used should call wait_dma explicitly.'
SPI1_BASE = 0x40040000 # FIXME: will be different for another SPI bus?
SSPDR = 0x008
self.rp2_dma.config(
src_addr = uctypes.addressof(buf),
dst_addr = SPI1_BASE + SSPDR,
count = len(buf),
src_inc = True,
dst_inc = False,
trig_dreq= self.rp2_dma.DREQ_SPI1_TX
)
struct.pack_into('B',self.buf1,0,reg)
self.cs.value(0)
self.dc.value(0)
self.spi.write(self.buf1)
self.dc.value(1)
self.rp2_dma.enable()
if is_blocking: self.rp2_wait_dma()
def rp2_wait_dma(self):
'''
Wait for rp2-port DMA transfer to finish; no-op unless self.rp2_dma is defined.
Can be used as callback before accessing shared SPI bus e.g. with the xpt2046 driver.
'''
if self.rp2_dma is None: return
while self.rp2_dma.is_busy(): pass
self.rp2_dma.disable()
# wait to send last byte. It should take < 1uS @ 10MHz
time.sleep_us(1)
self.cs.value(1)
def _run_seq(self,seq):
'''
Run sequence of (initialization) commands; those are given as list of tuples, which are either
`(command,data)` or `(command,data,delay_ms)`
'''
for i,cmd in enumerate(seq):
if len(cmd)==2: (reg,data),delay=cmd,0
elif len(cmd)==3: reg,data,delay=cmd
else: raise ValueError('Command #%d has %d items (must be 2 or 3)'%(i,len(cmd)))
self.write_register(reg,data)
if delay>0: time.sleep_ms(delay)
class St7735_hw(St77xx_hw):
'''There are several ST7735-based LCD models, we only tested the blacktab model really.'''
def __init__(self,res,model='greentab',**kw):
super().__init__(res=res,suppRes=[(128,160),],model=model,suppModel=['greentab','redtab','blacktab'],**kw)
def config_hw(self):
# mostly from here
# https://github.com/stechiez/raspberrypi-pico/blob/main/pico_st7735/st7735/ST7735.py
init7735r=[
# see here for explanations: https://github.com/adafruit/Adafruit-ST7735-Library/blob/master/Adafruit_ST7735.cpp
(ST77XX_SWRESET,None, 50),
(ST77XX_SLPOUT, None, 100),
(ST77XX_FRMCTR1,b'\x01\x2c\x2d'),
(ST77XX_FRMCTR2,b'\x01\x2c\x2d'),
(ST77XX_FRMCTR3,b'\x01\x2c\x2d\x01\x2c\x2d'),
(ST77XX_INVCTR,b'\x07'),
(ST7735_PWCTR1,b'\xa2\x02\xb4'),
(ST7735_PWCTR2,b'\xc5'),
(ST7735_PWCTR3,b'\x0a\x00'),
(ST7735_PWCTR4,b'\x8a\x2a'),
(ST7735_PWCTR5,b'\x8a\xee'),
(ST7735_VMCTR1,b'\x0e'),
(ST77XX_INVOFF,None),
# ST77XX_MADCTL: do later, depending on rotation
(ST77XX_COLMOD,bytes([ST77XX_COLOR_MODE_65K | ST77XX_COLOR_MODE_16BIT])),
(ST77XX_CASET,bytes([0x00,0x00,0x00,0x7f])),
(ST77XX_RASET,bytes([0x00,0x00,0x00,0x9f])),
# gamma adjustment: Waveshare values
(ST7789_GMCTRP1,b'\x0f\x1a\x0f\x18\x2f\x28\x20\x22\x1f\x1b\x23\x37\x00\x07\x02\x10'),
(ST7789_GMCTRN1,b'\x0f\x1b\x0f\x17\x33\x2c\x29\x2e\x30\x30\x39\x3f\x00\x07\x03\x10'),
(ST77XX_NORON, None, 10),
(ST77XX_DISPON, None,100),
]
# the "blue version" only (not tested)
init7735=[
# swreset
(ST77XX_SWRESET, None, 50),
# out of sleep mode
(ST77XX_SLPOUT, None, 100),
# RGB565
(ST77XX_COLMOD,bytes([ST77XX_COLOR_MODE_65K | ST77XX_COLOR_MODE_16BIT])),
# fast refresh (??)
(ST77XX_FRMCTR1,bytes([0x00,0x06,0x03])),
(ST77XX_MADCTL,bytes([0x03])),
(ST77XX_INVCTR,b'\x00'),
(ST7735_PWCTR1,b'\x02\x70'),
(ST7735_PWCTR2,b'\x05'),
(ST7735_PWCTR3,b'\x01\x02'),
(ST7735_VMCTR1,b'\x3c\x38'),
(ST7735_PWCTR6,b'\b11\b15'),
# (ST77XX_GMCTRP1,b'\
## memory access direction
# (ST77XX_MADCTL, bytes([ST77XX_MADCTL_ROTS[self.rot%4]]), 0),
# inverted on (?)
#(ST77XX_INVON, None, 10),
# normal display on
(ST77XX_NORON, None, 10),
# display on
(ST77XX_DISPON, None,100)
]
if self.model in ('redtab','blacktab'): self._run_seq(init7735r)
else:
print('Warning: the greentab model was never properly tested')
self._run_seq(init7735)
# ST77XX_MADCTL applied in apply_rotation
class St7789_hw(St77xx_hw):
def __init__(self,res,**kw):
super().__init__(res=res,suppRes=[(240,320),],model=None,suppModel=None,**kw)
def config_hw(self):
init7789=[
# out of sleep mode
(ST77XX_SLPOUT, None, 100),
# memory access direction (this is set again in apply_rotation)
(ST77XX_MADCTL, bytes([ST77XX_MADCTL_ROTS[self.rot%4]])),
# RGB565
(ST77XX_COLMOD, bytes([ST77XX_COLOR_MODE_65K | ST77XX_COLOR_MODE_16BIT])),
# front/back porch setting in normal/idle/partial modes; 3rd byte (PSEN) 0x00 = disabled
(ST7789_PORCTRL, b"\x0C\x0C\x00\x33\x33"),
# VGH=14.06V, VGL=-8.87V [Adafruit: 0x14]
(ST7789_GCTRL, b"\x35"),
# [Adafruit: missing]
(ST77XX_DISPOFF, b"\x28"),
# power control [Adafruit: 0x2c]
(ST7789_LCMCTRL, b"\x3C"),
# power control (set VDV and VRD by register write), write VRH and VDV
(ST7789_VDVVRHEN, b"\x01"),(ST7789_VRHS, b"\x0B"),(ST7789_VDVS, b"\x20"),
# frame rate 60Hz
(ST7789_FRCTRL2, b"\x0F"),
# power control: AVDD=6.6V, AVCL=-4.8V, VDS=2.4V
(ST7789_PWCTRL1, b"\xA4\xA1"),
# positive voltage gamma control
(ST7789_PVGAMCTRL, b"\xD0\x01\x08\x0F\x11\x2A\x36\x55\x44\x3A\x0B\x06\x11\x20"),
# negative voltage gamma control
(ST7789_NVGAMCTRL, b"\xD0\x02\x07\x0A\x0B\x18\x34\x43\x4A\x2B\x1B\x1C\x22\x1F"),
# content adaptive brightness control and color enhancement: color enhancement on, high enhancement
(ST7789_WRCACE, bytes([0b1011_0000])),
# display on
(ST77XX_DISPON, None, 100),
## FIXME: needs out of sleep mode AGAIN, otherwise will stay bleck the first time on?
(ST77XX_SLPOUT, None, 100),
]
self._run_seq(init7789)
# ST77XX_MADCTL applied in apply_rotation
class St77xx_lvgl(object):
'''LVGL wrapper for St77xx, not to be instantiated directly.
* creates and registers LVGL display driver;
* allocates buffers (double-buffered by default);
* sets the driver callback to the disp_drv_flush_cb method.
'''
def disp_drv_flush_cb(self,disp_drv,area,color):
# print(f"({area.x1},{area.y1}..{area.x2},{area.y2})")
self.rp2_wait_dma() # wait if not yet done and DMA is being used
# blit in background
self.blit(area.x1,area.y1,w:=(area.x2-area.x1+1),h:=(area.y2-area.y1+1),color.__dereference__(2*w*h),is_blocking=False)
self.disp_drv.flush_ready()
def __init__(self,doublebuffer=True,factor=4):
import lvgl as lv
import lv_utils
if lv.COLOR_DEPTH!=16: raise RuntimeError(f'LVGL *must* be compiled with LV_COLOR_DEPTH=16 (currently LV_COLOR_DEPTH={lv.COLOR_DEPTH}.')
bufSize=(self.width*self.height*lv.color_t.__SIZE__)//factor
if not lv.is_initialized(): lv.init()
# create event loop if not yet present
if not lv_utils.event_loop.is_running(): self.event_loop=lv_utils.event_loop()
# attach all to self to avoid objects' refcount dropping to zero when the scope is exited
self.disp_drv = lv.disp_create(self.width, self.height)
self.disp_drv.set_flush_cb(self.disp_drv_flush_cb)
self.disp_drv.set_draw_buffers(bytearray(bufSize), bytearray(bufSize) if doublebuffer else None, bufSize, lv.DISP_RENDER_MODE.PARTIAL)
self.disp_drv.set_color_format(lv.COLOR_FORMAT.NATIVE if self.bgr else lv.COLOR_FORMAT.NATIVE_REVERSED)
class St7735(St7735_hw,St77xx_lvgl):
def __init__(self,res,doublebuffer=True,factor=4,**kw):
'''See :obj:`St77xx_hw` for the meaning of the parameters.'''
St7735_hw.__init__(self,res=res,**kw)
St77xx_lvgl.__init__(self,doublebuffer,factor)
class St7789(St7789_hw,St77xx_lvgl):
def __init__(self,res,doublebuffer=True,factor=4,**kw):
'''See :obj:`St77xx_hw` for the meaning of the parameters.'''
St7789_hw.__init__(self,res=res,**kw)
St77xx_lvgl.__init__(self,doublebuffer,factor)