Multi-tap keypad support?

Hi all,
I have been searching for a bit but did not find anything related, neither here nor in the github repo. Is there any driver that supports multi-tap keypads?

If not how could one implement such indev device? Would be feasible from the driver get the associated group, get the focused object, check if it belongs to textarea class, cast it, and then edit it through lv_textarea_* methods updating the last char accordingly to the multiple taps in the same key?

Thanks.

As I know - there are no built in support of multi-tap.

But you can make it by yourself.Make a CUSTOM keyboard layout like NUMERIC LAYOUT and hook KEY_PRESS event to you handle where you can put key code to temp variable and start lv_timer to countdown timeout to real keypress. If input repeated while timer counting down - put next key code to temp variable and reset timer timeout. When timer will end counting - do send REAL key event to recepient from keyboard with keycode from temp variable.

This in not hard.

Thanks. Your suggestion would work but it does not provide instant feedback for the user. The user would have no way to know the currently select char.

I will try to implement my idea and see how it performs. I’ll update you all when it’s done or if any problem arises.

you can make “hint” with current KEY over keyboard. and update KEY in hint on every press. after timer has counted hide this hint.

I think there was a slight misunderstanding that I didn’t realize at the time. I think your proposed solution was for a drawn, in-screen, keypad. While what I have is a physical keypad. Irregardless this is what I came up with and works well enough for my needs:

static void process_multitap(evdev_drv_state_t *state, lv_indev_drv_t *drv,
                        lv_indev_data_t *data, lv_indev_t *desc,
                        struct input_event *in)
{
    if (in->value == 0) return;

    if (lv_tick_elaps(state->timestamp_pr) > tap_timeout) {
        state->pressed_streak = 0;
    } else {
        if (state->key_raw == in->code) {
            state->pressed_streak++;
        } else {
            state->pressed_streak = 0;
        }
    }
    state->key_raw = in->code;
    state->timestamp_pr = lv_tick_get();

    lv_group_t *g = desc->group;
    if (g == NULL) return;

    lv_obj_t *focus_obj = lv_group_get_focused(g);
    if(focus_obj == NULL) return;

    if(!lv_obj_check_type(focus_obj, &lv_textarea_class)) return;

    if (state->pressed_streak != 0) {
        lv_textarea_del_char(focus_obj);
    }
    lv_textarea_add_char(focus_obj, get_key_from_map(key_maps[0], in->code, state->pressed_streak));

    return;
}

Hi @MiguelHorta
I am searching about this topic too. I have 3x4 matrix keypad, and I want to enable the user to enter alpha-numeric values. How can I apply your solution? The keypad_read function doesn’t have access to the text area object.

Hi @bader ,
the solution I posted is a bit outdated, once I gained a bit of familiarity with LVGL I realized my solution bypassed the event system, I had to redo it. I’m stuck with evdev, but it should work fine with libinput.

This is my current evdev file,
evdev.c (6.7 KB)

And it is used like this,

static evdev_drv_state_t kp_state;
static lv_indev_drv_t kp_driver;
static lv_indev_t  *kp_desc;

static void my_kp_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    evdev_read(&kp_state, indev_drv, kp_desc, data);
}
int main() {
...
    evdev_init_keypad(&kp_state, "/dev/keypad");
    lv_indev_drv_init(&kp_driver);
    kp_driver.type = LV_INDEV_TYPE_KEYPAD;
    kp_driver.read_cb = my_kp_read;
    kp_desc = lv_indev_drv_register(&kp_driver);
...
}

P.S:

typedef struct multitap_keymap
{
   char key;
   char *sequence;
} multitap_keymap_t;

static multitap_keymap_t key_maps[INPUT_KEYBOARD_MAX_KEY_MAP][12] = {
    { /* 0 - NUMERIC */
        {   PKEY_1,      "1" },
        {   PKEY_2,      "2" },
        {   PKEY_3,      "3" },
        {   PKEY_4,      "4" },
        {   PKEY_5,      "5" },
        {   PKEY_6,      "6" },
        {   PKEY_7,      "7" },
        {   PKEY_8,      "8" },
        {   PKEY_9,      "9" },
        {   PKEY_0,      "0" },
        {   PKEY_UNDEFINED, NULL},
        {   PKEY_UNDEFINED, NULL},
    },
    {  /* 1 - ALPHANUMERIC */
        {   PKEY_1,      "1." },
        {   PKEY_2,      "2ABCabc" },
        {   PKEY_3,      "3DEFdef" },
        {   PKEY_4,      "4GHIghi" },
        {   PKEY_5,      "5JKLjkl" },
        {   PKEY_6,      "6MNOmno" },
        {   PKEY_7,      "7PQRSpqrs" },
        {   PKEY_8,      "8TUVtuv" },
        {   PKEY_9,      "9WXYZwxyz" },
        {   PKEY_0,      "0 " },
        {   PKEY_UNDEFINED, NULL},
        {   PKEY_UNDEFINED, NULL},
    },
...
}
1 Like

Thank you Miguell. It was so helpful to me. I think you are using linux driver (evdev) while I am trying to make it work on bare-metal platform. At the end, I successfully did it, but with some modifications to bypassed the evdev drivers. I guess I will redo it with a clean way.

