Description
Basic / simple drop-down menu does not close once item is selected !?
Looks ok with online simulator, so I assume I’m doing something wrong…
What MCU/Processor/Board and compiler are you using?
Two different configurations:
1/ custom board with STM32U5A5 + 320x480 LCD screen (ST7796S controller / RGB565 swapped) + I2C touch screen ; screen is connected to MCU with parallel bus, and I’m using MCU’s FMC hardware to write data to the LCD ; note that it’s a bare-metal application, there is no operating system.
Compiler is arm-none-eabi-gcc (GNU Toolchain for the Arm Architecture 11.2-2022.02 (arm-11.14)) 11.2.1 20220111
2/ simulator of above custom board, running on Linux Ubuntu ; simulator is quite accurate and simulates all required MCU & peripherals hardware. So I also have a simulator for the LCD display controller, touch screen, and all other chips on the custom board…
Compiler is gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
Both configurations show same issue.
What LVGL version are you using?
9.3.0
What do you want to achieve?
Once an item is selected, I would like the dropdown menu to close and the items list to be hidden.
I expect screen area behind the drop down list to be refreshed.
What have you tried so far?
It’s the first time I’m using LVGL, so I started to have it compile with my project, and “connect” it to the the LCD and to the touch screen => OK
Then I created a basic button + callback => OK
Then I added a switch + callback => OK.
Then I added a simple dropdown menu => this issue.
Since my issue occurs on both actual board and on simulator, I focused on debugging with simulator.
Simulator adds a huge amount of powerful tools.
I’m a unit test psychopath, so I wrote a unit test for my issue.
The scenario is:
1/ get the pixel color where the dropdown menu list should pop
2/ open dropdown menu (finger touch & release)
3/ select item #2 (finger touch & release)
4/ get the same pixel and assert it has the same color (screen refreshed)
I noticed the dropdown menu list should close when user removes his finger (RELEASE) from the wanted item, so I start to debug when I ask the touch screen simulator to remove the finger from the selected item.
Also I updated my LCD simulator, and it’s now saving its internal pixels buffer after each selected area memory write complete. It adds some red dots around the selected pixel area so I can see which pixel area was written.
My unit test:
TEST(test_lvgl_dropdown)
{
test_helper_console_enter_string("trace app.lvgl debug");
test_core_run_ms(50U);
uint8_t red1, green1, blue1;
stub_lcd_get_pixel(100U, 200U, &red1, &green1, &blue1);
test_info("TEST", "red=0x%02X green=0x%02X blue=0x%02X",
(unsigned)red1,
(unsigned)green1,
(unsigned)blue1);
/* Put finger on screen on dropdown menu */
stub_touch_press(0U, 80U, 140U);
test_core_run_ms(250U);
/* Remove finger, should open dropdown menu */
stub_touch_release(0U);
test_core_run_ms(250U);
/* Put finger on screen, on 2nd item */
stub_touch_press(0U, 80U, 210U);
test_core_run_ms(250U);
if( test_debugger_is_attached() )
test_debugger_trigger_breakpoint();
/* Remove finger from 2nd line */
stub_touch_release(0U);
test_core_run_ms(250U);
uint8_t red2, green2, blue2;
stub_lcd_get_pixel(100U, 200U, &red2, &green2, &blue2);
test_info("TEST", "red=0x%02X green=0x%02X blue=0x%02X",
(unsigned)red2,
(unsigned)green2,
(unsigned)blue2);
CU_ASSERT(red1 == red2 );
CU_ASSERT(green1 == green2);
CU_ASSERT(blue1 == blue2 );
}
Screenshot and/or video
Above unit test makes LVGL perform 11 flush requests.
Touching screen on dropdown menu:
Steps 1 to 5 : finger is touching the screen
Steps 6 to 9 : finger has been removed from screen
Touching 2 item in menu list
Step 10 : finger is touching item #2 on list (Banana)
Step 11 : finger has been removed from screen
Selected item (Banana) is visible on dropdown box.
But there is no refresh of the 3-items list.
I expect this area to be invalidated & updated on the LCD.
Code to reproduce
Here are extracts of my code…
Global variables
static struct {
struct debug_t dbg;
struct ticks_t timer;
struct touch_gesture_t gesture, gesture_old;
lv_display_t* disp;
lv_indev_t* indev;
uint16_t buffer1[DDM_COMPONENT_CUSTOMIZE_LCD_WIDTH * DDM_COMPONENT_CUSTOMIZE_LCD_HEIGHT];
uint16_t buffer2[DDM_COMPONENT_CUSTOMIZE_LCD_WIDTH * DDM_COMPONENT_CUSTOMIZE_LCD_HEIGHT];
} ctxt;
LVGL initialization
static void lvgl_init(void)
{
lv_obj_t* screen;
lv_obj_t* widget;
/* Default state */
ticks_schedule(&ctxt.timer, 0U);
ctxt.gesture_old.what = TOUCH_GESTURE_TYPE_NONE;
/* Initialize LVGL */
lv_init();
ctxt.disp = lv_display_create(DDM_COMPONENT_CUSTOMIZE_LCD_WIDTH, DDM_COMPONENT_CUSTOMIZE_LCD_HEIGHT);
lv_display_set_color_format(ctxt.disp, LV_COLOR_FORMAT_RGB565);
lv_display_set_flush_cb(ctxt.disp, lvgl_on_flush_start);
lv_display_set_buffers(ctxt.disp, &ctxt.buffer1[0], &ctxt.buffer2[0], sizeof(ctxt.buffer1), LV_DISPLAY_RENDER_MODE_PARTIAL);
/* Create input device */
ctxt.indev = lv_indev_create();
lv_indev_set_type(ctxt.indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_mode(ctxt.indev, LV_INDEV_MODE_EVENT);
lv_indev_set_read_cb(ctxt.indev, lvgl_on_read_input);
/* Set custom 1ms ticks handler */
ticks_set_handler(lvgl_on_ticks);
/* Get active screen, needed for widgets creation */
screen = lv_screen_active();
/* Create a drop-down menu */
widget = lv_dropdown_create(screen);
lv_dropdown_set_options(widget, "Apple\n"
"Banana\n"
"Orange");
lv_obj_set_pos(widget, 10, 120);
}
Flush callback function
My internal functions lcd_select() and lcd_copy() are async and only prepare the job.
LCD actual update is performed when lcd_run() is called.
test_core_yield() is an injected test function required here to let my simulator lives.
Usually it’s not required for simulator code to be such intrusive in firmware code, but it helps a lot here to be minimalist.
static void lvgl_on_flush_start(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map)
{
debug(&ctxt.dbg, "LVGL: redraw (%"PRIi32",%"PRIi32") -> (%"PRIi32",%"PRIi32")",
area->x1, area->y1,
area->x2, area->y2);
uint16_t w = area->x2 - area->x1 + 1U;
uint16_t h = area->y2 - area->y1 + 1U;
uint32_t count = (uint32_t)w * (uint32_t)h;
lv_draw_sw_rgb565_swap(px_map, count);
lcd_select(area->x1, area->y1, w, h);
lcd_copy(px_map, count, 0U/*gap*/, true/*restart*/);
while( !lcd_is_ready() )
{
#ifndef PLATFORM_target
test_core_yield();
#endif
lcd_run();
}
lv_display_flush_ready(ctxt.disp);
}
Input device reading
static void lvgl_on_read_input(lv_indev_t* indev, lv_indev_data_t* data)
{
switch( ctxt.gesture.what )
{
case TOUCH_GESTURE_TYPE_PRESS:
case TOUCH_GESTURE_TYPE_MOVE:
data->point.x = ctxt.gesture.pos.x;
data->point.y = ctxt.gesture.pos.y;
data->state = LV_INDEV_STATE_PRESSED;
break;
case TOUCH_GESTURE_TYPE_RELEASE:
default:
data->state = LV_INDEV_STATE_RELEASED;
break;
}
}
Ticks handler
This function is called from SysTicks IRQ every millisecond
static void lvgl_on_ticks(void)
{
lv_tick_inc(1U);
}
Internal function to evaluate if two events reported by touch screen controller are the same.
When a finger is detected, the touch controller on my board triggers lot of redundants events at ~60Hz which is useless for LVGL
static bool is_gesture_change(const struct touch_gesture_t* g1, const struct touch_gesture_t* g2)
{
return g1->what != g2->what
|| g1->pos.x != g2->pos.x
|| g1->pos.y != g2->pos.y;
}
Run function
This function is called from main() as often as possible
static void lvgl_run(void)
{
if( ticks_is_elapsed(&ctxt.timer) )
{
uint32_t next = lv_timer_handler();
ticks_schedule(&ctxt.timer, next);
}
if( touch_pop_event(&ctxt.gesture) && is_gesture_change(&ctxt.gesture, &ctxt.gesture_old) )
{
char buf[64];
debug(&ctxt.dbg, "LVGL: %s", touch_gesture_to_string(&ctxt.gesture, &buf[0], sizeof(buf)));
lv_indev_read(ctxt.indev);
ctxt.gesture_old = ctxt.gesture;
}
lv_task_handler();
}
Firmware log output
I dump flush requests and touch screen events:
Value in first column is a timestamp (unit is second).
root@dev:/ # trace app.lvgl debug
app.lvgl: debug
root@dev:/ #
[2.050] LVGL: press at (80,140)
[2.050] LVGL: redraw (10,120) -> (139,155)
[2.083] LVGL: redraw (10,120) -> (139,155)
[2.116] LVGL: redraw (10,120) -> (139,155)
[2.149] LVGL: redraw (10,120) -> (139,155)
[2.182] LVGL: redraw (10,120) -> (139,155)
[2.300] LVGL: release
[2.300] LVGL: redraw (10,120) -> (139,155)
[2.300] LVGL: redraw (0,0) -> (129,129)
[2.300] LVGL: redraw (10,156) -> (139,265)
[2.399] LVGL: redraw (10,120) -> (139,155)
[2.432] LVGL: redraw (10,120) -> (139,155)
[2.465] LVGL: redraw (10,120) -> (139,155)
[2.550] LVGL: press at (80,210)
[2.550] LVGL: redraw (10,156) -> (139,265)
[2.800] LVGL: release
[2.800] LVGL: redraw (10,156) -> (139,265)
[2.800] LVGL: redraw (10,120) -> (139,155)
Extract of simulator log output.
Probably not very usefull.
You can see commands received by the LCD & touch screen simulators
CASET = Column Address Set (1st command to select pixel area)
RASET = Row Address Set (2nd command to select pixel area)
[2.05000] TEST: red=0xF0 green=0xF4 blue=0xF0
[2.05000] FT6336G: point #0 pressed at (80,140) ; reporting to X1H
[2.05030] LCD: ST7796S cmd `CASET'
[2.05036] LCD: ST7796S SELECT from=(10,0) size=(130,480)
[2.05036] LCD: ST7796S cmd `RASET'
[2.05044] LCD: ST7796S SELECT from=(10,120) size=(130,36)
[2.05046] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.05046] LCD: saving `output/LCD/host/version_test/bmp/001-internal_010-120_130x36.bmp' 320x480
[2.08304] LCD: ST7796S cmd `CASET'
[2.08310] LCD: ST7796S cmd `RASET'
[2.08320] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.08320] LCD: saving `output/LCD/host/version_test/bmp/002-internal_010-120_130x36.bmp' 320x480
[2.11604] LCD: ST7796S cmd `CASET'
[2.11610] LCD: ST7796S cmd `RASET'
[2.11620] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.11620] LCD: saving `output/LCD/host/version_test/bmp/003-internal_010-120_130x36.bmp' 320x480
[2.14904] LCD: ST7796S cmd `CASET'
[2.14910] LCD: ST7796S cmd `RASET'
[2.14920] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.14920] LCD: saving `output/LCD/host/version_test/bmp/004-internal_010-120_130x36.bmp' 320x480
[2.18204] LCD: ST7796S cmd `CASET'
[2.18210] LCD: ST7796S cmd `RASET'
[2.18220] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.18220] LCD: saving `output/LCD/host/version_test/bmp/005-internal_010-120_130x36.bmp' 320x480
[2.30000] FT6336G: point #0 released
[2.30018] LCD: ST7796S cmd `CASET'
[2.30024] LCD: ST7796S cmd `RASET'
[2.30032] LCD: ST7796S SELECT from=(10,156) size=(130,110)
[2.30034] LCD: ST7796S GRAM from=(10x156) size=(130x110) area 100% written
[2.30034] LCD: saving `output/LCD/host/version_test/bmp/006-internal_010-156_130x110.bmp' 320x480
[2.39905] LCD: ST7796S cmd `CASET'
[2.39911] LCD: ST7796S cmd `RASET'
[2.39919] LCD: ST7796S SELECT from=(10,120) size=(130,36)
[2.39921] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.39921] LCD: saving `output/LCD/host/version_test/bmp/007-internal_010-120_130x36.bmp' 320x480
[2.43205] LCD: ST7796S cmd `CASET'
[2.43211] LCD: ST7796S cmd `RASET'
[2.43221] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.43221] LCD: saving `output/LCD/host/version_test/bmp/008-internal_010-120_130x36.bmp' 320x480
[2.46505] LCD: ST7796S cmd `CASET'
[2.46511] LCD: ST7796S cmd `RASET'
[2.46521] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.46521] LCD: saving `output/LCD/host/version_test/bmp/009-internal_010-120_130x36.bmp' 320x480
[2.55000] FT6336G: point #0 pressed at (80,210) ; reporting to X1H
[2.55030] LCD: ST7796S cmd `CASET'
[2.55036] LCD: ST7796S cmd `RASET'
[2.55044] LCD: ST7796S SELECT from=(10,156) size=(130,110)
[2.55046] LCD: ST7796S GRAM from=(10x156) size=(130x110) area 100% written
[2.55046] LCD: saving `output/LCD/host/version_test/bmp/010-internal_010-156_130x110.bmp' 320x480
[2.80000] FT6336G: point #0 released
[2.80018] LCD: ST7796S cmd `CASET'
[2.80024] LCD: ST7796S cmd `RASET'
[2.80032] LCD: ST7796S SELECT from=(10,120) size=(130,36)
[2.80034] LCD: ST7796S GRAM from=(10x120) size=(130x36) area 100% written
[2.80034] LCD: saving `output/LCD/host/version_test/bmp/011-internal_010-120_130x36.bmp' 320x480
[3.05000] TEST: red=0xD8 green=0xDC blue=0xD8
Deep debugging
When running unit test with debugger, I can trace what is happening when touch screen controller reports “finger removed” when finger was on 2nd dropdown list item.
I have traced up to finding invalidating for area (10,156) → (139,265) which is the area of the list with the 3 items.
After that, I’m lost. I don’t have enough LVGL knowledge to figure out why this invalidated area is not flushed to LCD as it is supposed to be.
Call stack:
lv_indev_read
indev_pointer_proc
indev_proc_release
send_event(LV_EVENT_RELEASED
lv_indev_send_event
lv_obj_send_event
event_send_core
lv_obj_event_base
lv_dropdown_list_event LV_EVENT_RELEASED
lv_obj_event_base
lv_obj_event
lv_obj_remove_state
update_obj_state lv_dropdownlist_class 0x30 -> 0x10
cmp_res = LV_STYLE_STATE_CMP_DIFF_REDRAW
lv_obj_invalidate lv_dropdownlist_class lv_obj.c:923
lv_obj_invalidate_area lv_dropdownlist_class {x1 = 10, y1 = 156, x2 = 139, y2 = 265}
lv_inv_area
It’s very easy to reproduce this issue, so I’m able to perform any other required test / change / debugging.
Thanks for your help.
Best regards,
Fabrice