Description
TL;DR: Calling lv_obj_add_state( ui_playBtn, LV_STATE_PRESSED )
does not change the appearance of a image button at runtime, but works during lvgl setup.
I’m using ESP-IDF to build an app running on a ESP32. While the GUI properly renders at startup, including the pressed status of the “play” button; it does not render status changes of the “play” and other buttons after startup, triggered by a separate FreeRTOS task on some events (turning a rotary encoder).
The main app structure (and LVGL setup) comes from the esp_lcd_ili9488 example, which works. The esp_lcd_ili9488 example has been (heavily) refactored of course, but the way LVGL and the SPI display are initialized is the same.
Does anyone have any idea what’s wrong in what I am doing?
What MCU/Processor/Board and compiler are you using?
MCU: ESP32-WROVER (Freenove)
DISPLAY: ILI9488 SPI, 480x320
ESP-IDF: 5.2
ESP Components:
atanisoft/esp_lcd_ili9488
: 1.0.9 (esp component registry, project’s github)lvgl/lvgl
: 8.3.11 (esp component registry)
What LVGL version are you using?
8.3.11
What do you want to achieve?
Change the appearance of image buttons in the GUI when triggering the rotary encoder callback.
What have you tried so far?
- moving
lv_tick_inc
from anesp_timer_start_periodic
callback to a separate FreeRTOS task in a loop withvTaskDelay
- moving
lv_tick_inc
basically everywhere (sorry for the lack of details) - sending
LV_EVENT_REFRESH
events to all buttons and to the root object - 2-3 more weeks of different attempts I lost track of
Code to reproduce
The code is sitting in this repo in the ui_logic branch:
- ILI9488 setup: /components/drabaioli__ili9488_display/ili9488_display.c
- LVGL initialization : /components/drabaioli__lvgl_ui/lvgl_ui.c
- LVGL ticker:
static void IRAM_ATTR lvgl_tick_cb(void *param)
{
lv_tick_inc(LVGL_UPDATE_PERIOD_MS);
}
void initialize_lvgl(...) {
...
ESP_LOGI(TAG, "Creating LVGL tick timer");
const esp_timer_create_args_t lvgl_tick_timer_args =
{
.callback = &lvgl_tick_cb,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LVGL_UPDATE_PERIOD_MS * 1000));
}
- GUI setup (automatically generated with SquareLine Studio):
lv_obj_t * ui_Screen1;
lv_obj_t * ui_playBtn;
...
void ui_Screen1_screen_init(void)
{
ui_Screen1 = lv_obj_create(NULL);
lv_obj_clear_flag(ui_Screen1, LV_OBJ_FLAG_SCROLLABLE); /// Flags
ui_screenBackground = lv_obj_create(ui_Screen1);
...
ui_playBtn = lv_imgbtn_create(ui_Screen1);
lv_imgbtn_set_src(ui_playBtn, LV_IMGBTN_STATE_RELEASED, NULL, &ui_img_play_png, NULL);
lv_imgbtn_set_src(ui_playBtn, LV_IMGBTN_STATE_PRESSED, NULL, &ui_img_play_border_png, NULL);
...
}
void ui_init(void)
{
lv_disp_t * dispp = lv_disp_get_default();
lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
true, LV_FONT_DEFAULT);
lv_disp_set_theme(dispp, theme);
ui_Screen1_screen_init();
lv_disp_load_scr(ui_Screen1);
}
- Setup initial UI state and update logic (works at startup, does not at runtime,
update_ui_components
gets correctly triggered by rotary encoder callback, butlv_obj_add_state
has no effect):
static void update_ui_components( state_t * state )
{
...
lv_obj_clear_state( ui_playBtn, LV_STATE_PRESSED );
...
switch( state->active_component )
{
...
case PLAY_PAUSE:
lv_obj_add_state( ui_playBtn, LV_STATE_PRESSED );
ESP_LOGI( "DIEGO", "PLAY_PAUSE" );
lv_event_send( ui_playBtn, LV_EVENT_REFRESH, NULL );
break;
...
}
lv_event_send( ui_Screen1, LV_EVENT_REFRESH, NULL );
}
void app_main(void)
{
// Setup everything
...
while( 1 )
{
vTaskDelay( pdMS_TO_TICKS( 10 ) );
lv_timer_handler();
}
}
- rotary encoder position change callback:
static void IRAM_ATTR encoder_position_cb( position_event_t event )
{
// indirectly call update_ui_components
}
Screenshot and/or video
(top-left corner CPU usage gets updated, media player buttons don’t)