LittleVGL on Teensy

prickle

Active member
LittleVGL https://littlevgl.com/ 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.

Code:
//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 https://github.com/prickle/ILI9341_t3n

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.

Code:
//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, https://docs.littlevgl.com/#Porting

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.

Code:
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
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
  pinMode(PUSH, INPUT_PULLUP);

  //Start USB Serial
  Serial.begin(115200);
 
  //Wait a bit for power to settle
  delay(100);

  //Start TFT display. Use this initializer if you're using a 2.8" TFT
  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(ILI9341_BLACK);
  BACKLIGHT_ON;

  //Touchscreen init
  ts.begin();
  ts.setRotation(1);

  //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 ");
    SerialFlash.readID(buf);
    Serial.print(SerialFlash.capacity(buf)/1024);
    Serial.println("Kb.");
  }

  guiInit();
  fileSystemInit();
  mainScreenCreate();

}

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..

Code:
void loop() {
  lv_task_handler();
  ..
}

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.

Code:
//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);
    lv_init();

    //Initialize the display
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.disp_flush = disp_flush;
    lv_disp_drv_register(&disp_drv);
    //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;
    indev_drv.read = touch_read;
    lv_indev_drv_register(&indev_drv);

    //Initialize the encoder
    lv_indev_drv_init(&indev_drv);  //Basic initialization
    indev_drv.type = LV_INDEV_TYPE_ENCODER;
    indev_drv.read = 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() {
  lv_tick_inc(5);  
}

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
  //tft.writeRect(x1,y1,x2-x1+1,y2-y1+1,(uint16_t*)color_p);
  //lv_flush_ready();
  //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 = knob.read();
  if (rel != 0) encTimer = millis();
  rel /= 3;
  if (rel != 0) {
    data->enc_diff = rel;
    knob.write(0);
  }
  if (encTimer && millis() > encTimer + ENC_RESET_TIME) {
    knob.write(0);
    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");

Code:
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_drv.open = sd_open;
  sd_drv.close = sd_close;
  sd_drv.read = sd_read;
  sd_drv.seek = sd_seek;
  sd_drv.tell = sd_tell;
  lv_fs_add_drv(&sd_drv);

  // 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_drv.open = sf_open;
  sf_drv.close = sf_close;
  sf_drv.read = sf_read;
  sf_drv.seek = sf_seek;
  sf_drv.tell = sf_tell;
  lv_fs_add_drv(&sf_drv);
}

//----------------------------------------------------
// 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 = SD.open(buf, flags);
    if(!f) return LV_FS_RES_UNKNOWN;
    else {
        f.seek(0);
        /* '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*/
    fp->close();
    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*/
    fp->seek(pos);
    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)
{
    tft.waitUpdateAsyncComplete();
    SerialFlashFile sf = SerialFlash.open(fn);
    if(!sf) return LV_FS_RES_UNKNOWN;
    else {
        sf.seek(0);
        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*/
    tft.waitUpdateAsyncComplete();
    *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*/
    fp->seek(pos);
    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:

Code:
#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_theme_set_current(th);

    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");

    //synthScreenCreate(tab1);
    //log_create(tab2, 320, 240);
    //ampScreenCreate(tab3);
    debugScreenCreate(tab4);
}

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*/
    sysmon_task(NULL);
}

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;
    lv_mem_monitor(&mem_mon);
    mem_used_pct = mem_mon.used_pct;
#else
    //ram.run();
    //uint32_t totram = ram.adj_unallocd() + ram.heap_total();
    //uint32_t freeram = ram.adj_free();
    mem_used_pct = 0; //((float)(totram - freeram) / totram) * 100;
