Multitasking with LVGL

Hi

Does LVGL include a multitasking function? Do all other functions that use LVGL need to use the built-in function to handle multitasking, such as display and touch panel?

If I need to use an RTOS, like FreeRTOS, how do I do that? Are there any tutorial resources you can recommend?

For a simple application, could we use a simple timer interrupt (e.g. ring wheel)?

My goal is to create a program using LVGL (Squareline Studio) to control several PWM outputs, LEDs, and communicate with sensors. While I can code for each of these functions individually, I’m not sure how to integrate them together. If I simply merge them, the performance may be blocked by one another. For example, the PWM signal may be affected or blocked by GUI updates or input.

MCU/Processor/Board = ESP32
compiler = PlatformIO with Arduino framework
LVGL version = v8.3

I’m not asking for a complete code, but I would appreciate if you could provide an example or tutorial for me to study and try out myself.

#include <Arduino.h>
#include <TAMC_GT911.h>

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <ui.h>

hw_timer_t * timer = NULL; // create a timer object

void IRAM_ATTR onTimer() 
{ // timer interrupt handler function
  // do something on timer interrupt
    
    lv_timer_handler(); /* let the GUI do its work */
}

/*Don't forget to set Sketchbook location in File/Preferencesto the path of your UI project (the parent foder of this INO file)*/

/*Change to your screen resolution*/
static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[ screenWidth * screenHeight / 10 ];

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */

//myTest
#define TOUCH_SDA 21
#define TOUCH_SCL 22
#define TOUCH_INT 25
#define TOUCH_RST 26
#define TOUCH_WIDTH  screenWidth
#define TOUCH_HEIGHT screenHeight

TAMC_GT911 tp = TAMC_GT911(TOUCH_SDA, TOUCH_SCL, TOUCH_INT, TOUCH_RST, TOUCH_WIDTH, TOUCH_HEIGHT);

#define LED_GPIO 14
 
#if LV_USE_LOG != 0
/* Serial debugging */
void my_print(const char * buf)
{
    Serial.printf(buf);
    Serial.flush();
}
#endif

/* Display flushing */
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
    uint32_t w = ( area->x2 - area->x1 + 1 );
    uint32_t h = ( area->y2 - area->y1 + 1 );

    tft.startWrite();
    tft.setAddrWindow( area->x1, area->y1, w, h );
    tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
    tft.endWrite();

    lv_disp_flush_ready( disp );
}

/*Read the touchpad*/
void my_touchpad_read( lv_indev_drv_t * indev_driver, lv_indev_data_t * data )
{
//myTest
    tp.read();
    if(tp.isTouched)
    {
        data->state = LV_INDEV_STATE_PR;
        data->point.x = tp.points[0].x;
        data->point.y = tp.points[0].y;
    }
    else
    {
        data->state = LV_INDEV_STATE_REL;
    }
}

void setup()
{

	
    Serial.begin( 115200 ); /* prepare for possible serial debug */

    String LVGL_Arduino = "Hello Arduino! ";
    LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

    Serial.println( LVGL_Arduino );
    Serial.println( "I am LVGL_Arduino" );

//myTest
    tp.begin();
    tp.setRotation(ROTATION_LEFT);

    lv_init();

#if LV_USE_LOG != 0
    lv_log_register_print_cb( my_print ); /* register print function for debugging */
#endif

    tft.begin();          /* TFT init */
    tft.setRotation( 3 ); /* Landscape orientation, flipped */

    lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 10 );

    /*Initialize the display*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init( &disp_drv );
    /*Change the following line to your display resolution*/
    disp_drv.hor_res = screenWidth;
    disp_drv.ver_res = screenHeight;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register( &disp_drv );

    /*Initialize the (dummy) input device driver*/
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init( &indev_drv );
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = my_touchpad_read;
    lv_indev_drv_register( &indev_drv );


    ui_init();

    Serial.println( "Setup done" );
	
	//myTest
    pinMode(TFT_BL, OUTPUT);
	digitalWrite(TFT_BL,HIGH);

    pinMode(LED_GPIO, OUTPUT);
	digitalWrite(LED_GPIO,LOW);

	timer = timerBegin(0, 80, true); // use timer 0, prescaler 80, count up
	timerAttachInterrupt(timer, &onTimer, true); // attach the interrupt handler function to the timer
	timerAlarmWrite(timer, 1000000, true); // set the timer alarm to 1 second, repeat timer
	timerAlarmEnable(timer); // enable the timer alarm

    //lv_label_set_text(ui_Label1, "------");
}

