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

The function is in lv_espidf.c

/*
 * espidf extension definition for:
 * esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel)
 */

STATIC mp_obj_t mp_esp_lcd_new_rgb_panel(size_t mp_n_args, const mp_obj_t *mp_args, void *lv_func_ptr)
{
    const esp_lcd_rgb_panel_config_t *rgb_panel_config = (const esp_lcd_rgb_panel_config_t *)mp_write_ptr_esp_lcd_rgb_panel_config_t(mp_args[0]);
    esp_lcd_panel_handle_t *ret_panel = mp_to_ptr(mp_args[1]);
    esp_err_t _res = ((esp_err_t (*)(const esp_lcd_rgb_panel_config_t *, esp_lcd_panel_handle_t *))lv_func_ptr)(rgb_panel_config, ret_panel);
    return mp_obj_new_int(_res);
}

 

STATIC MP_DEFINE_CONST_LV_FUN_OBJ_VAR(mp_esp_lcd_new_rgb_panel_mpobj, 2, mp_esp_lcd_new_rgb_panel, esp_lcd_new_rgb_panel);
    

STATIC const mp_rom_map_elem_t mp_esp_lcd_rgb_panel_config_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(esp_lcd_rgb_panel_config_t))) },
    { MP_ROM_QSTR(MP_QSTR_new_rgb_panel), MP_ROM_PTR(&mp_esp_lcd_new_rgb_panel_mpobj) },
    
};

STATIC MP_DEFINE_CONST_DICT(mp_esp_lcd_rgb_panel_config_t_locals_dict, mp_esp_lcd_rgb_panel_config_t_locals_dict_table);
        

STATIC const mp_rom_map_elem_t mp_esp_lcd_rgb_timing_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(esp_lcd_rgb_timing_t))) },
    
};

STATIC MP_DEFINE_CONST_DICT(mp_esp_lcd_rgb_timing_t_locals_dict, mp_esp_lcd_rgb_timing_t_locals_dict_table);
        

STATIC const mp_rom_map_elem_t mp_esp_lcd_rgb_timing_flags_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(esp_lcd_rgb_timing_flags_t))) },
    
};

STATIC MP_DEFINE_CONST_DICT(mp_esp_lcd_rgb_timing_flags_t_locals_dict, mp_esp_lcd_rgb_timing_flags_t_locals_dict_table);
        

STATIC const mp_rom_map_elem_t mp_esp_lcd_panel_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(struct esp_lcd_panel_t))) },
    
};

STATIC MP_DEFINE_CONST_DICT(mp_esp_lcd_panel_t_locals_dict, mp_esp_lcd_panel_t_locals_dict_table);
        

STATIC const mp_rom_map_elem_t mp_esp_lcd_rgb_panel_config_flags_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(esp_lcd_rgb_panel_config_flags_t))) },
    
};

STATIC MP_DEFINE_CONST_DICT(mp_esp_lcd_rgb_panel_config_flags_t_locals_dict, mp_esp_lcd_rgb_panel_config_flags_t_locals_dict_table);    

Just after a working one (with less additional structs, but similar):

/*
 * espidf extension definition for:
 * esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus)
 */

STATIC mp_obj_t mp_esp_lcd_new_i80_bus(size_t mp_n_args, const mp_obj_t *mp_args, void *lv_func_ptr)
{
    const esp_lcd_i80_bus_config_t *bus_config = (const esp_lcd_i80_bus_config_t *)mp_write_ptr_esp_lcd_i80_bus_config_t(mp_args[0]);
    esp_lcd_i80_bus_handle_t *ret_bus = mp_to_ptr(mp_args[1]);
    esp_err_t _res = ((esp_err_t (*)(const esp_lcd_i80_bus_config_t *, esp_lcd_i80_bus_handle_t *))lv_func_ptr)(bus_config, ret_bus);
    return mp_obj_new_int(_res);
}

 

STATIC MP_DEFINE_CONST_LV_FUN_OBJ_VAR(mp_esp_lcd_new_i80_bus_mpobj, 2, mp_esp_lcd_new_i80_bus, esp_lcd_new_i80_bus);
    

