High CPU and Low FPS Issue on my NXP MIMXRT1062 with LVGL V8.1.0

High CPU and FPS Issue on NXP MIMXRT1062 with LVGL V8.1.0

Environment

  • CPU: NXP MIMXRT1062
  • Display Size: 7 inch
  • Display Resolution: 800 x 480 RGB565
  • LVGL V8.1.0
  • FreeRTOS

Delete this section if you read and applied the mentioned points.

Description

Hi, I’m developing GUI based on LVGL V8.1.0 on my customized MIMXRT1062 board. I found an issue regarding CPU/FPS performance, I put some image and one animation on GUI, and found the CPU will be around 90% and FPS will dramatically drop to only 5 FPS(I set refresh period as 40ms, meaning FPS should be 25).

Then I did some simple test: If I just create a blank widgetContainer with nothing inside, the CPU is only around 5%, and FPS is 25 as expect. But when I put a static image of which size is about 400 x 400 inside the widgetContainer, the CPU will significantly increaed to around 55% and FPS drop to approx. 6.

Could anyone kindly help me on this issue?

Code to reproduce

Add the relevant code snippets here.

Here’s my lvgl_support.c:

#include <lvgl/lvgl.h>
#include "lvgl_support.h"
#include "FreeRTOS.h"
#include "task.h"

#include "fsl_elcdif.h"
#include "fsl_gpio.h"
#ifdef USE_TSC
#include "fsl_lpi2c.h"
#include "fsl_ft5406_rt.h"
#endif
#include "console_support.h"
#include "platform_rtos.h"
#include "platform_utils.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#ifdef USE_TSC
/* Macros for the touch touch controller. */
#define TOUCH_I2C LPI2C1

/* Select USB1 PLL (480 MHz) as master lpi2c clock source */
#define TOUCH_LPI2C_CLOCK_SOURCE_SELECT (0U)
/* Clock divider for master lpi2c clock source */
#define TOUCH_LPI2C_CLOCK_SOURCE_DIVIDER (5U)

#define TOUCH_I2C_CLOCK_FREQ ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 8) / (TOUCH_LPI2C_CLOCK_SOURCE_DIVIDER + 1U))
#define TOUCH_I2C_BAUDRATE 100000U
#endif

/* Macros for panel. */
#define LCD_HSW 2
#define LCD_HFP 44
#define LCD_HBP 16
#define LCD_VSW 2
#define LCD_VFP 38
#define LCD_VBP 10
#define LCD_POL_FLAGS \
    (kELCDIF_DataEnableActiveHigh | kELCDIF_VsyncActiveLow | kELCDIF_HsyncActiveLow | kELCDIF_DriveDataOnRisingClkEdge)
#define LCD_LCDIF_DATA_BUS kELCDIF_DataBus16Bit

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static void Board_InitLcd(void);

static void Board_InitLcdClock(void);

static void Board_InitLcdBackLight(void);

static void Board_FlushDisplay(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

/*******************************************************************************
 * Variables
 ******************************************************************************/
#ifdef USE_TSC
static ft5406_rt_handle_t touchHandle;
#endif
static lv_disp_draw_buf_t disp_buf;
AT_NONCACHEABLE_SECTION_ALIGN(static uint8_t s_frameBuffer[2][LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL], 64);

static OsMutexHandle_t g_oRefreshLock = 0;

static lv_obj_t* gpLogo = 0;
/*******************************************************************************
 * Code
 ******************************************************************************/

static void lv_port_pre_init(void)
{
    g_oRefreshLock = os_mutex_create();
    ASSERT(g_oRefreshLock != 0);

    lv_disp_draw_buf_init(&disp_buf, s_frameBuffer[0], s_frameBuffer[1], LCD_WIDTH * LCD_HEIGHT);   /*Initialize the display buffer*/
}

static void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    Board_InitLcd();

    /*-----------------------------------
     * Register the display in LittlevGL
     *----------------------------------*/

    static lv_disp_drv_t disp_drv;      /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv); /*Basic initialization*/
    // extra initialization
    disp_drv.hor_res          = LCD_WIDTH;
    disp_drv.ver_res          = LCD_HEIGHT;
    disp_drv.full_refresh     = 1;

    /*Used in buffered mode (LV_VDB_SIZE != 0  in lv_conf.h)*/
    disp_drv.flush_cb = Board_FlushDisplay;



    /*Set a display buffer*/
    disp_drv.draw_buf = &disp_buf;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

