LittleVGL is a very nice GUI library I recently came across.

As part of a project I am currently working on I have managed to get LittleVGL working on a Teensy 3.6 and it works very well.

For anybody interested, I will attempt to describe my setup, the drivers I used and the LittleVGL configurations I have tried.

My hardware is an ILI9341 with XPT2046 touchscreen. I purchased it last year so it's listing is expired but an ebay search for "2.8'' TFT LCD Display Touch Panel SPI Serial Port Module ILI9341 SPI 5V/3.3V" should present similar units.

In my project I am using the Audio Shield, so the SPI is connected using the alternate SPI pins and is sharing with an 8MB serial flash on the audio shield. The screen is using ILI9341_t3n and XPT2046_Touchscreen Teensy libraries for the back end. LittleVGL can also access the drives, so I want the SerialFlash and SD libraries as well. The start of my code includes these.

//Teensy-specific display library
#include <lvgl.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Encoder.h>
#include <SerialFlash.h>
#include <ILI9341_t3n.h>      //Modified to include drawImageAsync
#include <XPT2046_Touchscreen.h>
LittleVGL can be obtained through Arduino's Library Manager. I think XPT2046_Touchscreen is included with Teensyduino. There is a hacked version of the ILI9341_t3n library that includes a new function to give LittleVGL DMA to the screen for a substantial performance boost at the cost of some SPI bus sharing synchronisation trickery. This hack is optional, and is only set up for Teensy 3.6 right now, download here

Touch and screen are both sharing the SPI bus. Pins are set up as follows in code, in actual fact I disconnected TFT_MISO to prevent it interfering with touch. As you can see my backlight circuit is active high. There is also an encoder knob.

//TFT Pin assignments
#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST    255  // 255 = unused, connect to 3.3V
#define TFT_MOSI     7
#define TFT_MISO    12
#define TFT_SCLK    14
#define BACKLIGHT    2
#define BACKLIGHT_ON  digitalWrite(BACKLIGHT, HIGH)
#define BACKLIGHT_OFF digitalWrite(BACKLIGHT, LOW)

//Touch Screen Pin assignments
#define TOUCH_IRQ    5
#define TOUCH_CS     10

//Encoder Pin assignments
#define ENC_1        28
#define ENC_2        35
#define PUSH          36
Bringing in LittleVGL is pretty easy. Good instructions are given in the docs,

For the system tick requirement I am using an IntervalTimer. This I instantiate as globals along with the display and touch, here is what the setup() looks like.

XPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ);  // Param 2 - Touch IRQ Pin - interrupt enabled polling

// Create an IntervalTimer object 
IntervalTimer guiTimer;

Encoder knob(ENC_1, ENC_2);

