lvgl/lvgl

LVGL task sleep

espzav opened this issue · 5 comments

Introduce the problem

I want to sleep main LVGL task in LVGL9. But it sleeps too much...
I have this inside task:

 while (lvgl_port_ctx.running) {
        /* Wait for queue or timeout (sleep task) */
        xQueueReceive(lvgl_port_ctx.lvgl_queue, &event, pdMS_TO_TICKS(task_delay_ms));

        if (lv_display_get_default() && lvgl_port_lock(0)) {
            task_delay_ms = lv_timer_handler();
            lvgl_port_unlock();
        }
        if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) {
            task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
        } else if (task_delay_ms < 1) {
            task_delay_ms = 1;
        }

        /* Sleep task, when everything done */
        if (lv_anim_count_running() == 0 && lv_display_get_inactive_time(NULL) > 2 * LV_DEF_REFR_PERIOD) {
            task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
        }
    }

Task wake up from touch interrupt and from these events:

    lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_INVALIDATE_AREA, disp_ctx);
    lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_REFR_REQUEST, disp_ctx);

And I don't know, how to handle it right. Sometimes, the task sleeps too much. For example in this code (https://github.com/espressif/esp-bsp/blob/master/examples/display/main/lvgl_demo_ui.c):

static void anim_timer_cb(lv_timer_t *timer)
{
    my_timer_context_t *timer_ctx = (my_timer_context_t *) timer->user_data;
    int count = timer_ctx->count_val;
    lv_obj_t *scr = timer_ctx->scr;

    // Play arc animation
    if (count < 90) {
        lv_coord_t arc_start = count > 0 ? (1 - cosf(count / 180.0f * PI)) * 270 : 0;
        lv_coord_t arc_len = (sinf(count / 180.0f * PI) + 1) * 135;

        for (size_t i = 0; i < sizeof(arc) / sizeof(arc[0]); i++) {
            lv_arc_set_bg_angles(arc[i], arc_start, arc_len);
            lv_arc_set_rotation(arc[i], (count + 120 * (i + 1)) % 360);
        }
    }

    // Delete arcs when animation finished
    if (count == 90) {
        for (size_t i = 0; i < sizeof(arc) / sizeof(arc[0]); i++) {
            lv_obj_del(arc[i]);
        }

        // Create new image and make it transparent
        img_text = lv_img_create(scr);
        lv_img_set_src(img_text, &esp_text);
        lv_obj_set_style_img_opa(img_text, 0, 0);
    }

    // Move images when arc animation finished
    if ((count >= 100) && (count <= 180)) {
        lv_coord_t offset = (sinf((count - 140) * 2.25f / 90.0f) + 1) * 20.0f;
        lv_obj_align(img_logo, LV_ALIGN_CENTER, 0, -offset);
        lv_obj_align(img_text, LV_ALIGN_CENTER, 0, 2 * offset);
        lv_obj_set_style_img_opa(img_text, offset / 40.0f * 255, 0);
    }

    // Delete timer when all animation finished
    if ((count += 5) == 220) {
        lv_timer_del(timer);
    } else {
        timer_ctx->count_val = count;
    }
}

void example_lvgl_demo_ui(lv_obj_t *scr)
{
    // Create image
    img_logo = lv_img_create(scr);
    lv_img_set_src(img_logo, &esp_logo);
    lv_obj_center(img_logo);

    // Create arcs
    for (size_t i = 0; i < sizeof(arc) / sizeof(arc[0]); i++) {
        arc[i] = lv_arc_create(scr);

        // Set arc caption
        lv_obj_set_size(arc[i], 220 - 30 * i, 220 - 30 * i);
        lv_arc_set_bg_angles(arc[i], 120 * i, 10 + 120 * i);
        lv_arc_set_value(arc[i], 0);

        // Set arc style
        lv_obj_remove_style(arc[i], NULL, LV_PART_KNOB);
        lv_obj_set_style_arc_width(arc[i], 10, 0);
        lv_obj_set_style_arc_color(arc[i], arc_color[i], 0);

        // Make arc center
        lv_obj_center(arc[i]);
    }

    // Create timer for animation
    static my_timer_context_t my_tim_ctx = {
        .count_val = -90,
    };
    my_tim_ctx.scr = scr;
    lv_timer_create(anim_timer_cb, 20, &my_tim_ctx);
}

Could you help me please?

Hi Vilem,

I don't understand this part:

        if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) {
            task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;

If LVGL says there is only '1 ms t'o the next timer, task_max_sleep_ms is used instead of 1. Why?

I think it can be as simple as this:

 while (lvgl_port_ctx.running) {
        /* Wait for queue or timeout (sleep task) */
        xQueueReceive(lvgl_port_ctx.lvgl_queue, &event, pdMS_TO_TICKS(task_delay_ms));

        if (lv_display_get_default() && lvgl_port_lock(0)) {
            task_delay_ms = lv_timer_handler();
            lvgl_port_unlock();
        } else {
           task_delay_ms = 1; /*Keep trying*/
        }
        
        if ((task_delay_ms == LV_NO_TIMER_READY) task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
    }

Hi @kisvegabor,

if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) {
            task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;

You are right, this is not right. It was added in past in some PR as a fix.

But the main problem is, when I make it like this:

       TickType_t wait = pdMS_TO_TICKS(task_delay_ms);
        if (wait < 1) {
            wait = 1;
        }
        xQueueReceive(lvgl_port_ctx.lvgl_queue, &event, wait);

        if (lv_display_get_default() && lvgl_port_lock(0)) {
            task_delay_ms = lv_timer_handler();
            lvgl_port_unlock();
        } else {
           task_delay_ms = 1; /*Keep trying*/
        }
        
        if (task_delay_ms == LV_NO_TIMER_READY) {
            task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
        }

There is maximum sleep 33ms. The last condition isn't true at no time.
This is the reason, why I added this:

if (lv_anim_count_running() == 0 && lv_display_get_inactive_time(NULL) > 2 * LV_DEF_REFR_PERIOD) {
            task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
        }

There is maximum sleep 33ms. The last condition isn't true at no time.

I think it's because the read timer of the indev is always running. (The animation and display refresh timers are paused if there is nothing to do on their side)

For indevs you can do 2 things:

  1. After some inactivity pause the indev(s):
  lv_indev_t * indev = lv_indev_get_next(NULL); /**Can be used to iterate through all indevs*/
  lv_timer_t * t = lv_indev_get_read_timer();
  lv_timer_pause(t);

and in case of a touch or key interrupt resume the indev.
2. Use the indevs in event drive mode.

Thank you very much! The second solution is working great with interrupts for touch/buttons indev!

Cool! Bot note that you need to be sure that you don't call lv_timer_handler and lv_indev_read at the same time.