Lv_task_handler doesn't refresh screen, only lv_refr_now works, even for hello world


I’m trying to get a simple “hello world” up and running. To show one that can update the screen, I have it change words once every second eg “hello”…“world.” The task handler is not updating on its own - it’s like it draws once, maybe twice, then gives up. The values can be output via Serial prints but the screen will only update if I use lv_refr_now(NULL) which feels hacky, I want to understand and fix what’s broken.

What MCU/Processor/Board and compiler are you using?

ESP32, and I’m using VSCode’s Platformio extension, installing the LVGL library with Platformio’s UI.

What do you want to achieve?

For now, just a working hello world demo that shows text or visuals that change on the screen. Eventually, I’m trying to create a dashboard for a car that’s being remote controlled, so we can monitor what’s going on, and tweak values that control the gas, steering etc.

What have you tried so far?

I started with LVGL 8.3.7 (latest available for Platformio), but the device would immediately get stuck in a reboot loop if I tried to run lv_task_handler.
So I downgraded to 7.11.0, and I was glad to see that the initial “Hello world” static text was displayed. A simple chart with static values was also working. But I noticed that if I tried to update the values, the screen still showed the old data.
I’ve also tried adding delays before, and after updating the values. I’ve tried moving the lv_task_handler call to after the value updates, and sometimes it draws a second value but then stops (This really drove me crazy).
I’ve changed LV_DISP_DEF_REFR_PERIOD to 20,30,100, 1000
I’ve tried adding while(lv_task_handler()) {delay(5);}
I’ve tried adding lv_obj_invalidate(chart) and lv_chart_refresh(chart)
I’ve spent days trouble shooting this and giving in, I need some help :slight_smile:

Code to reproduce

#include "lvgl_display.h"
#include "lvgl.h"
#include <TFT_eSPI.h> // Include the graphics library

TFT_eSPI tft = TFT_eSPI(); 

// Called by the LVGL library to write to the display // eSPI
void 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.setAddrWindow(area->x1, area->y1, w, h);
    tft.pushColors((uint16_t *)color_p, w * h, true);


// Buffer for LVGL to draw
static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10]; // Declare a buffer for 10 lines

void lvgl_init() {
    // Initialize LVGL

    // Initialize your display buffer
    lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);

    // Initialize a display driver
    lv_disp_drv_t disp_drv;               /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);          /*Basic initialization*/
    disp_drv.hor_res = 320;
    disp_drv.ver_res = 240;
    disp_drv.flush_cb = disp_flush;    /*Set your driver function*/
    disp_drv.buffer = &disp_buf;          /*Assign the buffer to the display*/
    lv_disp_drv_register(&disp_drv);      /*Finally register the driver*/

    // Initialize TFT
    tft.setRotation(3); // Use landscape orientation

lv_obj_t *label; // global
void create_hello_world_label() {
    // Create and style a label with a larger blue box around it
    lv_obj_t *label_bg = lv_obj_create(lv_scr_act(), NULL);
    lv_obj_set_size(label_bg, 200, 50);
    lv_obj_set_style_local_bg_color(label_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLUE);
    lv_obj_set_style_local_radius(label_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 10);
    lv_obj_set_style_local_value_font(label_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, &lv_font_montserrat_14);

    label = lv_label_create(label_bg, NULL);
    lv_label_set_text(label, "Hello, world!");
    lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0);

    // Align label to center
    lv_obj_align(label_bg, NULL, LV_ALIGN_CENTER, 0, 0);

void setup() {
    Serial.begin (115200);  // Open serial port
    // Initialize the display


// Add an array of words to cycle through
const char* words[] = {"Hello", "World", "ESP32", "Arduino", "LVGL"};
const int num_words = sizeof(words) / sizeof(words[0]);  // Calculate the number of words
int word_index = 0;  // Start with the first word

void update_hello_world_label(const char* word) {
    lv_label_set_text(label, word);  // Update the label text
    lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0); // Re-center the label on the screen

void loop() {
    // Update the disp
    delay(5); // Allow some delay for system tasks
    // Update the hello world text every second
    word_index = (word_index + 1) % num_words;  // Cycle through the words
    update_hello_world_label(words[word_index]);  // Update the label with the new word
    // lv_refr_now(NULL); // force redraw

    delay(1000); // delay so that you don't flood your Serial monitor with messages

I don’t see you calling lv_tick_inc anywhere. This needs to be done in order to increment the timers. There is a timer that has to expire for LVGL to refresh the display and if you don’t tell LVGL how much time has passed it’s not going to know when the timer has expired.

You can call lv_tick_inc from an ISR but you cannot call lv_task_handler from an ISR, that has to be done from your main loop.

You don’t want to have a 1000 millisecond stall in your main loop. You are better of with a main loop that is something along the lines of

unsigned long last_serial_time = millis();
unsigned long current_serial_time = last_serial_time;
unsigned long last_time = millis();
unsigned long current_time = last_time;

void loop() {
    current_time = millis();
    lv_tick_inc(current_time - last_time);
    last_time = current_time;

    current_serial_time = millis();
    if (current_serial_time - last_serial_time >= 1000) {
        last_serial_time = current_serial_time;
        // Update the hello world text every second
        word_index = (word_index + 1) % num_words;  // Cycle through the words
        update_hello_world_label(words[word_index]);  // Update the label with the new word

    // lv_refr_now(NULL); // force redraw

serial_print_func you would have to define for whatever it is you need to print out to the serial port.

1 Like

You’re right! It works :slight_smile: Thank you so much! I think there was a setting in lv_conf.h that I misread, or maybe it was from version 8.x, I thought it said I didn’t have to use the lv_tick_inc(), it was called LV_TICK_CUSTOM. I probably didn’t set it up properly. It’s crazy that none of the chatgpt type bots I asked about this brought up lv_tiick_inc(). Anyway, you made my day, thank you again!

no worries m8, glad to help.

You are using an old’er ish version of LVGL and I would have to look into how the custom tick thing works. while I do not believe 8.3.x is different than 9 IDK about 7.x which is what you have said you are using.

Is 9 available already? I’ve tried making these changes for 8.3.x and I’m still having the reboot loop if I run lv_task_handler, so I can’t even get hello world to display. I made a new thread if you’d like to take a look help plz

It’s not released yet. I believe it’s scheduled for release 4th quarter this year.

where is: lvgl_display.h?