Is it possible to run ESP32+micropython+LVGL with ST7796 display?

Hi!
I have a ST7796 4.0 SPI TFT 480x320 display and a ESP32 board with Micropython and LVGL on it. An ili9341 320x240 display works fine with this setup, but when I try to use ili9341 driver with the ST7796, only a part of the display is working and colors are a little incorrect.
On the forum I saw a message regarding running a ST7796s display with the ili9488 driver - because they are very similar. If I try to use ili9488 driver - it requires COLOR DEPTH parameter = 32 while building the firmware. Building is a complicated :slight_smile: Did somebody try to make this kind of displays work with LVGL and Micropython? Does it work and what driver do I need to use?

Thanks in advance!


import espidf as esp
import lvgl as lv
import ili9XXX


class st7796(ili9XXX.ili9XXX):

    # The st7795 display controller has an internal framebuffer
    # arranged in 320 x 480
    # configuration. Physical displays with pixel sizes less than
    # 320 x 480 must supply a start_x and
    # start_y argument to indicate where the physical display begins
    # relative to the start of the
    # display controllers internal framebuffer.

    # this display driver supports RGB565 and also RGB666. RGB666 is going to
    # use twice as much memory as the RGB565. It is also going to slow down the
    # frame rate by 1/3, This is becasue of the extra byte of data that needs
    # to get sent. To use RGB666 the color depth MUST be set to 32.
    # so when compiling
    # make sure to have LV_COLOR_DEPTH=32 set in LVFLAGS when you call make.
    # For RGB565 you need to have LV_COLOR_DEPTH=16

    # the reason why we use a 32 bit color depth is because of how the data gets
    # written. The entire 8 bits for each byte gets sent. The controller simply
    # ignores the lowest 2 bits in the byte to make it a 6 bit color channel
    # We just have to tell lvgl that we want to use

    def __init__(
        self,
        miso=-1,
        mosi=19,
        clk=18,
        cs=13,
        dc=12,
        rst=4,
        power=-1,
        backlight=15,
        backlight_on=1,
        power_on=0,
        spihost=esp.HSPI_HOST,
        spimode=0,
        mhz=80,
        hybrid=True,
        width=320,
        height=480,
        start_x=0,
        start_y=0,
        colormode=ili9XXX.COLOR_MODE_RGB,
        rot=ili9XXX.LANDSCAPE,
        invert=False,
        double_buffer=False,
        half_duplex=True,
        asynchronous=False,
        initialize=True,
        color_format=lv.COLOR_FORMAT.NATIVE
    ):

        if lv.color_t.__SIZE__ == 4:
            display_type = ili9XXX.DISPLAY_TYPE_ILI9488
            pixel_format = 0x06  # 262K-Colors
        elif lv.color_t.__SIZE__ == 2:
            pixel_format = 0x05  # 65K-Colors  55??
            display_type = ili9XXX.DISPLAY_TYPE_ST7789

        else:
            raise RuntimeError(
                'ST7796 micropython driver '
                'requires defining LV_COLOR_DEPTH=32 or LV_COLOR_DEPTH=16'
            )

        self.display_name = 'ST7796'

        self.init_cmds = [
            {'cmd': 0x01, 'delay':120},  # SWRESET
            {'cmd': 0x11, 'delay':120},  # SLPOUT
            {'cmd': 0xF0, 'data': bytes([0xC3])},  # CSCON  Enable extension command 2 partI
            {'cmd': 0xF0, 'data': bytes([0x96])},  # CSCON  Enable extension command 2 partII
            {'cmd': 0x36, 'data': bytes([self.madctl(colormode, rot, ili9XXX.ORIENTATION_TABLE)])},  # MADCTL
            # Interface_Pixel_Format
            {'cmd': 0x3A, 'data': bytes([pixel_format])},
            {'cmd': 0xB4, 'data': bytes([0x01])},  # INVTR  1-dot inversion
            {'cmd': 0xB6, 'data': bytes([
                0x80,  # Bypass
                0x02,  # Source Output Scan from S1 to S960, Gate Output scan from G1 to G480, scan cycle=2
                0x3B  # LCD Drive Line=8*(59+1)
            ])},  # DFC
            {'cmd': 0xE8, 'data': bytes([
                0x40,
                0x8A,
                0x00,
                0x00,
                0x29,  # Source eqaulizing period time= 22.5 us
                0x19,  # Timing for "Gate start"=25 (Tclk)
                0xA5,  # Timing for "Gate End"=37 (Tclk), Gate driver EQ function ON
                0x33
            ])},  # DOCA
            {'cmd': 0xC1, 'data': bytes([0x06])},  # PWR2 VAP(GVDD)=3.85+( vcom+vcom offset), VAN(GVCL)=-3.85+( vcom+vcom offset)
            {'cmd': 0xC2, 'data': bytes([0xA7])},  # PWR3 Source driving current level=low, Gamma driving current level=High
            {'cmd': 0xC5, 'data': bytes([0x18]), 'delay':120},  # VCMPCTL VCOM=0.9
            {'cmd': 0xE0, 'data': bytes([
                0xF0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2F,
                0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B
            ])},  # PGC
            {'cmd': 0xE1, 'data': bytes([
                0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, 0x2B,
                0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B
            ]), 'delay':120},  # NGC
            {'cmd': 0xF0, 'data': bytes([0x3C])},  # CSCON  Disable extension command 2 partI
            {'cmd': 0xF0, 'data': bytes([0x69]), 'delay':120},  # CSCON Disable extension command 2 partII
            {'cmd': 0x29}  # DISPON
        ]

        super().__init__(
            miso=miso,
            mosi=mosi,
            clk=clk,
            cs=cs,
            dc=dc,
            rst=rst,
            power=power,
            backlight=backlight,
            backlight_on=backlight_on,
            power_on=power_on,
            spihost=spihost,
            spimode=spimode,
            mhz=mhz,
            hybrid=hybrid,
            width=width,
            height=height,
            start_x=start_x,
            start_y=start_y,
            invert=invert,
            double_buffer=double_buffer,
            half_duplex=half_duplex,
            display_type=display_type,
            asynchronous=asynchronous,
            initialize=initialize,
            color_format=color_format
        )

