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

Did the following changes:

  1. Changed the set_draw_buffers function.
  2. Changed the value of factor from 4 to 8.

I am getting a black screen.

black screen is a good. So no errors.

Now all that we need to do is correct the main script so it loops and updates the tick and also updates the widgets.

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()

give that a go and see if it works for ya.

anything you want to run in your program needs to be inside of that loop. Otherwise MicroPython will end up going into the repl and sit there waiting for user input over a serial connection. When you want to have a GUI run you will not be able to do this because the script needs to continually loop in order to update the GUI. You can use an ISR to handle the updating of the timer and scheduling a task that would call lv.task_handler but I would recommend doing this. The reason being is using an ISR is going to cause more overhead an you will only be able to do the updates every 10ms or so at best. might be even longer because of the board you are using. You cannot call lv.task_handler from the ISR due to memory allocation so you would have to use the ISR to schedule a task to call lv_task_handler. You have take make sure that you do not schedule too many tasks because it will consume all of the memory on the board do to how long LVGL can take to process the call to lv.task_handler. If you decide to go that route take care when writing the code.

I find it easier to just do what I did here at the bottom of the main.py script. It is also easier to bug test due to a complete stack trace being available.

actually scratch those last 2 posts. I just notice the code is not going to work properly. I need to rewrite it.

It’s the order in which things are being set up that isn’t right.

Some errors are still there. No sure how I did not get them before.

Traceback (most recent call last):
  File "<stdin>", line 34, in <module>
  File "ili9xxx.py", line 295, in disp_drv_flush_cb
  File "ili9xxx.py", line 124, in blit
  File "ili9xxx.py", line 111, in set_window
NameError: name 'ST77XX_COL_ROW_MODEL_START_ROTMAP' isn't defined

in st77xx.py the value of ST77XX_COL_ROW_MODEL_START_ROTMAP is as follows. But the display that I am using is 480x320

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)],
}

I was having a look at this issue. Its driver library for ST7796 but with ESP32.

give me some time. I am writing you a driver.

There are bound to be some problems with this. I keyed it up using Windows Notepad which is not exactly a code editor.
Give the code below a try, you do not need to compile again, all you need to do is replace the code in your main.py with the code below and upload it to your board.

I am sure you can figure out any basic problems like syntax errors or spelling mistakes that will come up. If there is something you are not sure about lemme know and I will fix it. This example does not make use of the DMA support for your board. I will add that support once we know this is working properly. the DMA stuff for your board is not straight forward and easy to do but I can get it done for yam just want to make sure it is working properly without DMA first.

from micropython import const
import lvgl as lv
import machine
import time
import struct
# import lv_utils

COLOR_MODE_RGB = const(0x00)
COLOR_MODE_BGR = const(0x08)


_MADCTL = const(0x36)
_MADCTL_RTL = const(0x04)  # refresh right to left

_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)


# Negative orientation constants indicate the MADCTL value will come from the ORIENTATION_TABLE,
# otherwise the rot value is used as the MADCTL value.
PORTRAIT = const(-1)
LANDSCAPE = const(-2)
INV_PORTRAIT = const(-3)
INV_LANDSCAPE = const(-4)