void loop()
{
    //lv_timer_handler(); /* let the GUI do its work */
    delay(5);
    //delay(2000);
    //lv_label_set_text(ui_Label1, "------");
}

1 Like

LVGL itself doesn’t do anything actively. It just waits for inputs and expects you to regularly call lv_tick_inc() and lv_timer_handler().

Now it’s your choice if you go for a Superloop architecture with the Arduino loop() or go with an RTOS, where you can separate your work into actual tasks/threads. Research these two topics and you will see how either of them would work, you can decide which path is for you :slight_smile:

Also see here for LVGL with Arduino Arduino — LVGL documentation and for RTOS it is a bit more involved, but not a lot, especially with the smaller RTOSs like FreeRTOS (nothing to fear at all!).

1 Like

checkout GitHub - ivanseidel/ArduinoThread: ⏳ A simple way to run Threads on Arduino

1 Like

Thanks, nylnx and denisdemais for your support.

I am learning lv_tick_inc() and lv_timer_handler(). I would like to clarify the differences and relationships between them. Somebody said that lv_tick_inc() is not included in the standard/ basic library of LVGL.

I don’t see it in the Arduino code.

denisdemais I will try your example later.

Hope you(all) can support me in the future.

Hi

I am currently facing a challenge with the functions lv_tick_inc() and lv_task_create() in my project, and I was hoping to seek your expert guidance to resolve the issue.

While working with PlatformIO, I encountered an error indicating that these functions are undefined. I suspect that I might be missing a necessary library or configuration. Could you please provide me with any relevant libraries that need to be included? It would be immensely helpful if you could also provide some code examples illustrating the correct implementation.

Furthermore, I have observed a peculiar behavior with the lv_task_handler() function, which I believe is responsible for refreshing the display in the LVGL framework. Typically, it can be called multiple times within the main loop without any issues. However, when I attempt to invoke it from within a timer interrupt (specifically, within the onTimer() function) with a frequency of one second per interrupt, the system hangs up.

I am seeking your expertise to understand what might be causing this problem and to determine the necessary steps to rectify it. Your insights and guidance in this matter would be greatly appreciated.

#include <Arduino.h>
#include <TAMC_GT911.h>

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <ui.h>

hw_timer_t * timer = NULL; // create a timer object

/*Don't forget to set Sketchbook location in File/Preferencesto the path of your UI project (the parent foder of this INO file)*/

/*Change to your screen resolution*/
static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[ screenWidth * screenHeight / 10 ];

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */

//myTest
#define TOUCH_SDA 21
#define TOUCH_SCL 22
#define TOUCH_INT 25
#define TOUCH_RST 26
#define TOUCH_WIDTH  screenWidth
#define TOUCH_HEIGHT screenHeight

TAMC_GT911 tp = TAMC_GT911(TOUCH_SDA, TOUCH_SCL, TOUCH_INT, TOUCH_RST, TOUCH_WIDTH, TOUCH_HEIGHT);

#define LED_GPIO 14
hw_timer_t *My_timer = NULL;

void IRAM_ATTR onTimer()
{
    digitalWrite(LED_GPIO, !digitalRead(LED_GPIO));
    lv_task_handler();
}
 
#if LV_USE_LOG != 0
/* Serial debugging */
void my_print(const char * buf)
{
    Serial.printf(buf);
    Serial.flush();
}
#endif

/* Display flushing */
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
    uint32_t w = ( area->x2 - area->x1 + 1 );
    uint32_t h = ( area->y2 - area->y1 + 1 );

    tft.startWrite();
    tft.setAddrWindow( area->x1, area->y1, w, h );
    tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
    tft.endWrite();

    lv_disp_flush_ready( disp );
}

/*Read the touchpad*/
void my_touchpad_read( lv_indev_drv_t * indev_driver, lv_indev_data_t * data )
{
//myTest
    tp.read();
    if(tp.isTouched)
    {
        data->state = LV_INDEV_STATE_PR;
        data->point.x = tp.points[0].x;
        data->point.y = tp.points[0].y;
    }
    else
    {
        data->state = LV_INDEV_STATE_REL;
    }
}