#endif

    /*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",
            LV_TXT_COLOR_CMD,
            CPU_LABEL_COLOR,
            cpu_busy,
            LV_TXT_COLOR_CMD);

    sprintf(buf_long, "%s%s%s DSP: %d %%%s\n",
            buf_long,
            LV_TXT_COLOR_CMD,
            DSP_LABEL_COLOR,
            (int)AudioProcessorUsage(),
            LV_TXT_COLOR_CMD);

#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 %%",

            
            buf_long,
            MEM_LABEL_COLOR,
            mem_used_pct,
            (int)mem_mon.total_size,
            (int)(mem_mon.total_size - mem_mon.free_size), (int)(mem_mon.free_size), (int)(mem_mon.frag_pct));

#else
    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",
            buf_long,
            MEM_LABEL_COLOR,
            mem_used_pct,
            0, //(int)totram,
            0); //(int)(totram - freeram), (int)freeram);
#endif
    sprintf(buf_long, "%sDSP Blocks: %d\n"
            "DSP Maximum: %d",
            buf_long,
            (int)AudioMemoryUsage(),
            (int)AudioMemoryUsageMax());            

    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:

Code:
/**
 * @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: https://docs.littlevgl.com/#Drawing*/

/* 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_SIZE = LV_HOR_RES * LV_VER_RES
 * - LV_VDB_DOUBLE = 1
 */
#define LV_VDB_TRUE_DOUBLE_BUFFERED 0

/*=================
   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`) */
#if LV_TICK_CUSTOM == 1
#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*/
#if USE_LV_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 caused problem
 * LV_LOG_LEVEL_ERROR       Only critical issue, when the system may fail
 */
#  define LV_LOG_LEVEL    LV_LOG_LEVEL_WARN
/* 1: Print the log with 'printf'; 0: user need to register a callback*/

#  define LV_LOG_PRINTF   0
#endif  /*USE_LV_LOG*/

/*================
 *  THEME USAGE
 *================*/
#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"*/

/*==================
 *    FONT USAGE
 *===================*/

/* More info about fonts: https://docs.littlevgl.com/#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_DEJAVU_10_LATIN_SUP    4
#define USE_LV_FONT_DEJAVU_10_CYRILLIC     4
#define USE_LV_FONT_SYMBOL_10              4

#define USE_LV_FONT_DEJAVU_20              4
#define USE_LV_FONT_DEJAVU_20_LATIN_SUP    4
#define USE_LV_FONT_DEJAVU_20_CYRILLIC     4
#define USE_LV_FONT_SYMBOL_20              4

#define USE_LV_FONT_DEJAVU_30              4
#define USE_LV_FONT_DEJAVU_30_LATIN_SUP    4
#define USE_LV_FONT_DEJAVU_30_CYRILLIC     4
#define USE_LV_FONT_SYMBOL_30              4

#define USE_LV_FONT_DEJAVU_40              4
#define USE_LV_FONT_DEJAVU_40_LATIN_SUP    4
#define USE_LV_FONT_DEJAVU_40_CYRILLIC     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.
 * #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) \
 *                                LV_FONT_DECLARE(my_font_2) \
 */
#define LV_FONT_CUSTOM_DECLARE


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

/*===================
 *  LV_OBJ SETTINGS
 *==================*/
#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*/

/*==================
 *  LV OBJ X USAGE
 *================*/
/*
 * Documentation of the object types: https://docs.littlevgl.com/#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*/
#endif

/*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*/
#endif

/*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)*/
#endif

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

/*************************
 * 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
#if USE_LV_TABLE
#  define LV_TABLE_COL_MAX    12
#endif

/*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*/
#endif

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

/*Calendar (dependencies: -)*/
#define USE_LV_CALENDAR 1

/*Preload (dependencies: lv_arc)*/
#define USE_LV_PRELOAD      1
#if USE_LV_PRELOAD != 0
#  define LV_PRELOAD_DEF_ARC_LENGTH   60      /*[deg]*/
#  define LV_PRELOAD_DEF_SPIN_TIME    1000    /*[ms]*/
#  define LV_PRELOAD_DEF_ANIM         LV_PRELOAD_TYPE_SPINNING_ARC
#endif

/*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)*/
#endif

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

/*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)  */
#endif

/*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)*/
#endif

/*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)*/
#endif

/*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*/
#  define _CRT_SECURE_NO_WARNINGS
#endif