class St7796(object):
    def __init__(self, width, height, cs, dc, spi, bl=-1, rst=-1, rot=LANDSCAPE, colormode=COLOR_MODE_RGB, rp2_dma=None, bl_pwm=False, doublebuffer=False, factor=8):
        """See :obj:`Ili9341_hw` for the meaning of the parameters."""

        if lv.color_t.__SIZE__ != 2: 
            raise RuntimeError('LVGL *must* be compiled with LV_COLOR_DEPTH=16')

        bufSize = int((width * height * lv.color_t.__SIZE__) // factor)

        if not lv.is_initialized(): 
            lv.init()

        self.cmd_trans_data = bytearray(1)
        self.word_trans_data = bytearray(4)

        self.flush_buf1 = bytearray(bufSize)
        if doublebuffer:
            self.flush_buf2 = bytearray(bufSize)
        else:
            self.flush_buf2 = None

        if colormode == COLOR_MODE_RGB:
            colorformat = lv.COLOR_FORMAT.NATIVE_REVERSED
        else:
            colorformat = lv.COLOR_FORMAT.NATIVE
        
        self.colormode = colormode
        self.colorformat = colorformat

        self.disp_drv = lv.disp_create(width, height)
        self.disp_drv.set_flush_cb(self.flush_cb)
        self.disp_drv.set_draw_buffers(self.flush_buf1, self.flush_buf2, int(bufSize // lv.color_t.__SIZE__), lv.DISP_RENDER_MODE.PARTIAL)
        self.disp_drv.set_color_format(colorformat)
        
        if isinstance(cs, int):
            if cs == -1:
               cs = None
            else:
                cs = machine.Pin(cs, machine.Pin.OUT)

        if isinstance(dc, int):
            dc = machine.Pin(dc, machine.Pin.OUT)

        if isinstance(rst, int):
            if rst == -1:
               rst = None
            else:
                rst = machine.Pin(cs, machine.Pin.OUT)
                rst.value(1)

        if isinstance(bl, int):
            if bl == -1:
               bl = None
            else:
                bl = machine.Pin(cs, machine.Pin.OUT)

        if bl is not None and bl_pwm:
            bl = machine.PWM(bl)
            
            
        self.cs = cs
        self.dc = dc
        self.rst = rst
        self.bl = bl
        self.bl_pwm = bl_pwm

        self.rot = rot
        self.width = 0
        self.height = 0
        self.res = (width, height)

        self.rp2_dma = rp2_dma
        self.spi = spi
        self.init()
        
        # create event loop if not yet present
        #comment this section out if you do not want to use an ISR to handle the refreshing of the display
        # if not lv_utils.event_loop.is_running(): 
        #    self.event_loop=lv_utils.event_loop()

    def off(self): 
        self.set_backlight(0)

    def hard_reset(self):
        if self.rst:
            for v in (0,1):
                self.rst.value(v)
                time.sleep_ms(100)

        self.init()
        
    def set_backlight(self, percent):
        if self.bl is None: 
            return
        if self.bl_pwm:
            self.bl.duty_u16(percent * 655)
        elif percent <= 25:
            self.bl.value(0)
        else:
            self.bl.value(1)

    def blit(self, x, y, w, h, buf, is_blocking=True):
        # Memory write by DMA, disp_flush_ready when finished

        if self.rp2_dma: 
            self._rp2_write_register_dma(0x2C, buf, is_blocking)
        else: 
            self.send_cmd(0x2C, buf)


    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
        )

        self.cmd_trans_data[0] = reg
        if self.cs:
            self.cs.value(0)

        self.dc.value(0)

        self.spi.write(self.cmd_trans_data)

        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)
        if self.cs:
            self.cs.value(1)
    
    def send_cmd(self, cmd, buf=None):
        if self.cs:
            self.cs.value(0)

        self.dc.value(0)
        self.cmd_trans_data[0] = cmd
        self.spi.write(self.cmd_trans_data)
        
        if buf is not None:
            self.dc.value(1)
            self.spi.write(buf)

        if self.cs:
            self.cs.value(1)

    def 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

        # Column addresses
        x1 = area.x1
        x2 = area.x2

        self.word_trans_data[0] = (x1 >> 8) & 0xFF
        self.word_trans_data[1] = x1 & 0xFF
        self.word_trans_data[2] = (x2 >> 8) & 0xFF
        self.word_trans_data[3] = x2 & 0xFF
        self.send_cmd(0x2A, self.word_trans_data)

        # Page addresses        
        y1 = area.y1
        y2 = area.y2

        self.word_trans_data[0] = (y1 >> 8) & 0xFF
        self.word_trans_data[1] = y1 & 0xFF
        self.word_trans_data[2] = (y2 >> 8) & 0xFF
        self.word_trans_data[3] = y2 & 0xFF
        self.send_cmd(0x2B, self.word_trans_data)

        width = x2 - x1 + 1
        height = y2 - y1 + 1
        data_view = color.__dereference__(width * height * lv.color_t.__SIZE__)

        self.blit(x1, y1, width, height, data_view, is_blocking=False)

        self.disp_drv.flush_ready()

    def init(self):
        if self.rot in (-1, -3):
            self.width, self.height = self.res
        else:
            self.height, self.width = self.res

        self._run_seq(
            [
                (0x01,None, 120),
                (0x11, None, 120),
                (0xF0,b"\xc3"),
                (0xF0,b"\x96"),
                (0x36,bytes([bytes([self.madctl(self.colormode, rot, ORIENTATION_TABLE))),
                #(0X36,b"\x0"),
                (0x3A,b"\x05"),  # pixelformat
                (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 rot in (-1, -3):
            self.width, self.height = self.res
        else:
            self.height, self.width = self.res

        self.send_cmd(_MADCTL, bytes([self.madctl(self.colormode, rot, ORIENTATION_TABLE))
    
    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

    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:
              self.send_cmd(*cmd)
            elif len(cmd) == 3: 
                self.send_cmd(*cmd[:2])
                time.sleep_ms(cmd[-1])
            else: 
              raise ValueError('Command #%d has %d items (must be 2 or 3)'%(i,len(cmd)))


spi = maachine.SPI(1, baudrate=27000000, sck=machine.Pin(10), mosi=machine.Pin(11), miso=machine.Pin(8))
drv = St7796(width=320, height=480, factor=8, spi=spi, dc=23, cs=29, rst=28)

scr = lv.scr_act()
scr.set_style_bg_color(lv.color_hex(0x000000), lv.PART.MAIN)

btn = lv.btn(scr)
btn.set_style_bg_color(lv.color_hex(0xFF0000), lv.PART.MAIN)


label = lv.label(btn)
label.set_text("Hello World!")

btn.set_width(120)
btn.set_height(35)

btn.center()

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()

Corrected the spelling mistakes and the missing parenthesis. I uploaded the code a couple of times but its giving me a blank screen

from micropython import const
import lvgl as lv
import machine
import time
import struct
# import lv_utils

COLOR_MODE_RGB = const(0x00)
COLOR_MODE_BGR = const(0x08)


_MADCTL = const(0x36)
_MADCTL_RTL = const(0x04)  # refresh right to left

_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)


# Negative orientation constants indicate the MADCTL value will come from the ORIENTATION_TABLE,
# otherwise the rot value is used as the MADCTL value.
PORTRAIT = const(-1)
LANDSCAPE = const(-2)
INV_PORTRAIT = const(-3)
INV_LANDSCAPE = const(-4)



class St7796(object):
    def __init__(self, width, height, cs, dc, spi, bl=-1, rst=-1, rot=LANDSCAPE, colormode=COLOR_MODE_RGB, rp2_dma=None, bl_pwm=False, doublebuffer=False, factor=8):
        """See :obj:`Ili9341_hw` for the meaning of the parameters."""

        if lv.color_t.__SIZE__ != 2: 
            raise RuntimeError('LVGL *must* be compiled with LV_COLOR_DEPTH=16')

        bufSize = int((width * height * lv.color_t.__SIZE__) // factor)

        if not lv.is_initialized(): 
            lv.init()

        self.cmd_trans_data = bytearray(1)
        self.word_trans_data = bytearray(4)

        self.flush_buf1 = bytearray(bufSize)
        if doublebuffer:
            self.flush_buf2 = bytearray(bufSize)
        else:
            self.flush_buf2 = None

        if colormode == COLOR_MODE_RGB:
            colorformat = lv.COLOR_FORMAT.NATIVE_REVERSED
        else:
            colorformat = lv.COLOR_FORMAT.NATIVE
        
        self.colormode = colormode
        self.colorformat = colorformat

        self.disp_drv = lv.disp_create(width, height)
        self.disp_drv.set_flush_cb(self.flush_cb)
        self.disp_drv.set_draw_buffers(self.flush_buf1, self.flush_buf2, int(bufSize // lv.color_t.__SIZE__), lv.DISP_RENDER_MODE.PARTIAL)
        self.disp_drv.set_color_format(colorformat)
        
        if isinstance(cs, int):
            if cs == -1:
               cs = None
            else:
                cs = machine.Pin(cs, machine.Pin.OUT)

        if isinstance(dc, int):
            dc = machine.Pin(dc, machine.Pin.OUT)

        if isinstance(rst, int):
            if rst == -1:
               rst = None
            else:
                rst = machine.Pin(cs, machine.Pin.OUT)
                rst.value(1)

        if isinstance(bl, int):
            if bl == -1:
               bl = None
            else:
                bl = machine.Pin(cs, machine.Pin.OUT)

        if bl is not None and bl_pwm:
            bl = machine.PWM(bl)
            
            
        self.cs = cs
        self.dc = dc
        self.rst = rst
        self.bl = bl
        self.bl_pwm = bl_pwm

        self.rot = rot
        self.width = 0
        self.height = 0
        self.res = (width, height)

        self.rp2_dma = rp2_dma
        self.spi = spi
        self.init()
        
        # create event loop if not yet present
        #comment this section out if you do not want to use an ISR to handle the refreshing of the display
        # if not lv_utils.event_loop.is_running(): 
        #    self.event_loop=lv_utils.event_loop()

    def off(self): 
        self.set_backlight(0)

    def hard_reset(self):
        if self.rst:
            for v in (0,1):
                self.rst.value(v)
                time.sleep_ms(100)

        self.init()
        
    def set_backlight(self, percent):
        if self.bl is None: 
            return
        if self.bl_pwm:
            self.bl.duty_u16(percent * 655)
        elif percent <= 25:
            self.bl.value(0)
        else:
            self.bl.value(1)

    def blit(self, x, y, w, h, buf, is_blocking=True):
        # Memory write by DMA, disp_flush_ready when finished

        if self.rp2_dma: 
            self._rp2_write_register_dma(0x2C, buf, is_blocking)
        else: 
            self.send_cmd(0x2C, buf)


    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
        )

        self.cmd_trans_data[0] = reg
        if self.cs:
            self.cs.value(0)

        self.dc.value(0)

        self.spi.write(self.cmd_trans_data)

        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)
        if self.cs:
            self.cs.value(1)
    
    def send_cmd(self, cmd, buf=None):
        if self.cs:
            self.cs.value(0)

        self.dc.value(0)
        self.cmd_trans_data[0] = cmd
        self.spi.write(self.cmd_trans_data)
        
        if buf is not None:
            self.dc.value(1)
            self.spi.write(buf)

        if self.cs:
            self.cs.value(1)

    def 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

        # Column addresses
        x1 = area.x1
        x2 = area.x2

        self.word_trans_data[0] = (x1 >> 8) & 0xFF
        self.word_trans_data[1] = x1 & 0xFF
        self.word_trans_data[2] = (x2 >> 8) & 0xFF
        self.word_trans_data[3] = x2 & 0xFF
        self.send_cmd(0x2A, self.word_trans_data)

        # Page addresses        
        y1 = area.y1
        y2 = area.y2

        self.word_trans_data[0] = (y1 >> 8) & 0xFF
        self.word_trans_data[1] = y1 & 0xFF
        self.word_trans_data[2] = (y2 >> 8) & 0xFF
        self.word_trans_data[3] = y2 & 0xFF
        self.send_cmd(0x2B, self.word_trans_data)

        width = x2 - x1 + 1
        height = y2 - y1 + 1
        data_view = color.__dereference__(width * height * lv.color_t.__SIZE__)

        self.blit(x1, y1, width, height, data_view, is_blocking=False)

        self.disp_drv.flush_ready()

    def init(self):
        if self.rot in (-1, -3):
            self.width, self.height = self.res
        else:
            self.height, self.width = self.res

        self._run_seq(
            [
                (0x01,None, 120),
                (0x11, None, 120),
                (0xF0,b"\xc3"),
                (0xF0,b"\x96"),
                (0x36,bytes([bytes([self.madctl(self.colormode, rot, ORIENTATION_TABLE)])])),
                #(0X36,b"\x0"),
                (0x3A,b"\x05"),  # pixelformat
                (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 rot in (-1, -3):
            self.width, self.height = self.res
        else:
            self.height, self.width = self.res

        self.send_cmd(_MADCTL, bytes([self.madctl(self.colormode, rot, ORIENTATION_TABLE)]))
    
    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

    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:
              self.send_cmd(*cmd)
            elif len(cmd) == 3: 
                self.send_cmd(*cmd[:2])
                time.sleep_ms(cmd[-1])
            else: 
              raise ValueError('Command #%d has %d items (must be 2 or 3)'%(i,len(cmd)))


spi = machine.SPI(1, baudrate=27000000, sck=machine.Pin(10), mosi=machine.Pin(11), miso=machine.Pin(8))
drv = St7796(width=320, height=480, factor=8, spi=spi, dc=23, cs=29, rst=28)

scr = lv.scr_act()
scr.set_style_bg_color(lv.color_hex(0x000000), lv.PART.MAIN)

btn = lv.btn(scr)
btn.set_style_bg_color(lv.color_hex(0xFF0000), lv.PART.MAIN)


label = lv.label(btn)
label.set_text("Hello World!")

btn.set_width(120)
btn.set_height(35)

btn.center()

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()

does the back lighting turn on?

yes its turning on

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