Thank you again.

Hello All,
Finally, here is the modified version of your code to achieve this without evdev driver and it works with me thanks to you MiguelHorta. I think it needs a lot of enhancments, any suggestions?


#define rows 4
#define cols 4

uint8_t colPins[4] = {KEYPAD_COL1, KEYPAD_COL2, KEYPAD_COL3, KEYPAD_COL4};
uint8_t rowPins[4] = {KEYPAD_ROW1, KEYPAD_ROW2, KEYPAD_ROW3, KEYPAD_ROW4};

const char keys[rows][cols] = {{'1', '2', '3', 'A'},
                               {'4', '5', '6', 'B'},
                               {'7', '8', '9', 'C'},
                               {'0', 'F', 'E', 'D'}};


char alphanumericKeypad[10][10] = {
    {"20 "},
    {"21."},
    {"72ABCabc"},
    {"73DEFdef"},
    {"74GHIghi"},
    {"75JKLjkl"},
    {"76MNOmno"},
    {"97PQRSpqrs"},
    {"78TUVtuv"},
    {"99WXYZwxyz"}
};

typedef struct
{
    unsigned int timestamp_pr;
    unsigned int pressed_streak;
    uint8_t key_raw;
} keypad_state_t;

uint8_t *_keystates;
keypad_state_t kb_state;
static int tap_timeout = 900; // 3s

int handle_special_keys(keypad_state_t* state, uint32_t act_key)
{

    int result = 0;
    /*Translate the keys to LVGL control characters according to your key definitions*/
    switch (act_key){
        case 'F':
            result = LV_KEY_NEXT;
			break;
        case '0':
            result = LV_KEY_PREV; 
			break;        
        case 'E':
            result = LV_KEY_LEFT;
			break;       
        case 'D':
            result = LV_KEY_RIGHT;
			break;
        case 'C':
            result = LV_KEY_ENTER;
			break;
        case 'B':
            result = LV_KEY_BACKSPACE;
			break;
        default:
            result = 0;
			break;
    }

    if(result){
        state->pressed_streak = 0;
        state->key_raw = 0;
    }
	
    return result;

}

static char get_key_from_map(uint32_t act_key, uint32_t pcount){
	
	// Convert ASCII number to hex
	uint8_t key = ((uint8_t) act_key) - 0x30;
	
	// Retrieve First Number  
	uint8_t numberOFKeyOptions = numberOFKeyOptions = ((uint8_t) alphanumericKeypad[key][0]) - 0x30;
        return alphanumericKeypad[key][(pcount%(numberOFKeyOptions))+1];
}


// This code has been written by @MiguelHorta
static int process_multitap(lv_indev_drv_t *drv, uint32_t act_key, keypad_state_t* state){

    if (lv_tick_elaps(state->timestamp_pr) > tap_timeout) {
	    state->pressed_streak = 0;
    } else {
        if (state->key_raw == act_key) {
            state->pressed_streak++;
        } else { 
            state->pressed_streak = 0;
        }
    }
	
    state->key_raw = act_key;

    lv_obj_t* focus_object;
    if (kb_state.pressed_streak != 0) {
		focus_object = lv_group_get_focused(lv_group_get_default());
		if(lv_obj_check_type(focus_object, &lv_textarea_class)){
			lv_textarea_del_char(focus_object);
		}
    }
    state->timestamp_pr = lv_tick_get();

    return get_key_from_map(act_key, state->pressed_streak);
}


void keypad_init(){
    // Array holds the pins state
    _keystates = (uint8_t *)malloc(rows * cols * sizeof(uint8_t));
    memset((void *)_keystates, 0, rows * cols);
    kb_state.key_raw = 0;
    kb_state.timestamp_pr = 0;
    kb_state.pressed_streak = 0;
}

// This code from adafruit_keypad Library 
char getKey(){
    for (int i = 0; i < 4; i++){
        gpio_set_pin_level(colPins[i], 1);
    }

    for (int c = 0; c < 4; c++){
        gpio_set_pin_level(colPins[c], 0);
        delay_ms(20);

        int i;
        for (int r = 0; r < 4; r++){
            bool pressed = !gpio_get_pin_level(rowPins[r]);
            i = r * cols + c;
            uint8_t *currState = (_keystates + i);

            if (pressed && !(*currState & 1 << 2)){
                *currState |= 1 << 2;
                printf("Pressed\r\n");
            }else if (!pressed && (*currState & 1 << 2)){
                *currState |= 1 << 3;
                *currState &= ~(1 << 2);
                printf("Released %c\r\n", keys[r][c]);
                return keys[r][c];
            }
        }
        gpio_set_pin_level(colPins[c], 1);
    }

    return 0;
}


void keypad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data){

    static uint32_t last_key = 0;

    uint32_t act_key = getKey();
    uint32_t act_key1 = act_key;
    if (act_key != 0){
        data->state = LV_INDEV_STATE_PR;

        act_key = handle_special_keys(&kb_state, act_key);
        if(act_key == 0){
            act_key = process_multitap(indev_drv, act_key1, &kb_state);
        }
		
        last_key = act_key;
    }else{
        data->state = LV_INDEV_STATE_REL;
    }

    data->key = last_key;
}

2 Likes