Errors building LVGL micropython for esp32-s3, but esp32 works

also the “DE” pin is the pin used to change from data mode to command mode. I do not think the Intel8080 driver that is built into the ESPIDF can be used for this display. I would have to do more reading up on the intel8080 interface but I believe it is a wide path SPI. The display you have does not have the pins set up in this manner. I didn’t see any mention of Intel8080 anywhere in the data sheet either.

All I am doing is taking the already existing modILI9341.c code and removing all of the SPI stuff and putting in the needed things for your display. It will be nice having the command registers to be able to initialize the display properly

Thanks!

I had that schematic from makerfabs github already, but not the precise LCD Driver reference.

The (working) arduino demo with arduino GFX lib does all the init and handling of the panel with a handful of esp_lcd calls. The only thing they feed it are the pins number (including DE) as well as timings, all in the esp_lcd_rgb_panel_config_t struct.
Nothing else in what I saw is specific to my screen, The Arduino GFX code does no more and it just “works”.
Similar parallel screens are defined the same way (except the pins) in the gfx lib as well.

I understand the multiple fb and copy madness you pointed out, but all the low level stuff really is fully handled by the esp_lcd stuff already.
Once you have the panel config struct, it literally boils down to 3 calls

ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(_panel_config, &_panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(_panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(_panel_handle));

That do all the low level magic and from there we have access to the fb.

What am I missing? Is there a reason that esp_lcd code would not work the same in Micropython context? (Since I went through the hole, I’m trying to properly understand)

The Arduino GFX library doesn’t do anything in the way of initializing it. It just blasts out the data is all it does.

I have yet to look at the data sheet for it. I am wondering if it is supposed to operate anything like the ILI or the ST driver ICs where a command needs to be sent to set the page address for what has updated otherwise it is going to always do full screen redraws. That would make sense as to why Makerfabs states 20fps. I get close to that using 4 wire SPI. parallel should be 16 times faster because it is able to send 16 bits at a single time where SPI does it one at a time.

You have to remember that data types in Python are not the same as C code and something could be getting mucked up somewhere in between.

Sorry for the noob question, but then there is no need for init, it just works with its default config on Arduino. What will we gain from properly initializing it?
From what I understood, no page on these RGB displays. A full refresh, often enough, is required. They are a completely different beast than the previous displays, also why the esp_lcd comes handy by abstracting and handling all the fine sync and timings.

Data types: Yep. To get things as simple as possible, all I do, called from a single python function with no param and no return, is pure c code adapted from arduino gfx. There is no data transfered between C and Python, I do draw in the fb from C as well.
I’ll try to trace the config struct etc in both contexts to triple check I didn’t mess up.

OK I am finished and it compiles. it running properly is a whole different story, :crazy_face:
You are going to have to modify a couple of the makefiles in order to get it to compile.

I am going to go and eat dinner and I will attach the source file and tell you what you have to change in the makefiles in order to get it to compile. I have a feeling that you already have one of the changes made otherwise you wouldn’t be able to compile what you have so far.

OK so here goes.

put the attached file into micropython/lib/lv_bindings/driver/esp32

edit micropython/lib/lv_bindings/mkrules.cmake

at the very bottom change

if(ESP_PLATFORM)
    LIST(APPEND LV_SRC
        ${LV_BINDINGS_DIR}/driver/esp32/espidf.c
        ${LV_BINDINGS_DIR}/driver/esp32/modrtch.c
        ${LV_BINDINGS_DIR}/driver/esp32/sh2lib.c
        ${LV_ESPIDF}
    )
endif(ESP_PLATFORM)

to

if(ESP_PLATFORM)
    LIST(APPEND LV_SRC
        ${LV_BINDINGS_DIR}/driver/esp32/espidf.c
        ${LV_BINDINGS_DIR}/driver/esp32/modrtch.c
        ${LV_BINDINGS_DIR}/driver/esp32/sh2lib.c
        ${LV_BINDINGS_DIR}/driver/esp32/makerfabs.c
        ${LV_ESPIDF}
    )
endif(ESP_PLATFORM)

then edit micropython/ports/esp32/main/CMakeLists.txt

in the middle of the file around line 106ish you will see

set(IDF_COMPONENTS
    app_update

change that to

set(IDF_COMPONENTS
    esp_lcd
    app_update

compile it and load it onto your ESP

Python code

from makerfabs import makerfabs

display = makerfabs(
    de=40, vsync=41, hsync=39, pclk=42, 
    d0=45, d1=48, d2=47, d3=21, d4=14, d5=5, d6=6, d7=7, 
    d8=15, d9=16, d10=4, d11=8, d12=3, d13=46, d14=9, d15=1, 
    width=800, height=480, 
    hsync_polarity=0, hsync_front_porch=8, hsync_pulse_width=4, 
    hsync_back_porch=8, vsync_polarity=0, vsync_front_porch=8, 
    vsync_pulse_width=4, vsync_back_porch=8, pclk_active_neg=1, 
    speed=16000000
)

You have to add the code for handling the back light for the display. simple GPIO and turn it on.

And that should do it. This is not going to do the touch. I am going to work on that in a little bit. That will be written in Python.

I am not sure if LVGL is going to play nice nice with using the buffer that is made by espidf. we will soon find out. I manually called the draw function in the flush instead of dropping the buffer into a queue to be written. This is because of using a shared buffer

makerfabs.c (16.2 KB)

Thanks!

This is quite close to what I did myself so far, yours is more organized and with a proper MP interface.
Note there was a missing MP_REGISTER_MODULE(MP_QSTR_Makerfabs, mp_module_makerfabs);
for the module to be exposed to MP.

Then

from machine import Pin
led = Pin(2, Pin.OUT)
led.on()
import Makerfabs
display = Makerfabs.display(de=40, vsync=41, hsync=39, pclk=42, d0=45, d1=48, d2=47, d3=21, d4=14, d5=5, d6=6, d7=7, d8=15, d9=16, d10=4, d11=8, d12=3, d13=46, d14=9, d15=1, width=800, height=480, hsync_polarity=0, hsync_front_porch=8, hsync_pulse_width=4, hsync_back_porch=8, vsync_polarity=0, vsync_front_porch=8, vsync_pulse_width=4, vsync_back_porch=8, pclk_active_neg=1, speed=6000000)

I set speed to 6000000 because CONFIG_SPIRAM_MODE_QUAD , Arduino_GFX/Arduino_ESP32RGBPanel.cpp at 3e4f9a0389d0c634cba798ef464f021ab5659b19 · moononournation/Arduino_GFX · GitHub

At that point, display.init() crashed in lv_disp_draw_buf_init()
I commented out the lv stuff, since a pixel was written above as a test anyway.
Now, the behaviour is the same as my Frankenstein code: compiles, all esp_lcd calls return ok, I can write into the fb, bit nothing shows on the lcd.
At init, the lcd refreshes fully white (from black initially), and none of the pixels I write are to be seen.

I then tried with speed=16000000 just to make sure, and there I can see my pixels.
So, I misunderstood the CONFIG_SPIRAM_MODE_QUAD and should have tried that before.

With that speed config my code works as well, and the rects I draw into the fb are shown.

Now I can dig the lvgl coupling!!!

OK so I am guessing it is working then? I would rather buff shine the module I made to get it all good so it can be added to the Micropython binding. The mechanism that is being used is not specific to makerfabs and it can be used as a raw staring point for either 8 bit or 16 bit parallel interfaces. So what would need to be done is a mechanism would need to be added so the flush function would either be able to send out any control commands that are needed before and after and also during. maybe using some constants and a callback being passed that would get called from inside the flush function. I kind of like that idea.

If you can pass me back the file I sent over with your modifications to get it running and I will start working on the touchscreen code. I have a custom touch interface I wrote for one of my displays that I can modify so the turn around time on that should be pretty fast as well.

Yes, and not yet.
Low level fb write is ok, 3 pixels are on the screen.
LVGL not yet.
I tweaked a few things in the C file related to the lvgl init, but wasn’t able to have it not crash in C.
It’s now commented out, and I’m trying from python.

I’m joining my current .c file as well as the py test.
Nothing crashes, but the screen remains like after init, 3 pixels, and flush does not seem to be called.

from machine import Pin
import Makerfabs
import lvgl as lv
import lv_utils


def mf_test_fb:
    led = Pin(2, Pin.OUT)  # Backlight
    led.on()
    lv.init()

    disp = Makerfabs.display(de=40, vsync=41, hsync=39, pclk=42, d0=45, d1=48, d2=47, d3=21, d4=14, d5=5, d6=6, d7=7, d8=15, d9=16, d10=4, d11=8, d12=3, d13=46, d14=9, d15=1, width=800, height=480, hsync_polarity=0, hsync_front_porch=8, hsync_pulse_width=4, hsync_back_porch=8, vsync_polarity=0, vsync_front_porch=8, vsync_pulse_width=4, vsync_back_porch=8, pclk_active_neg=1, speed=16000000)
    disp.init()

    # lv buffer
    disp_buf1 = lv.disp_draw_buf_t()
    buf1_1 = bytearray(800*10)
    disp_buf1.init(buf1_1, None, len(buf1_1))
    disp_drv = lv.disp_drv_t()
    disp_drv.init()
    disp_drv.draw_buf = disp_buf1
    disp_drv.flush_cb = disp.flush
    disp_drv.hor_res = 800
    disp_drv.ver_res = 480
    disp_drv.register()

    # lvgl hello world
    scr = lv.obj()
    btn = lv.btn(scr)
    btn.align(lv.ALIGN.CENTER, 0, 0)
    label = lv.label(btn)
    label.set_text("Hello World!")
    lv.scr_load(scr)

makerfabs.c (19.5 KB)

this is wrong

buf1_1 = bytearray(800*10)
disp_buf1.init(buf1_1, None, len(buf1_1))

The buffer needs to be whatever number of pixels * 2. The times 2 is 2 bytes for the colors. It is always best to use a width * height * 2 // factor. This ensures that you do not end up with buffer space being allocated that is not able to be filled.

The call to init for the display buffer needs to have the number of pixels passed, not the size of the buffer.
so

disp_buf1.init(buf1_1, None, len(buf1_1) // 2)

It is also better to allocate the buffer in a non python object.


import lv_utils
import lvgl as lv


width = 800
height = 480
factor = 8

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

buf1 = esp.heap_caps_malloc(buf_size, esp.MALLOC_CAP.DMA)

disp_buf = lv.disp_draw_buf_t()
disp_drv = lv.disp_drv_t()

disp_buf.init(buf1, None, buf_size // lv.color_t.__SIZE__)
disp_drv.init()


monitor_acc_time = 0
monitor_acc_px = 0
monitor_count = 0


def flush_cb(self, disp_drv_, area, color_p):
    display.panel_handle.draw_bitmap(display.panel_handle, area.x1, area.y1, area.x2, area.y2, color_p)


def monitor_cb(disp_drv_, time, px):
    global monitor_acc_time
    global monitor_acc_px 
    global monitor_count

    monitor_acc_time += time
    monitor_acc_px += px
    monitor_count += 1


disp_drv.draw_buf = disp_buf
disp_drv.flush_cb = fush_cb
disp_drv.monitor_cb = monitor_cb
disp_drv.hor_res = width
disp_drv.ver_res = height


event_loop = lv_utils.event_loop(asynchronous=False)

something along those lines.

you will not be able to allocate the memory in DMA space on the internal ram unless you use a factor of 8 or greater. That is because of the size of your display. You can also change the DMA flag to indicate another area of memory that is not DMA internal memory.

also I messed up in the flush code.

def flush_cb(self, disp_drv_, area, color_p):
    x1 = area.x1
    x2 = area.x2
    y1 = area.y1
    y2 = area.y2

    size = (x2 - x1 + 1) * (y2 - y1 + 1)
    data_view = color_p.__dereference__(size * lv.color_t.__SIZE__)

    display.panel_handle.draw_bitmap(display.panel_handle, x1, y1, x2, y2, data_view)

I am not sure if draw_bitmap uses the x1, y1, x2, y2 as location identifiers on where to collect the data from the buffer or not. It should only be where you want to render it to the display but I could be wrong. If it uses the coords to locate the position in the buffer where the data is then my first flush function would be the correct one to use.

Almost good.
The event_loop was the missing piece, and not there in the lvgl py examples.

import Makerfabs
from machine import Pin
led = Pin(2, Pin.OUT)
led.on()
import lvgl as lv
import lv_utils
import espidf as esp
lv.init()

disp = Makerfabs.display(de=40, vsync=41, hsync=39, pclk=42, d0=45, d1=48, d2=47, d3=21, d4=14, d5=5, d6=6, d7=7, d8=15, d9=16, d10=4, d11=8, d12=3, d13=46, d14=9, d15=1, width=800, height=480, hsync_polarity=0, hsync_front_porch=8, hsync_pulse_width=4, hsync_back_porch=8, vsync_polarity=0, vsync_front_porch=8, vsync_pulse_width=4, vsync_back_porch=8, pclk_active_neg=1, speed=16000000)
disp.init()

width = 800
height = 480
factor = 8
buf_size = (width * height * lv.color_t.__SIZE__) // factor
disp_buf1 = lv.disp_draw_buf_t()
buf1_1 = esp.heap_caps_malloc(buf_size, esp.MALLOC_CAP.DMA)
disp_buf1.init(buf1_1, None, buf_size // lv.color_t.__SIZE__)

disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = disp_buf1
disp_drv.flush_cb = disp.flush
disp_drv.hor_res = width
disp_drv.ver_res = height
lvd=disp_drv.register()

scr = lv.obj()
btn = lv.btn(scr)
btn.align(lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text("Hello World!")
lv.scr_load(scr)

event_loop = lv_utils.event_loop(asynchronous=False)

Directly using the C flush cb; display.panel_handle is not visible from python.

Now, looks like there are 2 offset by 1 errors in the work buffer => screen buffer copy.
I can see the 8 work buffers separated by a black line each, and the button being shifted right.

I’ll report again after a couple of recompile

Here is the final working flush cb

STATIC void makerfabs_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) {
    makerfabs_t *self = g_makerfabs;
    esp_lcd_panel_draw_bitmap(self->panel_handle, area->x1, area->y1, area->x2 + 1 , area->y2 + 1, color_p);
	lv_disp_flush_ready(disp_drv);
}

Thanks a lot for your help!
I was about to let the boards sleep in a drawer until they are supported…

Like often, the doc is mostly there, but scattered all over the place, addressing several versions, and someone like you with a global view of the arch is needed to have all the pieces fall in place…


I activated the perf monitor with the custom esp tick from here Show CPU usage and FPS count - #6 by Meekdai

It says 30 FPS with nothing animated, 25 FPS and 19% CPU with an LVGL spinner.
As expected, screen shakes a bit when a CPU intensive task is run.

That’s not too bad a refresh rate. The ESP running Micropython is not the strongest in terms of performance. Glad it’s working that’s a huge deal. Now only if Maker fabs made an 8bit version so there were GPIOs left to use. I have something that I am working on where I need to have 10 pins to use outside of the display and that can be pulled off using the S3 and an 8bit display. Not a 16 tho.

Next missing feature in line, apart from the touch driver:
Do you know how to activate usb-hid with esp32-s3, do you know if there is a working lib already?
One of the usb is a native one.
Arduino and circuit Python have it, but I did not found an example with micropython.

I was looking at circuit python for the mouse. It appears that it is for mouse output not input. meaning you can use the ESP as a mouse but you cannot plug a mouse into the ESP.