Need driver library for 3.5inch 480x320 display ST7796 with RP2040

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)


Seems window setup is bad , check

    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)

In the set_window function, its using a dict ST77XX_COL_ROW_MODEL_START_ROTMAP. In this dict values for different screen dimensions is there. Not sure what will be the value for 480x320 display./

comment this line and set r0 c0 = 0 or remove it (define) imnt pyhon fan

I did the changes as u suggested
Commented this line

c0,r0=ST77XX_COL_ROW_MODEL_START_ROTMAP[self.res[0],self.res[1],self.model][self.rot%4]

and set c0=0 and r0 =0. its still the same

Need swap 320,480 is right for ST77


yes
I am using ST7796

That worked
another pending issue is the screen is reversed. I tried changing the display orientation
But its the same.

use required rot
ST77XX_PORTRAIT = const(0)
ST77XX_LANDSCAPE = const(1)
ST77XX_INV_PORTRAIT = const(2)
ST77XX_INV_LANDSCAPE = const(3)

call it after drv =
drv.apply_rotation

@Mishal_Ferrao : Great !
would you care to share the complete working code and some details about the display, connection, and anything relevant that could help other people with the same combo MCU+LCD ?

I am still getting the error for memory allocation

Traceback (most recent call last):
  File "<stdin>", line 15, in <module>
  File "st7796.py", line 353, in __init__
  File "st7796.py", line 345, in __init__
MemoryError: memory allocation failed, allocating 536934808 bytes

I tried changing the factor to 10. But the error is still there.

line 345 onwards:

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), None if doublebuffer is False else bytearray(bufSize), 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)

you have to initialize the display driver as the very first thing that gets done.

But I was checking the scripts for the drivers in the generic folder of lv_micropython GitHub repo, there run sequence of (initialization) commands and other functions are set first and initialization of display driver is done later. Whats the correct sequence? its confusing

when I say the “display driver” I am taking about initializing the python class. You have to create the instance first thing. It is also beneficial if in that class the buffers get set up first as well.

I took a code snippet from actual script.

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

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=(320, 480),
            suppRes=[
                (320, 480),
            ],
            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"),
                (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""),
            ]
        )
        self.apply_rotation(3)


    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

import time
import machine
import struct
import uctypes

from micropython import const




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 = 0
        r0 = 0
        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), None if doublebuffer is False else bytearray(bufSize), 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)```
type or paste code here

There is a HUGE amount of wasted resources. All of the constant integers that only get used inside of a module and no where else should be prefixed with an underscore. What that does is it mimicks a macro in C where anything using the name gets replaced with the value so it doesn’t take up ram in a static manner.

The layering of classes that is done is a HUGE waste of memory. Each of the subclasses takes up quite a bit of RAM. Move everything to a single class.

Move the class for making the display driver into your main script. imports cost memory, so does having all of the extra code in the file that never gets used. No sense in loading unused code into RAM.

Where your big issue is the MCU you are using only has 264 KB RAM. The MicroPython heap unless it is really trimmed down is going to use north of 100KB of RAM That leaves you with less than 160K of memory. Now remember you need to have contiguous memory for the frame buffers. So if you have a frame buffer that is ((width * height) / 8) * 2 and you have a display size of 320 x 480 each frame buffer is going to take up 38,400 bytes of memory. 1/2 of the amount of RAM you have left. When the buffers are allocated you MUST have each buffer in contiguous memory.

You can try importing gc and just before the buffers are allocated call gc.collect. I believe that you are going to still have memory issue especially if you try to load any images.

You need to use either a smaller display or get an MCU that has a lot more memory if you keep on having memory problems.

I believe that LVGL needs a minimum of 32K of memory to run. Probably more on MicroPython. The Minimum isn’t going to allow you to do a whole lot.

Buffers can be much smaller than display size by setting higher values for factor.

i tried with a factor of 50, the issue is still there.

do you have any indicator where the memory allocation error is occurring at in your code?