void setup()
{

	
    Serial.begin( 115200 ); /* prepare for possible serial debug */

    String LVGL_Arduino = "Hello Arduino! ";
    LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

    Serial.println( LVGL_Arduino );
    Serial.println( "I am LVGL_Arduino" );

//myTest
    tp.begin();
    tp.setRotation(ROTATION_LEFT);

    lv_init();

#if LV_USE_LOG != 0
    lv_log_register_print_cb( my_print ); /* register print function for debugging */
#endif

    tft.begin();          /* TFT init */
    tft.setRotation( 3 ); /* Landscape orientation, flipped */

    lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 10 );

    /*Initialize the display*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init( &disp_drv );
    /*Change the following line to your display resolution*/
    disp_drv.hor_res = screenWidth;
    disp_drv.ver_res = screenHeight;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register( &disp_drv );

    /*Initialize the (dummy) input device driver*/
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init( &indev_drv );
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = my_touchpad_read;
    lv_indev_drv_register( &indev_drv );


    ui_init();

    Serial.println( "Setup done" );
	
	//myTest
    pinMode(TFT_BL, OUTPUT);
	digitalWrite(TFT_BL,HIGH);

    pinMode(LED_GPIO, OUTPUT);
	digitalWrite(LED_GPIO,LOW);

    My_timer = timerBegin(0, 80, true);
    timerAttachInterrupt(My_timer, &onTimer, true);
    timerAlarmWrite(My_timer, 1000000, true);
    timerAlarmEnable(My_timer);

}

void loop()
{
    delay(200);
    lv_label_set_text(ui_Label1, "111111");
    //lv_task_handler();
    delay(200);
    lv_label_set_text(ui_Label1, "222222");
    //lv_task_handler();
    delay(200);
    lv_label_set_text(ui_Label1, "333333");
    //lv_task_handler();
    delay(200);
    lv_label_set_text(ui_Label1, "444444");
    //lv_task_handler();
    //delay(1000);
/*
    for (uint8_t x = 1; x <5; x++) {
        Serial.println( x );
        digitalWrite(LED_GPIO,!digitalRead(LED_GPIO));
        delay(10);
        switch(x) {
            case 1:
            lv_label_set_text(ui_Label1, "111111");
            break;

            case 2:
            lv_label_set_text(ui_Label1, "222222");
            break;

            case 3:
            lv_label_set_text(ui_Label1, "333333");
            break;

            case 4:
            lv_label_set_text(ui_Label1, "444444");
            break;
        }
    } */
}

First learn howto use lv_conf.h
Then …
And FYI in interrupt you can call lv_tick_inc, but no lv_timer_handler.
And stop use Delay…

And better read this Quick overview — LVGL documentation

Hi Marian

Thanks for your advice. Sorry, my English is not good, and maybe explained my message clearly
.
I checked lv_conf.h, but don’t know how to use it for multitasking. Could you share more hints with me?

“And FYI in interrupt you can call lv_tick_inc, but no lv_timer_handler.”
But I can’t use “lv_tick_inc()”. Could you share an example with me?

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <ui.h>

/*Don't forget to set Sketchbook location in File/Preferencesto the path of your UI project (the parent foder of this INO file)*/

/*Change to your screen resolution*/
static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[ screenWidth * screenHeight / 10 ];

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */

#if LV_USE_LOG != 0
/* Serial debugging */
void my_print(const char * buf)
{
    Serial.printf(buf);
    Serial.flush();
}
#endif

/* Display flushing */
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
    uint32_t w = ( area->x2 - area->x1 + 1 );
    uint32_t h = ( area->y2 - area->y1 + 1 );

    tft.startWrite();
    tft.setAddrWindow( area->x1, area->y1, w, h );
    tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
    tft.endWrite();

    lv_disp_flush_ready( disp );
}

/*Read the touchpad*/
void my_touchpad_read( lv_indev_drv_t * indev_driver, lv_indev_data_t * data )
{
    uint16_t touchX = 0, touchY = 0;

    bool touched = false;//tft.getTouch( &touchX, &touchY, 600 );

    if( !touched )
    {
        data->state = LV_INDEV_STATE_REL;
    }
    else
    {
        data->state = LV_INDEV_STATE_PR;

        /*Set the coordinates*/
        data->point.x = touchX;
        data->point.y = touchY;

        Serial.print( "Data x " );
        Serial.println( touchX );

        Serial.print( "Data y " );
        Serial.println( touchY );
    }
}