/*--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!

Cheers,
Nick.
 
The RA8875 is a graphics processor? I don't know. If it can draw RGB565 bitmaps quickly enough, just change the disp_flush() call to output to the RA8875 library you are using and update resolution and dpi in lv_conf.h to suit.

Note that a new version of LittleVGL 6.0 has simplified how things work a bit. Some adaptions will be required to the code above. The updated documentation is very good. https://docs.littlevgl.com/en/html/porting/index.html

Cheers!
 
lv_theme_intro.png



The screenshots look very good!

EDIT: I'm suprised, it's MIT.
 
Last edited:
It's a great library, the movement and animation capabilities are outstanding in it's class. I really like it, all my recent projects use it. I've been using it now on Teensy 4 for some time, trying to complete a DAB+ radio with slideshow support using a MonkeyBoard receiver module. It's all coming together quite nicely, libraries are mostly done, front end is on it's way, and lots of hardware building and fit together yet to do.
https://www.monkeyboard.org/shop/index.php?main_page=product_info&cPath=70_75&products_id=271&zenid=ju992meo9a1ucpfr47jjfc8e05

LittleVGL is being very actively developed. The community is friendly and open, much like here. There are even efforts to provide a drag-and-drop GUI creator.
https://forum.littlevgl.com/t/wysiwyg-editor-for-littlevgl/642
 
Unfortunately, I'm overwhelmed and lost. I have been using the Teensy platform mainly the Nextion displays and the RA8875 boards with various bare LCDs. The problem I run into is everything with a graphics driver like the Nextion or 4D Systems etc... has an enormous PC board attached to the back and it limits where I can place the display. The RA8875 is nice in that it's so small and can place it anywhere allowing the display to snap into small bezel frames and existing product housings but it has quirks and various driver documentation seems lacking.

I need to go back and read up on this to get my head around what is in between LittleVGL and whatever display.
 
Very cool. I was just looking at this on the ESP32, nice to see it working on the Teensy. Thanks for sharing!
 
I wrote library that interfaces the ILI9341 and XPT2046 to littleVGL, it has a novel 5 point touch calibration method that corrects scaling factors and mechanical misalignment of the screen using least squares algorithm to get the solution. This technique is much more robust than using the standard Arduino map function that everyone seems to use.

I also implemented the all the SPI driver stuff in this library so you don't have to include any SPI library or even have to write any of the drivers for littleVGL just include this library with your littleVGL project, the drivers are already there and will link to littleVGL to use. I'll post it soon to my GitHub, have some examples that I need to finish first though.
 
Sounds great @duff. Do your ILI9341 drivers use DMA? My current T4 lvgl setup could benefit from that.
I have not had issues using map() for calibration. However, contact bounce or noise on the resistive touch lines made one of my screens do strange things, touches register in wrong spots, dragged items ping off in random directions.

Here is the filter I ended up using for the noisy screen (still using lvgl 6.1). It drops the first few touch activations as the x,y point is not immediately stable. This fixes the issues with no noticeable impact.

Code:
//My touchscreen is noisy and needs extra conditioning
bool my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
  static uint8_t touchCount = 0;
  static int16_t px = 0, py = 0;
  //spiLock = true; 
  TS_Point p = ts.getPoint();
  bool touch = ts.touched() && (p.z > 600);
  //spiLock = false; 
/*  
    Serial.print(" ("); Serial.print(p.x);
    Serial.print(", "); Serial.print(p.y);
    Serial.println(")");
*/  
  if (touch) {
    ScreenSaverInteraction();
    if (touchCount < 2) {
      touchCount++;
      touch = false;
    }
  } else touchCount = 0;
  // Scale from ~0->4000 to tft.width using the calibration #'s
  data->point.x = map((px + p.x) / 2, TS_MINX, TS_MAXX, 0, tft.width());
  data->point.y = map((py + p.y) / 2, TS_MINY, TS_MAXY, 0, tft.height());
  data->state = touch? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
  px = p.x;
  py = p.y;
  return false; /*No buffering now so no more data read*/
}
 
Hi prickle
I'm using Littlevgl with ESP32, it works, but it's slow, you think you can help me, to use Teensy 4 with LVGL?
If possible?
Thank you in advance
 
Hi @momosh13 I can try. I use lvgl with esp32 and it runs really well. What kind of slow? What kind of screen? Connected how?

Cheers,
Prickle
 
Thank you for reply.
I have LOLIN32 connected with ILI9488 with Capacitive Touch, connection is Parallel,
TFT_eSPI Library, and this is the pins,