void LCDIF_IRQHandler(void)
{
    uint32_t intStatus = ELCDIF_GetInterruptStatus(LCDIF);

    ELCDIF_ClearInterruptStatus(LCDIF, intStatus);

/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
  exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
    __DSB();
#endif
}

static void Board_InitLcdClock(void)
{
    extern const clock_video_pll_config_t videoPllConfig_BOARD_BootClockRUN;

    CLOCK_InitVideoPll(&videoPllConfig_BOARD_BootClockRUN);
}

static void Board_InitLcdBackLight(void)
{
    // controlled by peripheral processor
}

static void Board_InitLcd(void)
{
    /* Initialize the display. */
    const elcdif_rgb_mode_config_t config = {
        .panelWidth    = LCD_WIDTH,
        .panelHeight   = LCD_HEIGHT,
        .hsw           = LCD_HSW,
        .hfp           = LCD_HFP,
        .hbp           = LCD_HBP,
        .vsw           = LCD_VSW,
        .vfp           = LCD_VFP,
        .vbp           = LCD_VBP,
        .polarityFlags = LCD_POL_FLAGS,
        /* littlevgl starts render in frame buffer 0, so show frame buffer 1 first. */
        .bufferAddr  = (uint32_t)s_frameBuffer[1],
        .pixelFormat = kELCDIF_PixelFormatRGB565,
        .dataBus     = LCD_LCDIF_DATA_BUS,
    };

    /* Clear frame buffer. */
    memset((void *)s_frameBuffer, 0, sizeof(s_frameBuffer));

    /* Init board hardware. */
    /* Set the eLCDIF read_qos priority high, to make sure eLCDIF
     * can fetch data in time when PXP is used.
     */
    *((volatile uint32_t *)0x41044100) = 5;

    NVIC_SetPriority(LCDIF_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);

    Board_InitLcdClock();

    ELCDIF_RgbModeInit(LCDIF, &config);

    //ELCDIF_EnableInterrupts(LCDIF, kELCDIF_CurFrameDoneInterruptEnable);
    //NVIC_EnableIRQ(LCDIF_IRQn);
    ELCDIF_RgbModeStart(LCDIF);

    Board_InitLcdBackLight();
}