void setup()
{
    Serial.begin( 115200 ); /* prepare for possible serial debug */

    String LVGL_Arduino = "Hello Arduino! ";
    LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

    Serial.println( LVGL_Arduino );
    Serial.println( "I am LVGL_Arduino" );

    lv_init();

#if LV_USE_LOG != 0
    lv_log_register_print_cb( my_print ); /* register print function for debugging */
#endif

    tft.begin();          /* TFT init */
    tft.setRotation( 3 ); /* Landscape orientation, flipped */

    lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 10 );

    /*Initialize the display*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init( &disp_drv );
    /*Change the following line to your display resolution*/
    disp_drv.hor_res = screenWidth;
    disp_drv.ver_res = screenHeight;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register( &disp_drv );

    /*Initialize the (dummy) input device driver*/
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init( &indev_drv );
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = my_touchpad_read;
    lv_indev_drv_register( &indev_drv );


    ui_init();

    Serial.println( "Setup done" );

    lv_task_inc(1);
}

void loop()
{
    lv_timer_handler(); /* let the GUI do its work */
    delay(5);
}

Error message

C:\Users\Owner\Documents\SquareLine\New folder\TRIAL_r00_01\ui\ui.ino: In function 'void setup()':
C:\Users\Owner\Documents\SquareLine\New folder\TRIAL_r00_01\ui\ui.ino:109:5: error: 'lv_task_inc' was not declared in this scope
     lv_task_inc(1);
     ^~~~~~~~~~~
C:\Users\Owner\Documents\SquareLine\New folder\TRIAL_r00_01\ui\ui.ino:109:5: note: suggested alternative: '_lv_txt_ins'
     lv_task_inc(1);
     ^~~~~~~~~~~
     _lv_txt_ins

exit status 1

Compilation error: 'lv_task_inc' was not declared in this scope

WTF in setup and you maybe try write lv_tick_inc

I try explain your question ask multitasking, but your code never start next task only loop.

LV_CONF and link i send say all required info:

  • Call lv_tick_inc(x) every x milliseconds in a Timer or Task (x should be between 1 and 10). It is required for the internal timing of LVGL. Alternatively, configure LV_TICK_CUSTOM (see lv_conf.h) so that LVGL can retrieve the current time directly.

For esp is simpler enable in conf custom tick and you dont need code tick_inc.

And next info from conf is

/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 30      /*[ms]*/

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

this define lv_timer_handler repeated in loop do draw or input scan or nothink or your declared lvtimed tasks.

Thats all folks

Oh I see.

I correct it and try again.

Hi

I am reaching out to seek your assistance and expertise in an upcoming board development project that involves LVGL (UI) for controlling LED indicators and PWM for LCD dimming and servo motor control. I would greatly appreciate your support in optimizing the functionality and resolving some challenges associated with PWM implementation.

My target specifications for PWM are as follows:

PWM cycle period: 333Hz
PWM resolution: 4096
Currently, I am facing certain limitations with the available methods for PWM control. I have explored using the analogWrite() function, which restricts the range of change to 0-255 and lacks the flexibility to precisely control pulse frequency.

Additionally, I have experimented with the threads library, but it imposes a minimum period of 1 ms, which compromises the resolution required for precise motor movement and brightness adjustments.

I have also attempted to implement a timer interrupt, but the PWM signal gets affected by the interference caused by the indev (touch input). I am seeking guidance on reducing or isolating this interference to eliminate any unwanted blinking of the LED indicators.

I kindly request your expert insights and suggestions on overcoming these challenges, optimizing the PWM control for increased precision and stability, and ensuring the smooth operation of the board with LVGL UI integration.

Your comments and recommendations are highly valued, and your support in this endeavor would be immensely appreciated. Should you require any further details or have any questions, please do not hesitate to reach out to me.

Thank you for your time and consideration. I look forward to your valuable input.

BR

LC

For PWMs you require hw ESP support, not part of LVGL, but exist library for arduino. I use one khoih-prog/ESP32_PWM