No-Touch GUI with 3 Buttons on ESP32 with ST7789 240x240 display


Hello Gentlemen. I am looking to make a gui for an ESP32 powered device with a small 240x240 IPS screen with a ST7789 driver without touchscreen, but 3 buttons vertically right next to the screen. A classic configuration that I have hope will work well with what LVGL provides, once I have managed to understand this Library.

Where am I

I have managed to set everything up to compile and run fine on VSCode/PlatformIO. I have basic stuff displayed, input configured but not working as expected. I am chewing myself through the documentation, but feel like I am stuck on a few basics and architectural questions I am hoping to find help in here.

What I’d like to achive

As said earlier I have 3 vertical buttons which would make Sense to be Up/Enter/Down.

I’d like to have a number of different main screens/pages that display information without user input, and there is one more configuration screen that accepts user input to change settings. So when not on the settings screen up/down should scroll through the main pages, and once at the settings page, scroll through the individual settings which can be changed by pressing enter and change value with up/down. After the last setting start again from the first page, or back the other way around. The settings page itself also should have several submenus.

Additionally there needs to be a title bar with battery status, RSSI etc.

What have got so far

I have managed to get LVGL display things on my LCD, I guess that’s a good first step. I also manage to add Items to the screen, I am pretty sure I will manage to configure them, but I am struggling to get input to work the way I’d like to. So far I have the first object on my screen with the orange focus indicator. Up, down and enter keys all set and reset focus of the first element but would not move focus to the next or previous input. I am also not able to change the selected input.

sample code

I have set up and input device as an encoder and added it to a single group:

lv_indev_drv_t      indev_ButtonsDriver;
indev_ButtonsDriver.type = LV_INDEV_TYPE_ENCODER;
indev_ButtonsDriver.read_cb = readButtons;
indev_ButtonsEncoder = lv_indev_drv_register(&indev_ButtonsDriver);
lv_indev_set_group(indev_ButtonsEncoder, displayGroup);

and us the readButtons callback to change the button state:

switch (btnUpState)
        case BTN_STATE_CLICKED:
            data->state = LV_INDEV_STATE_PR;
            //data->key = LV_KEY_LEFT;
            data->key = LV_KEY_UP;
            btnUpState = BTN_STATE_REL;
            Serial.println("up clicked");

Same code for LV_KEY_DOWN and LV_KEY_ENTER as suggested in the docs. As you can Sey I have also tried LV_KEY_LEFTand LV_KEY_RIGHT. I am unsure if this is the correct way to do this. Unfortunately I was unable to find any tutorial that does the type of input I am trying to do, only the statements in the manual that it is well possible.

Then I have set up the guy itself like this:

static lv_style_t win_style;
lv_style_copy(&win_style, &lv_style_plain);

guiWindow = lv_win_create(scr, NULL);
lv_win_set_title(guiWindow, "Title Version V0.01a");
lv_win_set_btn_size(guiWindow, 20);

lv_page_set_scrl_layout(lv_win_get_content(guiWindow), LV_LAYOUT_OFF);
lv_win_set_style(guiWindow, LV_WIN_STYLE_BG, &lv_style_plain);
lv_win_set_style(guiWindow, LV_WIN_STYLE_CONTENT, &lv_style_plain);
//lv_group_add_obj(displayGroup, lv_win_get_content(guiWindow));     // with this line uncommented focus is on the window and wont change

// Battery Indicator
lv_obj_t * batteryButton = lv_win_add_btn(guiWindow, LV_SYMBOL_BATTERY_FULL);
lv_obj_set_protect(batteryButton, LV_PROTECT_CLICK_FOCUS);
const lv_style_t * btn_style = lv_btn_get_style(batteryButton, LV_BTN_STYLE_REL);

// A Container 
lv_obj_t * cont_A;