# #define PIN_SDA 18
# #define PIN_SCL 19
# #define TFT_MISO 12
# #define TFT_MOSI 13
# #define TFT_SCLK 14
# #define TFT_CS   15
# #define TFT_DC   21
# #define TFT_RST  22
# #define TFT_BL   23


LED = 23
RESET = 22
MOSI = 13
CS = 15
DC_RS = 21
SCK = 14
MISO = 12

# init display
disp = st7796(
    miso=MISO,
    mosi=MOSI,
    clk=SCK,
    cs=CS,
    dc=DC_RS,
    rst=RESET,
    backlight=LED,
    power=-1,
    width=480,
    height=320,
    rot=ili9XXX.LANDSCAPE
)

#
# screen = Screen()
#
# ui.Main(screen)
# lv.scr_load(screen)

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)
# Load the screen

lv.scr_load(scr)

You have to flip flop the width and height if you use portrait.

@kdschlosser this was very helpful. I am using RP2040 with micropython LVGL with ST7796 display. When I use this code, I am getting this error

AttributeError: 'st7796' object has no attribute 'madctl'

@Mishal_Ferrao : you´re hijacking an old thread that was buried months ago and is related to the ESP32. That makes it confusing for anyone. Please read FAQ - LVGL Forum

That’s because this driver is meant for the ESP32. At the time I posted this I didn’t realize the API didn’t match between the different MCU’s that are supported. If you want I can port the code so it will work on the RP2040 but you will have to wait until I get my PC fixed. I am waiting on a part that is being shipped for warranty exchange. Power supply went bad and it is not something I can pick up at a store. I can tinker about with it using a text editor but it is going to have bugs for dumb things like typos in it that will need to be corrected.