STATIC const mp_rom_map_elem_t mp_esp_lcd_i80_bus_config_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(esp_lcd_i80_bus_config_t))) },
    { MP_ROM_QSTR(MP_QSTR_new_i80_bus), MP_ROM_PTR(&mp_esp_lcd_new_i80_bus_mpobj) },
    
};

STATIC MP_DEFINE_CONST_DICT(mp_esp_lcd_i80_bus_config_t_locals_dict, mp_esp_lcd_i80_bus_config_t_locals_dict_table);

However, in the lv_espidf.json, it’s no more to be found, while the other is there

        "esp_lcd_new_panel_io_i80": {
            "type": "function",
            "args": [
                {
                    "type": "void*",
                    "name": "bus"
                },
                {
                    "type": "esp_lcd_panel_io_i80_config_t",
                    "name": "io_config"
                },
                {
                    "type": "void*",
                    "name": "ret_io"
                }
            ],
            "return_type": "int"
        },

I got no warning with a clean build.
The esp_lcd_new_rgb_panel func is both in lv_espidf.c.pp and lv_espidf.c.pp.filtered
So this looks like an issue with gen_mpy not converting this func for some reason, likely one of the extra struct used.

As for the ->del issue, rather than modifying existing existing build stuff that everyone relies on, wouldn’t it be more generic to use a wrapper like someone suggested on another thread?
I supposed there is little chance esp changes its structs just for some python specific build, and mp changes a default build component for a specific hardware and esp lib not following its conventions, so dealing with it “by hand” with a wrapper would limit the scope of the required changes to the mp esp32-s3 boards only. Less elegant, but easier to follow, more guarantees it remains compatible with future mp versions.

Update
I can confirm this is because of the structs, maybe the timings inside the config.
I create a new func with hardcoded params to make sure, like

