LVGL With external keypad


I want to use an external keypad since the present project I am working on doesn’t have a touchscreen

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


What LVGL version are you using?


What have you tried so far?

Here I have found this documentation, but I couldn’t find any sample project, since all of them are based on a touch-screen rather external keypad or keyboard.

What Is my goal?

I want to send the key obtained from the keyboard to the events on the screen object. I have directions, to move between widgets, and numbers to be entered in textareas. Is there any example code available?
I don’t want to use the built-in keyboard widget of LVGL.

you need to register read_cb function with LV_INDEV_TYPE_KEYPAD and thats all. To naviagte between widgets on the screen you need to add them to groups + your keyboard should have left/right/up/down/next/prev keys (or just read_cb() need to retrun LV_KEY_LEFT/_RIGHT/… - codes). add event callbacks to your widgets. thats all.

lvgl\demos\keypad_encoder have example which should work :slight_smile:

dear @glory-man
the example you mentioned doesnt show how to send the key,
for example, consider that I have registered the callback, and I have tested it and it is running in an ok manner. I get the key from key-pad, and that is also ok, I don’t know what to do from here, how to send the obtained key from key-pad to UI, in the case of touchscreen which I have used before, and that was ok, I set the x y position of the touched point, and then sent it like this

    data->point.x = touchpad_x;
    data->point.y = touchpad_y;
    data->state = LV_INDEV_STATE_PRESSED;

but how should it be in case of key-pad?

as mentioned in docs example

indev_drv.type = LV_INDEV_TYPE_KEYPAD;
indev_drv.read_cb = keyboard_read;