static void Board_FlushDisplay(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    if (!lv_disp_flush_is_last(disp_drv))
        return;

    ELCDIF_SetNextBufferAddr(LCDIF, (uint32_t)color_p);

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

#ifdef USE_TSC
/*Initialize your touchpad*/
static void Board_InitTouch(void)
{
    status_t status;

    lpi2c_master_config_t masterConfig = {0};

    /*Clock setting for LPI2C*/
    CLOCK_SetMux(kCLOCK_Lpi2cMux, TOUCH_LPI2C_CLOCK_SOURCE_SELECT);
    CLOCK_SetDiv(kCLOCK_Lpi2cDiv, TOUCH_LPI2C_CLOCK_SOURCE_DIVIDER);

    /*
     * masterConfig.debugEnable = false;
     * masterConfig.ignoreAck = false;
     * masterConfig.pinConfig = kLPI2C_2PinOpenDrain;
     * masterConfig.baudRate_Hz = 100000U;
     * masterConfig.busIdleTimeout_ns = 0;
     * masterConfig.pinLowTimeout_ns = 0;
     * masterConfig.sdaGlitchFilterWidth_ns = 0;
     * masterConfig.sclGlitchFilterWidth_ns = 0;
     */
    LPI2C_MasterGetDefaultConfig(&masterConfig);

    /* Change the default baudrate configuration */
    masterConfig.baudRate_Hz = TOUCH_I2C_BAUDRATE;

    /* Initialize the LPI2C master peripheral */
    LPI2C_MasterInit(TOUCH_I2C, &masterConfig, TOUCH_I2C_CLOCK_FREQ);

    /* Initialize touch panel controller */
    status = FT5406_RT_Init(&touchHandle, TOUCH_I2C);
    if (status != kStatus_Success)
    {
        PRINTF("Touch panel init failed\n");
    }
}

/* Will be called by the library to read the touchpad */
static bool Board_ReadTouch(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t *data)
{
    static int touch_x = 0;
    static int touch_y = 0;

    data->state = LV_INDEV_STATE_REL;

    touch_event_t touch_event;
    // swap x and y
    if (kStatus_Success == FT5406_RT_GetSingleTouch(&touchHandle, &touch_event, &touch_y, &touch_x))
    {
        if ((touch_event == kTouch_Down) || (touch_event == kTouch_Contact))
        {
            data->state = LV_INDEV_STATE_PR;
        }
    }

    /*Set the last pressed coordinates*/
    data->point.x = touch_x;
    data->point.y = touch_y;

    /*Return `false` because we are not buffering and no more data to read*/
    return false;
}
#else
OsQueueHandle_t g_LvglKeypadEventQueue;

bool lvgl_keypad_event(Key_Hw_Event_t event)
{
    return os_queue_send(g_LvglKeypadEventQueue, &event, 0);
}

void lvgl_keypad_cb(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t *data)
{
    Key_Hw_Event_t event;

    if (os_queue_receive(g_LvglKeypadEventQueue, &event, 0))
    {
        if (event.pressed)
        {
            switch (event.key)
            {
            case 0x0a:
                data->key = LV_KEY_ESC;
                break;
            case 0x0d:
                data->key = LV_KEY_ENTER;
                break;
            default:
                data->key = 0;
                break;
            }
            data->state = LV_INDEV_STATE_PR;
        }
        else
        {
            switch (event.key)
            {
            case 0x0a:
                data->key = LV_KEY_ESC;
                break;
            case 0x0d:
                data->key = LV_KEY_ENTER;
                break;
            default:
                data->key = 0;
                break;
            }
            data->state = LV_INDEV_STATE_REL;
        }
    }
}
#endif

lv_group_t * gInputKeypadGroup;
lv_indev_t * gInputKeypadDev;

OsQueueHandle_t g_LvglPollTaskQueue;

static void lv_port_indev_init(void)
{
    /*------------------
     * Touchpad
     * -----------------*/
#ifdef USE_TSC
    static lv_indev_drv_t indev_drv;
    Board_InitTouch();
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = Board_ReadTouch;
    lv_indev_drv_register(&indev_drv);
#else
    // Init and register FMT keyboard device
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_KEYPAD;
    indev_drv.read_cb = lvgl_keypad_cb;

    gInputKeypadDev = lv_indev_drv_register(&indev_drv);
    gInputKeypadGroup = lv_group_create();
    lv_indev_set_group(gInputKeypadDev, gInputKeypadGroup);

    g_LvglKeypadEventQueue = os_queue_create(2, sizeof(Key_Hw_Event_t));
    g_LvglPollTaskQueue = os_queue_create(1, sizeof(uint8_t));
#endif
}

static void lv_poll(uint16_t msDelay)
{
#if !defined (LV_TICK_CUSTOM) || (LV_TICK_CUSTOM == 0)
    lv_tick_inc(msDelay);
#endif
    lvgl_lock();
    lv_task_handler();
    lvgl_unlock();
}

static void lv_poll_task(void *pvParameters)
{
    uint8_t nEvt;

    while (true)
    {
        os_queue_receive(g_LvglPollTaskQueue, &nEvt, LV_DISP_GIF_REFR_PERIOD);
        lv_poll(LV_DISP_GIF_REFR_PERIOD);
    }
}

void lvgl_init(void)
{
    lv_port_pre_init();
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();
#ifndef BOOTLOADER
 //   lvgl_show_sf_logo(100, 193);
#endif
    os_task_create(lv_poll_task, 0, "lvgl", 4096, OS_TASK_GUI_PRIORITY);
}

// This LVGL lock/unlock mechanism wouldn't work without FreeRTOS environment
// It is FreeRTOS task context related.
void lvgl_lock(void)
{
    if (xGetTaskGUILockRefCount() > 0)
    {
        // Already locked, just increase the reference count
        xTaskGUILockRefInc();
        return;
    }

    // Otherwise, obtain the lock first
    if (!os_mutex_lock(g_oRefreshLock, 10000))
    {
        // Fatal issue. Seems deadlock happened. Restart unit
        ASSERT(0);
        return; // Shouldn't be here
    }

    // Now we own the lock, increase the reference count next
    xTaskGUILockRefInc();

    // It's safe to call any lvgl APIs
    return;
}

void lvgl_unlock(void)
{
    // Decrease the reference count first
    xTaskGUILockRefDec();

    if (xGetTaskGUILockRefCount() == 0)
    {
        // If no context referencing the lock, release it
        os_mutex_unlock(g_oRefreshLock);
    }
}

And here’re part of my lvgl_conf.h:

/* clang-format off */
#if 1 /*Set it to "1" to enable content*/

#ifndef LV_CONF_H
#define LV_CONF_H

#include <stdint.h>
#include "lvgl_support.h"

/*====================
   COLOR SETTINGS
 *====================*/

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16

/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0

/*Enable more complex drawing routines to manage screens transparency.
 *Can be used if the UI is above another layer, e.g. an OSD menu or video player.
 *Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/
#define LV_COLOR_SCREEN_TRANSP 0

/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
 * 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS (LV_COLOR_DEPTH == 32 ? 0: 128)

/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00)         /*pure green*/

/*=========================
   MEMORY SETTINGS
 *=========================*/

/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 1
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
#  define LV_MEM_SIZE (32U * 1024U)          /*[bytes]*/

/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#  define LV_MEM_ADR 0     /*0: unused*/
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
#if LV_MEM_ADR == 0
//#define LV_MEM_POOL_INCLUDE your_alloc_library  /* Uncomment if using an external allocator*/
//#define LV_MEM_POOL_ALLOC   your_alloc          /* Uncomment if using an external allocator*/
#endif

#else       /*LV_MEM_CUSTOM*/
#  define LV_MEM_CUSTOM_INCLUDE <stdlib.h>   /*Header for the dynamic memory function*/
#  define LV_MEM_CUSTOM_ALLOC   malloc
#  define LV_MEM_CUSTOM_FREE    free
#  define LV_MEM_CUSTOM_REALLOC realloc
#endif     /*LV_MEM_CUSTOM*/

/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
 *You will see an error log message if there wasn't enough buffers. */
#define LV_MEM_BUF_MAX_NUM 16  

/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 0

/*====================
   HAL SETTINGS
 *====================*/
/*GIF display refresh period. LVG will redraw changed ares with this period time*/
#define LV_DISP_GIF_REFR_PERIOD     20      /*[ms]*/

/*Default display refresh period. LVG will redraw changed ares with this period time*/
#define LV_DISP_DEF_REFR_PERIOD     (LV_DISP_GIF_REFR_PERIOD * 2)      /*[ms]*/   //2

/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30     /*[ms]*/

/*Use a custom tick source that tells the elapsed time in milliseconds.
 *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE  "platform_rtos.h"         /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (os_get_sys_tick_count())     /*Expression evaluating to current system time in ms*/
#endif   /*LV_TICK_CUSTOM*/

/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
 *(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130     /*[px/inch]*/

/*=======================
 * FEATURE CONFIGURATION
 *=======================*/

/*-------------
 * Drawing
 *-----------*/

/*Enable complex draw engine.
 *Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0

/*Allow buffering some shadow calculation.
 *LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
 *Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
#define LV_SHADOW_CACHE_SIZE 0

/* Set number of maximally cached circle data.
 * The circumference of 1/4 circle are saved for anti-aliasing
 * radius * 4 bytes are used per circle (the most often used radiuses are saved)
 * 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4

#endif /*LV_DRAW_COMPLEX*/

/*Default image cache size. Image caching keeps the images opened.
 *If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
 *With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
 *However the opened images might consume additional RAM.
 *0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 0

/*Maximum buffer size to allocate for rotation. Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)

/*-------------
 * GPU
 *-----------*/

/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
/*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f769xx.h" or "stm32f429xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif

/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP      1
#if LV_USE_GPU_NXP_PXP
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
 *   and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
 *   has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
 *0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
 */
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 1
#endif

/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0

/*Use exnternal renderer*/
#define LV_USE_EXTERNAL_RENDERER 0

/*Use SDL renderer API. Requires LV_USE_EXTERNAL_RENDERER*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#  define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
#endif

/*-------------
 * Logging
 *-----------*/

/*Enable the log module*/
#define LV_USE_LOG 0
#if LV_USE_LOG

/*How important log should be added:
 *LV_LOG_LEVEL_TRACE       A lot of logs to give detailed information
 *LV_LOG_LEVEL_INFO        Log important events
 *LV_LOG_LEVEL_WARN        Log if something unwanted happened but didn't cause a problem
 *LV_LOG_LEVEL_ERROR       Only critical issue, when the system may fail
 *LV_LOG_LEVEL_USER        Only logs added by the user
 *LV_LOG_LEVEL_NONE        Do not log anything*/
#  define LV_LOG_LEVEL LV_LOG_LEVEL_WARN

/*1: Print the log with 'printf';
 *0: User need to register a callback with `lv_log_register_print_cb()`*/
#  define LV_LOG_PRINTF 0

/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
#  define LV_LOG_TRACE_MEM        1
#  define LV_LOG_TRACE_TIMER      1
#  define LV_LOG_TRACE_INDEV      1
#  define LV_LOG_TRACE_DISP_REFR  1
#  define LV_LOG_TRACE_EVENT      1
#  define LV_LOG_TRACE_OBJ_CREATE 1
#  define LV_LOG_TRACE_LAYOUT     1
#  define LV_LOG_TRACE_ANIM       1

#endif  /*LV_USE_LOG*/

/*-------------
 * Asserts
 *-----------*/

/*Enable asserts if an operation is failed or an invalid data is found.
 *If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL          1   /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC        1   /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE         0   /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0   /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ           0   /*Check the object's type and existence (e.g. not deleted). (Slow)*/

/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1);   /*Halt by default*/

/*-------------
 * Others
 *-----------*/

/*1: Show CPU usage and FPS count in the right bottom corner*/
#define LV_USE_PERF_MONITOR 0
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif

/*1: Show the used memory and the memory fragmentation in the left bottom corner
 * Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_PERF_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif

/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0

/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
#  define LV_SPRINTF_INCLUDE <stdio.h>
#  define lv_snprintf  snprintf
#  define lv_vsnprintf vsnprintf
#else   /*LV_SPRINTF_CUSTOM*/
#  define LV_SPRINTF_USE_FLOAT 0
#endif  /*LV_SPRINTF_CUSTOM*/

#define LV_USE_USER_DATA 1

/*Garbage Collector settings
 *Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#  define LV_GC_INCLUDE "gc.h"                           /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/

/*=====================
 *  COMPILER SETTINGS
 *====================*/

/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0

/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC

/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER

/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY

/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1

/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
 * E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN

/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST

/*Complier prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY

/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM

/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA

/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
 *should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/

/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0

what kind of image you use - varibale, file (jpg, png) ? you can try to enable image caching - LV_IMG_CACHE_DEF_SIZE .

Hi, I converted png file to .c file using official Image Converter from LVGL website, you can check the data type as below

const lv_img_dsc_t lv_img_gradient_green = {
  .header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA,
  .header.always_zero = 0,
  .header.reserved = 0,
  .header.w = 337,
  .header.h = 337,
  .data_size = 113569 * LV_IMG_PX_SIZE_ALPHA_BYTE,
  .data = lv_img_gradient_green_map,
};