esp_err_t esp_lcd_new_testrgb_panel(esp_lcd_panel_handle_t *ret_panel) {
  	esp_lcd_rgb_panel_config_t *_panel_config = (esp_lcd_rgb_panel_config_t *)heap_caps_calloc(1, sizeof(esp_lcd_rgb_panel_config_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
     _panel_config->clk_src = LCD_CLK_SRC_PLL160M;
   
  _panel_config->timings.pclk_hz = 16000000L;
  _panel_config->timings.h_res = 800;
  _panel_config->timings.v_res = 600;
[...]

And there it’s exposed to MP.
Not a long term solution, but this will allow me to move on with further testing.
(init now crashes with corrupted stack trace)

Update2
I reverted to the working makerfabs.c specific driver for now, so I could test the touch driver.
I couldn’t get the generic display driver to properly work.

I issued a PR with some fixes and comments on the GT911 driver, there is also specific stuff with I2C on esp32s3 that needs a patch, see comments at file begin.
I also added comments with returned values in the code.
The TP interrupt pin is not used at all in the current implementation, is that ok?

the del thing never worked right and it has not been used up until now. The original design was to change del to delete on some things and then for others it was to change del to _del. It was not something that I believe was finished and tested 100%. So now it works properly. I also added some code that allows us to exclude specific functions in the build. This is because of linking errors. Some of the functions that needed to be excluded that was being added are deprecated functions and not something that is going to be used.

For the ESP32-S3 there are 2 functions that get added that cause problems. I had originally written a second script that would get run to remove the functions from the generated c file. Since I added the ability to gen_mpy to remove functions that script is no longer needed. It keeps things somewhat cleaner. I didn’t want to add the specific functions directly to gen_mpy because there is nothing in the gen_mpy that is specific to what gen_mpy is converting and I wanted to keep it that way. So adding an additional command line switch so that specific function ignores could be passed would be the best way to go about it. The makefile contains the functions names instead and the makefile only passes them when building the lv_espidf.py file.

The whole mechanics of it depends on the IDF_TARGET macro getting set to the SOC that the program is being compiled for. Now I know that macro does get set in the board configuration files the issue is it gets set too late in the compilation process at that point. So that macro needs to be passe on the command line/terminal

The interrupt pin does not need to be used because of the polling nature of LVGL. I do not know how to turn off the polling for touch input in LVGL in favor of using an interrupt. Using an interrupt would be better to use I just do not know hot to get that 100% implemented at this point in time. I know how to work with the interrupts on the MPY side of things I don’t know the mechanics in LVGL yet.

It is there for future use and I will get that sorted out but it is not a requirement at this point in time to get it functioning.

Did you manager to get the touch interface up and running?

The reason why I made the change to the current display driver is because I was told that python drivers are wanted over c drivers. With using the gen_,mpy script to expose the esp_lcd module to micropython and not byte/bit swapping needing be be done inside of the flush callback writing the driver in Python can be done… I have not pushed my most recent changes yet. the generic parallel driver has been changed so it is a better base class that other drivers are to use. I did the same thing with the touch drivers as well. It gets rid of a large amount of repetitive code.

I have also been working on writing a wrapper around around LVGL that provides a more pythonic interface. making it easier to use. This might be something that is more of a thing that only I am going to use but when I am done with it I will make it publicly available for anyone that wants to use it.

I want to make sure there is clarification on why you are seeing a difference in the generated C file.

It’s all because of the IDF_TARGET macro and whether it is supplied on the command line or not. I realized this last night when I needed a mechanism to be able to determine if some functions needed to be omitted or not. So always compile it moving forward with IDF_TARGET=esp32s3 added to the build command you key in.

Nope.
It now compiles and does not throw any error when registering, talks to the chip and gets answers, but does nothing: no event is triggered, see my debug print in the PR I did, that is never called.
(see the comments in the PR I sent on your fork)

Thanks, I’ll give it a try and report back

I did make clean and re-compiled with IDF_TARGET=esp32s3 on the cmd line.
Shows “-- Building ESP-IDF components for target esp32s3” (like it was before) at start.

the esp_lcd_new_rgb_panel is still missing from micropython.
It’s the only esp func missing. the others esp_lcd, usb aso are there.
is it exposed on your build?

if so, is that part in your build the same as mine?:

The version I have locally is not the same as what is in my fork. I am working on putting together a commit for it. I have a large amount of stuff sorted out in it including the missing esp_lcd_new_rgb_panel

That function has not been added at the module level. It is instead built into the esp_lcd_rgb_panel_config_t structure.

esp_lcd_rgb_panel_config_t.new_rgb_panel(esp_lcd_panel_handle_t)

There is also no esp_lcd_panel_handle_t structure because that structure is only a pointer to esp_lcd_panel_t

so in the __init__ method you need to make self.panel_handle as follows.

self.panel_handle = espidf.esp_lcd_panel_t()

and if you look you will see this in the __init__ method

self.panel_config = (
            espidf.esp_lcd_rgb_panel_config_t.__cast__(panel_config)
        )

that means the following code in the init method
espidf.esp_lcd_new_rgb_panel(panel_config, self.panel_handle)

needs to change to

panel_config.new_rgb_panel(self.panel_handle)

In the version I have locally I removed the function that calls the __containerof macro and replaced the function with one that calls that macro and returns the frame buffer since that is the only thing that we need so we can pass it to self.disp_draw_buf.init. IDK if it will work or not because of not holding a reference to the esp_rgb_panel_t structure. we will find out I am sure.

Until I get the new commit all put together and pushed you can change the panel config stuff and it should work.

The trick to knowing where the different bits and pieces are going to show up is to look for blocks of code like this that contain what you are looking for

STATIC const mp_rom_map_elem_t mp_esp_lcd_rgb_panel_config_t_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___SIZE__), MP_ROM_PTR(MP_ROM_INT(sizeof(esp_lcd_rgb_panel_config_t))) },
    { MP_ROM_QSTR(MP_QSTR_new_rgb_panel), MP_ROM_PTR(&mp_esp_lcd_new_rgb_panel_mpobj) },
    
};

This is telling us that the function ‘mp_esp_lcd_new_rgb_panel_mpobj’ has a name of MP_QSTR_new_rgb_panel which translates into new_rgb_panel = esp_lcd_new_rgb_panel and we can find out where it is located by looking at the first line. mp_esp_lcd_rgb_panel_config_t_locals_dict_table means the function is in esp_lcd_rgb_panel_config_t