void keyboard_read(lv_indev_drv_t * drv, lv_indev_data_t*data){
  data->key = last_key();            /*Get the last pressed or released key*/

  if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
  else data->state = LV_INDEV_STATE_RELEASED;

get pressed key code and put it in data->key

Ok, I have tested these codes, but they doesn’t work on Version 8.4. for example if I have 3 labels with 3 text area, If I send a down key or next key, then non of them get selected, or non of the textarea cursor would start to blink

did you add your objects into a group?

How should I do that? Consider that I have multiple pages.

as mentioned in docs
lv_group_create(), lv_group_add_obj(), lv_indev_set_group()

In the gif on the Main website, I see some changes while the keyboard doesn’t show, How can I have that behaviour?

what gif do you mean?

This one

Let us have a step-by-step walkthrough,
First, one creates a UI with 3 labels and 3 text areas, since labels are not going to change by input device, they are not added to the group.
Second, the programmer should make a group.
Third the programmer should add that three text area to the group.
Fourth, the programmer should use lv_indev_set_group function with registered input device.
Now what is next?
Two scenarios,

  1. input is a character
  2. input is up and down
  1. Any character should add the character to the current cursor position in focused textarea
  2. LV_KEY_UP/DOWN/LEFT/RIGHT should move the cursor in focused area
    LV_KEY_NEXT/PREV should focus on the next/previous object (textarea in your case)

For the case 2:
Does the library change the focus automatically or should I write any code for example any event or gesture code? In the case of prev or next message

as far as i undertand library change focus itself

So what does this part of the example code say?

static void ta_event_cb(lv_event_t * e)
    lv_indev_t * indev = lv_indev_get_act();
    if(indev == NULL) return;
    lv_indev_type_t indev_type = lv_indev_get_type(indev);

    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * ta = lv_event_get_target(e);
    lv_obj_t * kb = lv_event_get_user_data(e);

    if(code == LV_EVENT_CLICKED && indev_type == LV_INDEV_TYPE_ENCODER) {
        lv_keyboard_set_textarea(kb, ta);
        lv_obj_clear_flag(kb, LV_OBJ_FLAG_HIDDEN);
        lv_group_set_editing(lv_obj_get_group(kb), kb);
        lv_obj_set_height(tv, LV_VER_RES / 2);
        lv_obj_align(kb, LV_ALIGN_BOTTOM_MID, 0, 0);

    if(code == LV_EVENT_READY || code == LV_EVENT_CANCEL) {
        lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
        lv_obj_set_height(tv, LV_VER_RES);

@kisvegabor Would you please share your idea about this question?

It works as @glory-man described.

  1. Create a group. lv_group_t * g = lv_group_create();
  2. Create the text areas and add the to the group: lv_group_add_obj(g, ta);
  3. In the read_cb set
data->key = my_read_key(); 

You can use the special keys as described here.

when user clicked on textarea with encoder this code will show keyboard to enter text into it, and hides keyboard when all done.

Now focus change in my side is ok, but I have another problem, dont know how to send pressed numerical key to selected text area. here is my code

void ta_key_event(lv_event_t * e)
	lv_obj_t * ta = lv_event_get_target(e);
	uint8_t key = lv_indev_get_key(lv_indev_get_act()) - 160;
	lv_textarea_add_char(ta, key);
	if (ta == window_Login.textareaPassword)
	else if (ta == window_Login.textareaNewPassword)
	else if (ta == window_Login.textareaOldPassword)

extern lv_indev_t * keypad_indev;

void createWindowLogin(void)

	window_Login.boxLogin = lv_obj_create(window_Login.mainWindow.screen);
	lv_obj_set_size(window_Login.boxLogin, 267, 80);
	lv_obj_set_pos(window_Login.boxLogin, 267, 110);
	lv_obj_set_style_pad_left(window_Login.boxLogin, 0, LV_PART_MAIN);
	lv_obj_set_style_pad_top(window_Login.boxLogin, 0, LV_PART_MAIN);

	lv_obj_set_style_border_width(window_Login.boxLogin, 1, 0);

    lv_obj_set_scrollbar_mode(window_Login.boxLogin, LV_SCROLLBAR_MODE_OFF);

	window_Login.labelPassword = lv_label_create(window_Login.boxLogin);
	lv_label_set_text(window_Login.labelPassword, "Password:");
	lv_obj_set_pos(window_Login.labelPassword, 10, 30);

	window_Login.textareaPassword    = lv_textarea_create(window_Login.boxLogin);
	lv_obj_set_pos(window_Login.textareaPassword, 100, 20);
	lv_obj_set_size(window_Login.textareaPassword, 140, 40);
	lv_textarea_set_one_line(window_Login.textareaPassword, true);

	window_Login.boxChangePassword = lv_obj_create(window_Login.mainWindow.screen);
	lv_obj_set_size(window_Login.boxChangePassword, 267, 150);
	lv_obj_set_pos(window_Login.boxChangePassword, 267, 250);
	lv_obj_set_style_pad_left(window_Login.boxChangePassword, 0, LV_PART_MAIN);
	lv_obj_set_style_pad_top(window_Login.boxChangePassword, 0, LV_PART_MAIN);

	lv_obj_set_style_border_width(window_Login.boxChangePassword, 1, 0);

    lv_obj_set_scrollbar_mode(window_Login.boxChangePassword, LV_SCROLLBAR_MODE_OFF);

	window_Login.labelNewPassword = lv_label_create(window_Login.boxChangePassword);
	lv_label_set_text(window_Login.labelNewPassword, "New Password:");
	lv_obj_set_pos(window_Login.labelNewPassword, 10, 35);

	window_Login.labelOldPassword = lv_label_create(window_Login.boxChangePassword);
	lv_label_set_text(window_Login.labelOldPassword, "Old Password:");
	lv_obj_set_pos(window_Login.labelOldPassword, 10, 90);

	window_Login.textareaNewPassword = lv_textarea_create(window_Login.boxChangePassword);
	lv_obj_set_pos(window_Login.textareaNewPassword, 150, 25);
	lv_obj_set_size(window_Login.textareaNewPassword, 100, 40);
	lv_textarea_set_one_line(window_Login.textareaNewPassword, true);

	window_Login.textareaOldPassword = lv_textarea_create(window_Login.boxChangePassword);
	lv_obj_set_pos(window_Login.textareaOldPassword, 150, 80);
	lv_obj_set_size(window_Login.textareaOldPassword, 100, 40);
	lv_textarea_set_one_line(window_Login.textareaOldPassword, true);

	window_Login.labelBoxLogin = lv_label_create(window_Login.mainWindow.screen);
	lv_obj_set_pos(window_Login.labelBoxLogin, 280, 100);
	lv_label_set_text(window_Login.labelBoxLogin, "Log in");

	lv_obj_set_style_bg_opa(window_Login.labelBoxLogin,	LV_OPA_COVER, LV_PART_MAIN);

	window_Login.labelBoxChangePassword = lv_label_create(window_Login.mainWindow.screen);
	lv_obj_set_pos(window_Login.labelBoxChangePassword, 280, 240);
	lv_label_set_text(window_Login.labelBoxChangePassword, "Change Password");
	lv_obj_set_style_bg_opa(window_Login.labelBoxChangePassword, LV_OPA_COVER, LV_PART_MAIN);

    lv_obj_add_state(window_Login.textareaPassword, LV_STATE_FOCUSED);

	// Create a group and associate it with kb_indev
	lv_group_t * g = lv_group_create();

	// Now add Objects to the group which should be able to receive the Keyboard Input
	lv_group_add_obj(g, window_Login.textareaPassword);
	lv_group_add_obj(g, window_Login.textareaNewPassword);
	lv_group_add_obj(g, window_Login.textareaOldPassword);

	lv_indev_set_group(keypad_indev, g);

	lv_obj_add_event_cb(window_Login.textareaPassword, ta_key_event, LV_EVENT_KEY, NULL);
	lv_obj_add_event_cb(window_Login.textareaNewPassword, ta_key_event, LV_EVENT_KEY, NULL);
	lv_obj_add_event_cb(window_Login.textareaOldPassword, ta_key_event, LV_EVENT_KEY, NULL);

	activeWindow = &window_Login.mainWindow;


I can see that the selected textarea is correct, but lv_textarea_add_char(ta, key); doesnt work. had I made a mistake? or is something missing?