#define TFT_CS 0
#define TFT_DC 15
#define TFT_WR 4
#define TFT_RD 32
#define TFT_D0 12
#define TFT_D1 13
#define TFT_D2 26
#define TFT_D3 25
#define TFT_D4 19
#define TFT_D5 23
#define TFT_D6 27
#define TFT_D7 14
#define TFT_RST 33

but I like to use it with Teensy 4, if you can, help me.
 
Hi @momosh13. Contrary to what I have been told, I find parallel displays in Arduino can be slow unless handled really well in the driver. I think they waste pins as well. I am not sure parallel connection for ili9488 is supported by the T4 library at the moment.

First step is to get a display working on the T4, either by obtaining or modifying a library for your display or get a known good display like the ili9341 with the spi connection.

This thread might be relevant for you.
 
Teensy 4 - ILI9341

Here is how I connected the Teensy 4 to an ILI9341 resistive touch LCD with SD card

ili9341_t4.jpg

The two transistors control the backlight with an active low signal and are NPN BC548 and PNP BC558.

The 100 ohm resistors on the SPI lines may not be required.

The resistors on the back of the LCD associated with the SD card may need to be disabled by being removed and the pads bridged over.
 
Thank you, for all this info, I'm done with hardware, you think, you can share, any working LVGL / Teensy 4 code?
Thanks again
 
No, not quickly. My working code is part of a larger project with some parts possibly covered by an NDA. I could post snippets as above but duff offered a library that would be better.
Hi @duff, any progress on the library?
 
I just want to give a +1 to LittlevGL. Currently using it on a Teensy 4.0 with an IL9342 based 2.3" display and XPT2046 touchscreen, and it's pretty straightforward to use and the results are outstanding, far better than anything I could hope to spin by hand! Well worth a look if anyone is considering building a UI. I made a quick and dirty video of it running on at "only" 396Mhz.

https://www.youtube.com/watch?v=Xkov499IYvo

In the video, LittlevGL powers the main menu, the music player buttons and the Tic Tac Toe selection screen
 
I just want to give a +1 to LittlevGL. Currently using it on a Teensy 4.0 with an IL9342 based 2.3" display and XPT2046 touchscreen, and it's pretty straightforward to use and the results are outstanding, far better than anything I could hope to spin by hand! Well worth a look if anyone is considering building a UI. I made a quick and dirty video of it running on at "only" 396Mhz.

https://www.youtube.com/watch?v=Xkov499IYvo

In the video, LittlevGL powers the main menu, the music player buttons and the Tic Tac Toe selection screen

Awesome!
Is your code available? (esp. Pac Man? :) )
 
Awesome!
Is your code available? (esp. Pac Man? :) )

Thanks :) The Pacman code is currently a little too interwoven with my display/input code, and esp. coded to the rotation of the ILI9342 vs ILI9341, but yeah, after this project, I can separate and clean up and put out there!
 
Here is how I connected the Teensy 4 to an ILI9341 resistive touch LCD with SD card

View attachment 19096

The two transistors control the backlight with an active low signal and are NPN BC548 and PNP BC558.

The 100 ohm resistors on the SPI lines may not be required.

The resistors on the back of the LCD associated with the SD card may need to be disabled by being removed and the pads bridged over.

Is there somewhere I can download an example code for use with the Teensy 4 and ILI9341 ?
 
Hi @prickle I'm try to get LittleVGL to compile for Teensy 4.0 and looking at your example in post #1.

The LV_VDB_SIZE_IN_BYTES is not defined. I've made it to 307200. (480x320*16)/8 = 307200

But it still won't compile as it couldn't find lv_vdb_set_adr() function. I've searched through the lvgl directory and couldn't find it.

Where is the lv_vdb_set_adr() function ?
 
Hi skpang,

the code prickle posted was referred to LittleVGL v5, which is an old version.

You are probably using v6; there were many changes between v5 and v6, that's why you can't compile his example.
LittleVGL is now heading towards v7, by the way.

You may seek help in the LittleVGL forum too, maybe, to get help with the migration?
https://forum.littlevgl.com/
 
I've got it working with the Teensy 4.0 and a 3.5" LCD with ILI9488_t3 driver. Managed to get the SPI running at 60MHz. Its fast.

IMG_5559.jpg
 
Back
Top