1 Like

Thanks for helping me understand!
This helps a lot. This may be obvious for someone practicing daily, much less to a newcomer or someone just hacking there and there every 3 years :smiley:

Yep, that I got and changed already

ret = panel_config.new_rgb_panel(self.panel_handle) is now ok,
init crashes just after with a corrupted stacktrace and “InstructionFetchError” when calling
ret = espidf.esp_lcd_panel_reset(self.panel_handle)

well the crash is a result that means the function is there it’s just not right. so have to work that one out which is a good thing.

It took me a while to understand what was going on with the generated C files and I really just started getting the grasp of it in the last few weeks of messing about with it. While I can read C code and understand quite a bit of it the mechanics behind what it is doing I have never learned so it’s a lot of trial and error for me. Hence the reason why I have the computer that I do which allows me to compile LVGL micropython in 43 seconds or so.

comment out the reset line. It doesn’t have to be there really. lets find out if that is what it is crashing on.

it could be freaking out because technically speaking the handle is not the type it should be. This may or may not cause a problem. dunno yet.

I now know why it is freaking out. That’s because none of the callback functions are defined. When the structures are ported over to micropython it doesn’t create the callback functions in the same manner that is seen in C code. So some additional steps are needed.

So this is what happens.

if you look at the esp_lcd_panel_t structure you will see fields in it that are named the following.
invert_color
disp_off
set_gap
swap_xy
mirror
draw_bitmap
del
init
reset

Those fields hold functions and those functions get called when you use these module level espidf functions
esp_lcd_panel_invert_color
esp_lcd_panel_disp_off
esp_lcd_panel_set_gap
esp_lcd_panel_swap_xy
esp_lcd_panel_mirror
esp_lcd_panel_draw_bitmap
esp_lcd_panel_del
esp_lcd_panel_init
esp_lcd_panel_reset

Now to complicate matters when those callbacks are used the code inside of the callback uses the following functions

esp_lcd_panel_io_tx_param
esp_lcd_panel_io_rx_param
esp_lcd_panel_io_tx_color
esp_lcd_panel_io_del

and those functions call the functions that are stored in the esp_lcd_panel_io_t structure under the following field names

tx_param
rx_param
tx_color
del

It was done this way so that “plugins” could be made for a display and the software would not need to have any knowledge of how it operates or what it does. It is a mechanism that is used to get data from one place to another while also allowing the driver (plugin) to inject any commands that may be needed during any of the different types of controls that are being done to the display.

Doesn’t the code I included above have all the display connections defined? This Arduino code does work on the board - I’ve compiled and tested it. It’s the standard LVGL widgets demo that has been edited for this board.

The Arduino code works because there are libraries that are made for the Arduino IDE and the Arduino SDK that add support for the displays. This code does not exist in MicroPython.

The libraries for the Arduino SDK cannot be ported easily, it would be easier to make them from scratch as I am doing…

The link you provided is for a single display and because the io portion of the code is written in Python it is not going to be very fast. It also only supports the 8bit parallel version of the display and not the 16bit. The ESP32 SDK which is called the ESPIDF has code in it for handling the communications with displays. This was a more recent addition to the ESPIDF and it has not been fully populated yet with a lot of drivers. The boilerplate code is in place but the display specific code is only available for 2 displays in the version of the ESPIDF that MicroPython supports. In the most recent version of the ESPIDF there is support for more displays but MicroPython does not support that version. To add to the complexity of it the drivers are not all built into the ESPIDF. they are available as “components” that have to be downloaded and things have to be done at compile time to get them added.

The authors of LVGL want to see the display drivers written in Python code with the underlying io mechanics handled in C code. Because of that it makes using the already made display drivers in the ESPIDF moot. We still have the availability to use the io bits that are built into the ESPIDF. That is what we are attempting to hammer out. There is not a huge amount in the way of examples out there because it is still a pretty recent addition.

Thank you for the clarification, this is useful !