cont_A = lv_cont_create(guiWindow, NULL);
lv_obj_set_auto_realign(cont_A, true);                    // Auto realign when the size changes
lv_obj_align_origo(cont_A, NULL, LV_ALIGN_CENTER, 0, 0);  // This parametrs will be sued when realigned
lv_cont_set_fit(cont_A, LV_FIT_TIGHT);
lv_cont_set_layout(cont_A, LV_LAYOUT_COL_M);
lv_cont_set_style(cont_A, LV_CONT_STYLE_MAIN, &lv_style_plain);

// Settings 
lv_obj_t *roller1 = lv_roller_create(cont_A, NULL);

lv_roller_set_visible_row_count(roller1, 1);
lv_obj_align(roller1, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style(roller1, &lv_style_plain);
lv_group_add_obj(displayGroup, roller1);
//lv_obj_set_event_cb(roller1, event_handler);

lv_obj_t *label;
label = lv_label_create(cont_A, NULL);
lv_label_set_text(label, "Short text");
lv_group_add_obj(displayGroup, label);

label = lv_label_create(cont_A, NULL);
lv_label_set_text(label, "It is a long text");
lv_group_add_obj(displayGroup, label);

lv_obj_t* cb = lv_cb_create(cont_A, NULL);     // First check box
lv_cb_set_text(cb, "Red");
lv_group_add_obj(displayGroup, cb);

The Problem summarized

I have set up Input to send KEY_UP/DOWN or LEFT/RIGHT and ENTER. I have created a group, assigned both input and elements to the group. I have the orange focus indicator on the first element. but it would not move to the other elements, also would not change the value of the selected first element. If I press any of the 3 buttons the element would loose and gain the orange focus indicator

I have yet to understand …

how I would lay out the menu structure after I got my input problem fixed. How would I set up 3 or more scrolling pages with no inner focus, plus one last page that contains individual settings to be changed?

I appreciate your guys input!


After a bunch of headache I have managed to get my code cycle through the group.
I had a look at how mouse_wheel.c does it, and to my surprise it did not send a key, just the state LV_INDEV_STATE_PR plus data->enc_diff. Increasing/decreasing enc_diff and only sending a data->state = LV_INDEV_STATE_PR solved my input problem.

however, other problems emerge now of course. For instance if I use a Spinbox to change a value, i can skip through it. I can select it. I can change the first value, I can switch and change to the other values, however I can not get out of the selection anymore. The user is stuck changing the spinbox value.

Any Idea of how to solve this?

My personal idea would have been to make a long press on the center button act like an ESC key. But it seems this does not work with the encoder Type. Does the encoder Type only support state and enc_div?

I am still struggling with this problem, and actually found it is also apparent in the touchpadless demo, if you come to the field (using mouse wheel) with the long scrolling text, you will be able to enter the text, and scroll it. But you wont be able to get out of it anymore.

Any suggestions on how to solve this?

Sorry to post a 4th time on my own thread, but is there nobody around who has experience dealing with this kind of gui setup?

I would have answered you 3 days ago but I didn’t know the answer to your question.

Hold the mouse wheel down once you are inside the text box.

About enc_diff

Have you seen this part of the documentation? It shows that enc_diff should be used. Let me know, if it’s not straightforward enough. I’d kindly improve it if necessary.

About escaping from edit mode.

As @embeddedt pointed out you should be able to escape with long press.

It keeps fascinating me how I frequently get stuck with simple things while I have little trouble getting behind the more complex tasks. Both your short and spot on replies just saved me a whopping 100 lines of input handling code that I did not like at all in the first place, <20 lines are enough it seems.

thank you! Much appreciated! :pray:

Also on this note, great work on this library gabor! I managed to learn and implement almost all UI functionality on my first embedded project in just a few days. It’s not pretty yet, but it is closer to pretty than I was hoping, and it’s going to be rather easy to prettify. :rocket:

1 Like

Glad to hear that! :slight_smile: However, it’s not only my merit!