void setup() {
  //Encoder push input

  //Start USB Serial
  //Wait a bit for power to settle

  //Start TFT display. Use this initializer if you're using a 2.8" TFT

  //Touchscreen init

  //File storage init
  if (!SD.begin(SDCARD_CS)) {
    Serial.println("Unable to access!");
  else Serial.println("SD Card OK.");
  if (!SerialFlash.begin(FLASH_CS)) {
    Serial.println("Unable to access!");
  } else {
    uint8_t buf[5];
    log("Size ");


Those routines at the bottom of setup set up the LittleVGL drivers and display the initial window and I have them in different files. I will show them below, after loop() as they are a little bit big. So loop() is pretty simple..

void loop() {
loop() can do other non-blocking stuff. lv_task_handler() blocks when LVGL is busy, so loop() can slow down quite a bit. Good for general housekeeping functions. More time dependent operations are probably better served from LittleVGL's own timers.

Now for LittleVGL's drivers.

Because I am using DMA, I globally declare the screen buffers as such. I don't think it would hurt even if one was not to use DMA.

//Put LittleVGL's Virtual Display Buffers into DMAMEM
DMAMEM uint8_t screen1[LV_VDB_SIZE_IN_BYTES];
DMAMEM uint8_t screen2[LV_VDB_SIZE_IN_BYTES];

void guiInit(void)
    lv_vdb_set_adr(screen1, screen2);

    //Initialize the display
    lv_disp_drv_t disp_drv;
    disp_drv.disp_flush = disp_flush;
    //tft.setHandleDmaComplete(lv_flush_ready);  //For DMA 

    //Initialize the touch pad
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);  //Basic initialization
    indev_drv.type = LV_INDEV_TYPE_POINTER; = touch_read;

    //Initialize the encoder
    lv_indev_drv_init(&indev_drv);  //Basic initialization
    indev_drv.type = LV_INDEV_TYPE_ENCODER; = enc_read;
    enc_indev = lv_indev_drv_register(&indev_drv);
    group = lv_group_create();
    lv_indev_set_group(enc_indev, group);

    //Initialize the graphics library's tick
    //In a Timer call 'lv_tick_inc(1)' in every milliseconds
    //Or no other option call it loop
    guiTimer.begin(guiInc, 5000);  // run every 0.005 seconds


void guiInc() {

void disp_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t *color_p) {
  //Either these two lines for conventional drawing
  //Or this line for DMA. Dont forget to enable the flush callback above.
  tft.drawImageAsync(x1, y1, x2, y2, (uint16_t*)color_p);

bool touch_read(lv_indev_data_t *data) {
  tft.waitUpdateAsyncComplete();            //On the same SPI bus as the TFT display
  TS_Point p = ts.getPoint();
  // Scale from ~0->4000 to tft.width using the calibration #'s
  data->point.x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width());
  data->point.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());
  data->state = ts.touched()? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
  return false; /*No buffering so no more data read*/

#define ENC_RESET_TIME 50
unsigned long encTimer = 0;
bool enc_read(lv_indev_data_t *data) {
  int rel =;
  if (rel != 0) encTimer = millis();
  rel /= 3;
  if (rel != 0) {
    data->enc_diff = rel;
  if (encTimer && millis() > encTimer + ENC_RESET_TIME) {
    encTimer = 0;
  if(!digitalRead(PUSH)) data->state = LV_INDEV_STATE_PR;
  else data->state = LV_INDEV_STATE_REL;
  return false; /*No buffering so no more data read*/
The following drivers give LittleVGL filesystem access allowing images to be easily retrieved from storage by referring to them like lv_img_set_src(img_bin, "C:/flower.bin");

void fileSystemInit() {
  // Add a simple drive to open images from SD Card
  // SD card is D:/ (removable storage)
  lv_fs_drv_t sd_drv;                         //A driver descriptor
  memset(&sd_drv, 0, sizeof(lv_fs_drv_t));    //Initialization

  sd_drv.file_size = sizeof(File);       //Set up fields...
  sd_drv.letter = 'D'; = sd_open;
  sd_drv.close = sd_close; = sd_read; = sd_seek;
  sd_drv.tell = sd_tell;

  // And another simple drive to open images from Serial Flash
  // Serial Flash is C:/ (internal storage)
  lv_fs_drv_t sf_drv;                         //A driver descriptor
  memset(&sf_drv, 0, sizeof(lv_fs_drv_t));    //Initialization

  sf_drv.file_size = sizeof(SerialFlashFile);       //Set up fields...
  sf_drv.letter = 'C'; = sf_open;
  sf_drv.close = sf_close; = sf_read; = sf_seek;
  sf_drv.tell = sf_tell;

// SD access

 * Open a file from the PC
 * @param file_p pointer to a FILE* variable
 * @param fn name of the file.
 * @param mode element of 'fs_mode_t' enum or its 'OR' connection (e.g. FS_MODE_WR | FS_MODE_RD)
 * @return LV_FS_RES_OK: no error, the file is opened
 *         any error from lv_fs_res_t enum
static lv_fs_res_t sd_open(void * file_p, const char * fn, lv_fs_mode_t mode)
    uint8_t flags = 0;

    if(mode == LV_FS_MODE_WR) flags = O_READ | O_WRITE | O_CREAT;
    else if(mode == LV_FS_MODE_RD) flags = O_READ;
    else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = O_READ | O_WRITE | O_CREAT;

    /*Make the path relative to the current directory (the projects root folder)*/
    char buf[256];
    sprintf(buf, "/%s", fn);
    File f =, flags);
    if(!f) return LV_FS_RES_UNKNOWN;
    else {;
        /* 'file_p' is pointer to a file descriptor and
         * we need to store our file descriptor here*/
        File * fp = (File*)file_p;        /*Just avoid the confusing casings*/
        *fp = f;

    return LV_FS_RES_OK;

 * Close an opened file
 * @param file_p pointer to a FILE* variable. (opened with lv_ufs_open)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
static lv_fs_res_t sd_close(void * file_p)
    File * fp = (File*)file_p;        /*Just avoid the confusing casings*/
    return LV_FS_RES_OK;

 * Read data from an opened file
 * @param file_p pointer to a FILE variable.
 * @param buf pointer to a memory block where to store the read data
 * @param btr number of Bytes To Read
 * @param br the real number of read bytes (Byte Read)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
static lv_fs_res_t sd_read(void * file_p, void * buf, uint32_t btr, uint32_t * br)
    File * fp = (File*)file_p;        /*Just avoid the confusing casings*/
    *br = fp->read(buf, btr);
    return LV_FS_RES_OK;

 * Set the read write pointer. Also expand the file size if necessary.
 * @param file_p pointer to a FILE* variable. (opened with lv_ufs_open )
 * @param pos the new position of read write pointer
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
static lv_fs_res_t sd_seek(void * file_p, uint32_t pos)
    File * fp = (File*)file_p;        /*Just avoid the confusing casings*/
    return LV_FS_RES_OK;

 * Give the position of the read write pointer
 * @param file_p pointer to a FILE* variable.
 * @param pos_p pointer to to store the result
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
static lv_fs_res_t sd_tell(void * file_p, uint32_t * pos_p)
    File * fp = (File*)file_p;        /*Just avoid the confusing casings*/
    *pos_p = fp->position();
    return LV_FS_RES_OK;

// Serial Flash access

static lv_fs_res_t sf_open(void * file_p, const char * fn, lv_fs_mode_t mode)
    SerialFlashFile sf =;
    if(!sf) return LV_FS_RES_UNKNOWN;
    else {;
        memcpy(file_p, &sf, sizeof(SerialFlashFile));
    return LV_FS_RES_OK;

static lv_fs_res_t sf_close(void * file_p)
    SerialFlashFile * fp = (SerialFlashFile*)file_p;        /*Just avoid the confusing casings*/
    fp->close();    //Kinda does nothing
    return LV_FS_RES_OK;

static lv_fs_res_t sf_read(void * file_p, void * buf, uint32_t btr, uint32_t * br)
    SerialFlashFile * fp = (SerialFlashFile*)file_p;        /*Just avoid the confusing casings*/
    *br = fp->read(buf, btr);
    return LV_FS_RES_OK;

static lv_fs_res_t sf_seek(void * file_p, uint32_t pos)
    SerialFlashFile * fp = (SerialFlashFile*)file_p;        /*Just avoid the confusing casings*/
    return LV_FS_RES_OK;

static lv_fs_res_t sf_tell(void * file_p, uint32_t * pos_p)
    SerialFlashFile * fp = (SerialFlashFile*)file_p;        /*Just avoid the confusing casings*/
    *pos_p = fp->position();
    return LV_FS_RES_OK;
After the drivers are active a call to lv_task_handler() will update the GUI. If setup() has to wait for other peripherals the GUI can be kept responsive by putting lv_task_handler() in the wait loops.

Now we can draw a GUI. There are so many good examples on the LittleVGL website I guess something simple will do just to show it in action. The code I have is dependant on other parts of my specific project so cut and paste wont do. Perhaps stripped back, something like this:

#define CPU_LABEL_COLOR     "FF0000"
#define DSP_LABEL_COLOR     "00FF00"
#define MEM_LABEL_COLOR     "0000FF"
//#define LTM_LABEL_COLOR     "007F7F"
#define CHART_POINT_NUM     100
#define REFR_TIME    500
static lv_obj_t * cpuChart;
static lv_chart_series_t * cpu_ser;
static lv_chart_series_t * mem_ser;
static lv_chart_series_t * dsp_ser;
//static lv_chart_series_t * ltm_ser;
static lv_obj_t * info_label;
static lv_task_t * refr_task;

void mainScreenCreate(void)

   //Initialize the alien theme
   //* 210: a green HUE value
   //* NULL: use the default font (LV_FONT_DEFAULT)
    lv_theme_t * th = lv_theme_night_init(210, NULL);
    /*Set the surent system theme*/

    lv_obj_t * tv = lv_tabview_create(lv_scr_act(), NULL);
    lv_obj_set_parent(wp, ((lv_tabview_ext_t *) tv->ext_attr)->content);
    lv_obj_set_pos(wp, 0, -5);

    lv_obj_t * tab1 = lv_tabview_add_tab(tv, "Synth");
    lv_obj_t * tab2 = lv_tabview_add_tab(tv, "Patch");
    lv_obj_t * tab3 = lv_tabview_add_tab(tv, "Output");
    lv_obj_t * tab4 = lv_tabview_add_tab(tv, "Debug");

    //log_create(tab2, 320, 240);

void debugScreenCreate(lv_obj_t *parent) {
    refr_task = lv_task_create(sysmon_task, REFR_TIME, LV_TASK_PRIO_LOW, NULL);

    /*Create a chart with two data lines*/
    cpuChart = lv_chart_create(parent, NULL);
    lv_obj_set_size(cpuChart, 260, LV_VER_RES / 2);
    lv_obj_set_pos(cpuChart, 20, LV_DPI / 10);
    lv_chart_set_point_count(cpuChart, CHART_POINT_NUM);
    lv_chart_set_range(cpuChart, 0, 100);
    lv_chart_set_type(cpuChart, LV_CHART_TYPE_LINE);
    lv_chart_set_series_width(cpuChart, 4);
    cpu_ser =  lv_chart_add_series(cpuChart, LV_COLOR_RED);
    mem_ser =  lv_chart_add_series(cpuChart, LV_COLOR_BLUE);
    dsp_ser =  lv_chart_add_series(cpuChart, LV_COLOR_GREEN);
    //ltm_ser =  lv_chart_add_series(cpuChart, LV_COLOR_YELLOW);

    /*Set the data series to zero*/
    uint16_t i;
    for(i = 0; i < CHART_POINT_NUM; i++) {
        lv_chart_set_next(cpuChart, cpu_ser, 0);
        lv_chart_set_next(cpuChart, mem_ser, 0);
        lv_chart_set_next(cpuChart, dsp_ser, 0);
        //lv_chart_set_next(cpuChart, ltm_ser, 0);

    /*Create a label for the details of Memory and CPU usage*/
    info_label = lv_label_create(parent, NULL);
    lv_label_set_recolor(info_label, true);
    //lv_label_set_text(info_label, "Object usage demo");  /*Set the text*/
    lv_obj_align(info_label, cpuChart, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 20);

    /*Refresh the chart and label manually at first*/

static void sysmon_task(void * param)
    (void) param;    /*Unused*/

    /*Get CPU and memory information */
    uint8_t cpu_busy;
    cpu_busy = 100 - lv_task_get_idle();

    uint8_t mem_used_pct = 0;
#if  LV_MEM_CUSTOM == 0
    lv_mem_monitor_t mem_mon;
    mem_used_pct = mem_mon.used_pct;
    //uint32_t totram = ram.adj_unallocd() + ram.heap_total();
    //uint32_t freeram = ram.adj_free();
    mem_used_pct = 0; //((float)(totram - freeram) / totram) * 100;

    /*Add the CPU and memory data to the chart*/
    lv_chart_set_next(cpuChart, cpu_ser, cpu_busy);
    lv_chart_set_next(cpuChart, mem_ser, mem_used_pct);
    lv_chart_set_next(cpuChart, dsp_ser, AudioProcessorUsage());
    //lv_chart_set_next(cpuChart, ltm_ser, loopTime);

    /*Refresh the and windows*/
    char buf_long[512];
    sprintf(buf_long, "%s%s CPU: %d %%%s\n",

    sprintf(buf_long, "%s%s%s DSP: %d %%%s\n",

#if LV_MEM_CUSTOM == 0
    sprintf(buf_long, "%s"LV_TXT_COLOR_CMD"%s MEMORY: %d %%"LV_TXT_COLOR_CMD"\n"
            "Total: %d bytes\n"
            "Used: %d bytes\n"
            "Free: %d bytes\n"
            "Frag: %d %%",

            (int)(mem_mon.total_size - mem_mon.free_size), (int)(mem_mon.free_size), (int)(mem_mon.frag_pct));

    sprintf(buf_long, "%s"LV_TXT_COLOR_CMD"%s MEMORY: %d %%"LV_TXT_COLOR_CMD"\n"
            "Total: %d bytes\n"
            "Used: %d bytes\n"
            "Free: %d bytes\n",
            0, //(int)totram,
            0); //(int)(totram - freeram), (int)freeram);
    sprintf(buf_long, "%sDSP Blocks: %d\n"
            "DSP Maximum: %d",

    lv_label_set_text(info_label, buf_long);

Finally, to configure LittlevGL the file lv_conf.h is kept in Arduno/libraries/LittlevGL. Other than setting screen size and color depth, I have mainly just changed the default for custom (standard malloc/free) memory and double virtual display buffers. Here it is in entirety:

 * @file lv_conf.h

#if 1 /*Set it to "1" to enable content*/

#ifndef LV_CONF_H
#define LV_CONF_H
   Dynamic memory

/* Memory size which will be used by the library
 * to store the graphical objects and other data */
#define LV_MEM_CUSTOM      1                /*1: use custom malloc/free, 0: use the built-in lv_mem_alloc/lv_mem_free*/
#if LV_MEM_CUSTOM == 0
#  define LV_MEM_SIZE    (64U * 1024U)        /*Size memory used by `lv_mem_alloc` in bytes (>= 2kB)*/
#  define LV_MEM_ATTR                         /*Complier prefix for big array declaration*/
#  define LV_MEM_ADR          0               /*Set an address for memory pool instead of allocation it as an array. Can be in external SRAM too.*/
#  define LV_MEM_AUTO_DEFRAG  1               /*Automatically defrag on free*/
#else       /*LV_MEM_CUSTOM*/
#  define LV_MEM_CUSTOM_INCLUDE <stdlib.h>   /*Header for the dynamic memory function*/
#  define LV_MEM_CUSTOM_ALLOC   malloc       /*Wrapper to malloc*/
#  define LV_MEM_CUSTOM_FREE    free         /*Wrapper to free*/
#endif     /*LV_MEM_CUSTOM*/

/* Garbage Collector settings
 * Used if lvgl is binded to higher language and the memory is managed by that language */
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#  define LV_MEM_CUSTOM_REALLOC   your_realloc           /*Wrapper to realloc*/
#  define LV_MEM_CUSTOM_GET_SIZE  your_mem_get_size      /*Wrapper to lv_mem_get_size*/
#  define LV_GC_INCLUDE "gc.h"                           /*Include Garbage Collector related things*/
#endif /* LV_ENABLE_GC */

   Graphical settings

/* Horizontal and vertical resolution of the library.*/
#define LV_HOR_RES          (320)
#define LV_VER_RES          (240)

/* Dot Per Inch: used to initialize default sizes. E.g. a button with width = LV_DPI / 2 -> half inch wide
 * (Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI              100

/* Enable anti-aliasing (lines, and radiuses will be smoothed) */
#define LV_ANTIALIAS        1       /*1: Enable anti-aliasing*/

/*Screen refresh period in milliseconds*/
#define LV_REFR_PERIOD      30

 *  VDB settings

/* VDB (Virtual Display Buffer) is an internal graphics buffer.
 * The GUI will be drawn into this buffer first and then
 * the buffer will be passed to your `disp_drv.disp_flush` function to
 * copy it to your frame buffer.
 * VDB is required for: buffered drawing, opacity, anti-aliasing and shadows
 * Learn more:*/

/* Size of the VDB in pixels. Typical size: ~1/10 screen. Must be >= LV_HOR_RES
 * Setting it to 0 will disable VDB and `disp_drv.disp_fill` and `disp_drv.disp_map` functions
 * will be called to draw to the frame buffer directly*/
#define LV_VDB_SIZE         ((LV_VER_RES * LV_HOR_RES) / 10)

 /* Bit-per-pixel of VDB. Useful for monochrome or non-standard color format displays.
  * Special formats are handled with `disp_drv.vdb_wr`)*/
#define LV_VDB_PX_BPP       LV_COLOR_SIZE       /*LV_COLOR_SIZE comes from LV_COLOR_DEPTH below to set 8, 16 or 32 bit pixel size automatically */

 /* Place VDB to a specific address (e.g. in external RAM)
  * 0: allocate automatically into RAM
  * LV_VDB_ADR_INV: to replace it later with `lv_vdb_set_adr()`*/
#define LV_VDB_ADR          LV_VDB_ADR_INV

/* Use two Virtual Display buffers (VDB) to parallelize rendering and flushing
 * The flushing should use DMA to write the frame buffer in the background */
#define LV_VDB_DOUBLE       1

/* Place VDB2 to a specific address (e.g. in external RAM)
 * 0: allocate automatically into RAM
 * LV_VDB_ADR_INV: to replace it later with `lv_vdb_set_adr()`*/
#define LV_VDB2_ADR         LV_VDB_ADR_INV

/* Using true double buffering in `disp_drv.disp_flush` you will always get the image of the whole screen.
 * Your only task is to set the rendered image (`color_p` parameter) as frame buffer address or send it to your display.
 * The best if you do in the blank period of you display to avoid tearing effect.
 * Requires:
 * - LV_VDB_DOUBLE = 1

   Misc. setting

/*Input device settings*/
#define LV_INDEV_READ_PERIOD            50                     /*Input device read period in milliseconds*/
#define LV_INDEV_POINT_MARKER           0                      /*Mark the pressed points  (required: USE_LV_REAL_DRAW = 1)*/
#define LV_INDEV_DRAG_LIMIT             10                     /*Drag threshold in pixels */
#define LV_INDEV_DRAG_THROW             20                     /*Drag throw slow-down in [%]. Greater value means faster slow-down */
#define LV_INDEV_LONG_PRESS_TIME        400                    /*Long press time in milliseconds*/
#define LV_INDEV_LONG_PRESS_REP_TIME    100                    /*Repeated trigger period in long press [ms] */

/*Color settings*/
#define LV_COLOR_DEPTH     16                     /*Color depth: 1/8/16/32*/
#define LV_COLOR_16_SWAP   0                      /*Swap the 2 bytes of RGB565 color. Useful if the display has a 8 bit interface (e.g. SPI)*/
#define LV_COLOR_SCREEN_TRANSP        0           /*1: Enable screen transparency. Useful for OSD or other overlapping GUIs. Requires ARGB8888 colors*/
#define LV_COLOR_TRANSP    LV_COLOR_LIME          /*Images pixels with this color will not be drawn (with chroma keying)*/

/*Text settings*/
#define LV_TXT_UTF8             1                /*Enable UTF-8 coded Unicode character usage */
#define LV_TXT_BREAK_CHARS     " ,.;:-_"         /*Can break texts on these chars*/
#define LV_TXT_LINE_BREAK_LONG_LEN 12 /* If a character is at least this long, will break wherever "prettiest" */
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 /* Minimum number of characters of a word to put on a line before a break */
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 1 /* Minimum number of characters of a word to put on a line after a break */

/*Feature usage*/
#define USE_LV_ANIMATION        1               /*1: Enable all animations*/
#define USE_LV_SHADOW           1               /*1: Enable shadows*/
#define USE_LV_GROUP            1               /*1: Enable object groups (for keyboards)*/
#define USE_LV_GPU              0               /*1: Enable GPU interface*/
#define USE_LV_REAL_DRAW        0               /*1: Enable function which draw directly to the frame buffer instead of VDB (required if LV_VDB_SIZE = 0)*/
#define USE_LV_FILESYSTEM       1               /*1: Enable file system (might be required for images*/
#define USE_LV_MULTI_LANG       0               /* Number of languages for labels to store (0: to disable this feature)*/

/*Compiler settings*/
#define LV_ATTRIBUTE_TICK_INC                   /* Define a custom attribute to `lv_tick_inc` function */
#define LV_ATTRIBUTE_TASK_HANDLER               /* Define a custom attribute to `lv_task_handler` function */
#define LV_COMPILER_VLA_SUPPORTED            1  /* 1: Variable length array is supported*/
#define LV_COMPILER_NON_CONST_INIT_SUPPORTED 1  /* 1: Initialization with non constant values are supported */

/*HAL settings*/
#define LV_TICK_CUSTOM     0                        /*1: use a custom tick source (removing the need to manually update the tick with `lv_tick_inc`) */
#define LV_TICK_CUSTOM_INCLUDE  "sonething.h"         /*Header for the sys time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())     /*Expression evaluating to current systime in ms*/
#endif     /*LV_TICK_CUSTOM*/

/*Log settings*/
#define USE_LV_LOG      0   /*Enable/disable the log module*/
/* 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 caused problem
 * LV_LOG_LEVEL_ERROR       Only critical issue, when the system may fail
/* 1: Print the log with 'printf'; 0: user need to register a callback*/

#  define LV_LOG_PRINTF   0
#endif  /*USE_LV_LOG*/

#define LV_THEME_LIVE_UPDATE    0       /*1: Allow theme switching at run time. Uses 8..10 kB of RAM*/

#define USE_LV_THEME_TEMPL      0       /*Just for test*/
#define USE_LV_THEME_DEFAULT    0       /*Built mainly from the built-in styles. Consumes very few RAM*/
#define USE_LV_THEME_ALIEN      0       /*Dark futuristic theme*/
#define USE_LV_THEME_NIGHT      1       /*Dark elegant theme*/
#define USE_LV_THEME_MONO       0       /*Mono color theme for monochrome displays*/
#define USE_LV_THEME_MATERIAL   0       /*Flat theme with bold colors and light shadows*/
#define USE_LV_THEME_ZEN        0       /*Peaceful, mainly light theme */
#define USE_LV_THEME_NEMO       0       /*Water-like theme based on the movie "Finding Nemo"*/


/* More info about fonts:
 * To enable a built-in font use 1,2,4 or 8 values
 * which will determine the bit-per-pixel. Higher value means smoother fonts */
#define USE_LV_FONT_DEJAVU_10              4
#define USE_LV_FONT_SYMBOL_10              4

#define USE_LV_FONT_DEJAVU_20              4
#define USE_LV_FONT_SYMBOL_20              4

#define USE_LV_FONT_DEJAVU_30              4
#define USE_LV_FONT_SYMBOL_30              4

#define USE_LV_FONT_DEJAVU_40              4
#define USE_LV_FONT_SYMBOL_40              4

#define USE_LV_FONT_MONOSPACE_8            1

/* Optionally declare your custom fonts here.
 * You can use these fonts as default font too
 * and they will be available globally. E.g.
 *                                LV_FONT_DECLARE(my_font_2) \

#define LV_FONT_DEFAULT        &lv_font_dejavu_20     /*Always set a default font from the built-in fonts*/

#define LV_OBJ_FREE_NUM_TYPE    uint32_t    /*Type of free number attribute (comment out disable free number)*/
#define LV_OBJ_FREE_PTR         1           /*Enable the free pointer attribute*/
#define LV_OBJ_REALIGN          1           /*Enable `lv_obj_realaign()` based on `lv_obj_align()` parameters*/

 * Documentation of the object types:

 * Simple object

/*Label (dependencies: -*/
#define USE_LV_LABEL    1
#if USE_LV_LABEL != 0
#  define LV_LABEL_SCROLL_SPEED       25     /*Hor, or ver. scroll speed [px/sec] in 'LV_LABEL_LONG_SCROLL/ROLL' mode*/

/*Image (dependencies: lv_label*/
#define USE_LV_IMG      1
#if USE_LV_IMG != 0
#  define LV_IMG_CF_INDEXED   1       /*Enable indexed (palette) images*/
#  define LV_IMG_CF_ALPHA     1       /*Enable alpha indexed images*/

/*Line (dependencies: -*/
#define USE_LV_LINE     1

/*Arc (dependencies: -)*/
#define USE_LV_ARC      1

 * Container objects

/*Container (dependencies: -*/
#define USE_LV_CONT     1

/*Page (dependencies: lv_cont)*/
#define USE_LV_PAGE     1

/*Window (dependencies: lv_cont, lv_btn, lv_label, lv_img, lv_page)*/
#define USE_LV_WIN      1

/*Tab (dependencies: lv_page, lv_btnm)*/
#define USE_LV_TABVIEW      1
#  if USE_LV_TABVIEW != 0
#  define LV_TABVIEW_ANIM_TIME    300     /*Time of slide animation [ms] (0: no animation)*/

/*Tileview (dependencies: lv_page) */
#define USE_LV_TILEVIEW     1
#  define LV_TILEVIEW_ANIM_TIME   300     /*Time of slide animation [ms] (0: no animation)*/

 * Data visualizer objects

/*Bar (dependencies: -)*/
#define USE_LV_BAR      1

/*Line meter (dependencies: *;)*/
#define USE_LV_LMETER   1

/*Gauge (dependencies:lv_bar, lv_lmeter)*/
#define USE_LV_GAUGE    1

/*Chart (dependencies: -)*/
#define USE_LV_CHART    1

/*Table (dependencies: lv_label)*/
#define USE_LV_TABLE    1
#  define LV_TABLE_COL_MAX    12

/*LED (dependencies: -)*/
#define USE_LV_LED      1

/*Message box (dependencies: lv_rect, lv_btnm, lv_label)*/
#define USE_LV_MBOX     1

/*Text area (dependencies: lv_label, lv_page)*/
#define USE_LV_TA       1
#if USE_LV_TA != 0
#  define LV_TA_CURSOR_BLINK_TIME 400     /*ms*/
#  define LV_TA_PWD_SHOW_TIME     1500    /*ms*/

/*Spinbox (dependencies: lv_ta)*/
#define USE_LV_SPINBOX       1

/*Calendar (dependencies: -)*/

/*Preload (dependencies: lv_arc)*/
#define USE_LV_PRELOAD      1
#  define LV_PRELOAD_DEF_ARC_LENGTH   60      /*[deg]*/
#  define LV_PRELOAD_DEF_SPIN_TIME    1000    /*[ms]*/

/*Canvas (dependencies: lv_img)*/
#define USE_LV_CANVAS       1
 * User input objects

/*Button (dependencies: lv_cont*/
#define USE_LV_BTN      1
#if USE_LV_BTN != 0
#  define LV_BTN_INK_EFFECT   1       /*Enable button-state animations - draw a circle on click (dependencies: USE_LV_ANIMATION)*/

/*Image Button (dependencies: lv_btn*/
#define USE_LV_IMGBTN   1
#  define LV_IMGBTN_TILED 0           /*1: The imgbtn requires left, mid and right parts and the width can be set freely*/

/*Button matrix (dependencies: -)*/
#define USE_LV_BTNM     1

/*Keyboard (dependencies: lv_btnm)*/
#define USE_LV_KB       1

/*Check box (dependencies: lv_btn, lv_label)*/
#define USE_LV_CB       1

/*List (dependencies: lv_page, lv_btn, lv_label, (lv_img optionally for icons ))*/
#define USE_LV_LIST     1
#if USE_LV_LIST != 0
#  define LV_LIST_FOCUS_TIME  100 /*Default animation time of focusing to a list element [ms] (0: no animation)  */

/*Drop down list (dependencies: lv_page, lv_label, lv_symbol_def.h)*/
#define USE_LV_DDLIST    1
#if USE_LV_DDLIST != 0
#  define LV_DDLIST_ANIM_TIME     200     /*Open and close default animation time [ms] (0: no animation)*/

/*Roller (dependencies: lv_ddlist)*/
#define USE_LV_ROLLER    1
#if USE_LV_ROLLER != 0
#  define LV_ROLLER_ANIM_TIME     200     /*Focus animation time [ms] (0: no animation)*/

/*Slider (dependencies: lv_bar)*/
#define USE_LV_SLIDER    1

/*Switch (dependencies: lv_slider)*/
#define USE_LV_SW       1

 * Non-user section
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)    /* Disable warnings for Visual Studio*/

/*--END OF LV_CONF_H--*/

/*Be sure every define has a default value*/
#include "lv_conf_checker.h"

#endif /*LV_CONF_H*/

#endif /*End of "Content enable"*/
Letting LittlevGL handle it's own memory is probably a better idea long-term as it can defragment it's preallocated block.

This has been a very long post, it has taken me quite a long time to write. Forgive me, I must leave it here, full of errors and omissions which I can perhaps correct later.
I invite further discussion and I hope this thread helps somebody else give LittleVGL a go. Good luck!