1. lvgl 输入设备种类

实体按键属于 lvgl 的输入设备中的一种,所以对接外部的硬件实体按键实际上就是为 lvgl 添加输入设备。为 lvgl 添加输入设备需要在 lv_port_indev.c 这个 c 文件中完成,注意这个文件并不存在于 lvgl src 源码文件夹下,而是位于 examples/porting 文件夹下,在这个目录下官方为我们准备好了对接模板文件,我们对这个模板文件进行修改即可

lvgl 的输入设备共有以下五种:
Touchpad 触摸板,例如电容屏、电阻屏等
Mouse 鼠标
Keypad 键盘
Encoder 编码器
Button 外部按键

开发板目前现有的驱动是 ButtonKeypad 这两者其实都是外部或是说显示屏周边的按键,两者的的区别是,Button 属于独立按键而 Keypad 属于矩阵键盘。

2. lvgl 接入 Button 按键

**Button 按键特殊注意点:**在 lvgl 的设计思想上 Button 按键用于在没有触摸屏的情况下,为了点触软件按钮,而设计的与软件按钮对应的周边(比如硬件按钮位于软件按钮正上方,正下方,左方或右方)硬件实体按键。

**Button 按键如何工作呢:**通过 lvgl 的设计可以不难发现 lvgl 并没有把 Button 按键单纯作为一个按键,而是将软件按键一一对应的实体按键点按产生的电平变化转化为在屏幕某一个坐标上的点按,换句话说 lvglButton 按键模拟成了触摸屏,而模拟点按的坐标就是你想控制的那个软件按键在屏幕上显示的中心坐标(注意中心坐标需要我们根据我们要控制的软件按键坐标自行计算好,并提供给 lvgl,我们计算的坐标必须位于软件按键的中心,不然会发现无法控制,软件按键不会随硬件按键按下而跟着按下)。

所以按钮是指屏幕旁边的外部 硬件 按钮,它们被分配给屏幕的特定坐标。如果一个按钮被按下,它将模拟在指定坐标上的按下。(类似于触摸板)将按钮分配给坐标使用
lv_indev_set_button_points(my_indev,points_array)points_array 应该像下面这样:

static const lv_point_t btn_points[4] = {
    {54,  226},
    {124, 226},
    {194, 226},
    {264, 226},
};

提示:软件按键在屏幕上显示的中心坐标怎么计算呢?很简单,例如软件按钮(屏幕上显示的按钮)坐标为 ( x , y ) (x, y) (x,y),按钮的宽为 w w w,高为 h h h,在屏幕上显示的中心坐标为 ( x ′ , y ′ ) (x^{'}, y^{'}) (x,y),那么其中 x ′ = x + w / 2 x^{'} = x + w/2 x=x+w/2 y ′ = y + h / 2 y^{'} = y + h/2 y=y+h/2

2.1 修改输入设备初始化函数

20220109160346.jpg
因为这里需要对接的是 Button 按键(独立按键),所以我们将 lv_port_indev.c 模板文件中
void lv_port_indev_init() 初始化函数与 Button 按键无相关的内容(比如触摸屏,鼠标,编码器等)先注释(或使用条件编译进行隔离编译),留下需要的 Button 按键部分,如下图所示。
20220109160935.jpg

  • button_init(); 这个函数可以用于编写按键相关的硬件 IO 初始化相关代码,如果硬件 IO 初始化已经在专门统一的驱动中已经初始化,这个函数可以不使用,留空或不调用均可。
  • lv_indev_drv_init(&indev_drv); 初始化管理输入设备的结构体。
  • indev_drv.type = LV_INDEV_TYPE_BUTTON; 指定我们使用的输入设备类型。
  • indev_drv.read_cb = button_read; 指定用于读取按键状态的函数。
  • indev_button = lv_indev_drv_register(&indev_drv); 将实体按键注册到 lvgl 中。
  • 将实体按键映射到软件按键所在的屏幕中心坐标,并将坐标指定给 lvgl,注意中心坐标需要我们根据我们要控制的软件按键坐标在这里自行计算好,并提供给 lvgl,我们计算的坐标必须位于软件按键的中心,不然会发现无法控制,软件按键不会随硬件按键按下而跟着按下)具体如下代码块。
static const lv_point_t btn_points[2] = {
    {10, 10},   /*Button 0 -> x:10; y:10*/
    {40, 100},  /*Button 1 -> x:40; y:100*/
};
lv_indev_set_button_points(indev_button, btn_points);

2.2 修改按键状态读取相关函数

/*------------------
 * Button
 * -----------------*/

/*Initialize your buttons*/
static void button_init(void)
{
    /*Your code comes here*/
}

/*Will be called by the library to read the button*/
static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{

    static uint8_t last_btn = 0;

    /*Get the pressed button's ID*/
    int8_t btn_act = button_get_pressed_id();

    if(btn_act >= 0) {
        data->state = LV_INDEV_STATE_PR;
        last_btn = btn_act;
    } else {
        data->state = LV_INDEV_STATE_REL;
    }

    /*Save the last pressed button's ID*/
    data->btn_id = last_btn;
}

/*Get ID  (0, 1, 2 ..) of the pressed button*/
static int8_t button_get_pressed_id(void)
{
    uint8_t i;

    /*Check to buttons see which is being pressed (assume there are 2 buttons)*/
    for(i = 0; i < 2; i++) {
        /*Return the pressed button's ID*/
        if(button_is_pressed(i)) {
            return i;
        }
    }

    /*No button pressed*/
    return -1;
}

/*Test if `id` button is pressed or not*/
static bool button_is_pressed(uint8_t id)
{

    /*Your code comes here*/
// ----------- add by zhbi ------------------
    if (!F1_KEY_STATUS() == 0) {
        return LV_INDEV_STATE_PR;  // 和 LV_INDEV_STATE_PR 对应 
    } else {
        return LV_INDEV_STATE_REL; // 自己添加和 LV_INDEV_STATE_REL 对应
    } 
// ------------------------------------------
    return false;
}

注意这里主要编写完善 static bool button_is_pressed(uint8_t id) 这个判断物理按键是否被按下的函数,这个函数可以通过 id 来区分每一个物理按键,也就是说可以将所有物理按键的读取都放置在这里实现,实现之后 lvgl 可以通过 id 进行逐个读取。
这里需要实现 的就是获取硬件 IO 的电平,并将电平状态对应为按键的按下释放

2.3 创建一个被控按键

lv_obj_t * container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, 320, 240);
lv_obj_center(container);

lv_obj_t * button = lv_btn_create(container);
lv_obj_set_size(button, 20, 20);
lv_obj_set_pos(button, 0, 0);
lv_obj_add_event_cb(button, btn_event_cb_1, LV_EVENT_ALL, NULL);

这样独立的 Button 物理按键就可以控制 lvgl 的软件按键了,lvgl 的软件按键会随着物理按键的按下而按下,并呈现相应的动画显示效果。

3. lvgl 接入 Keypad 键盘

前面已经说到 Keypad 属于矩阵键盘,它与 button 独立按键的区别是可以通过返回不同的键值来区分控制的软件按键(换句话说就是通过不同的键值(事件)来控制不同的软件按键,button 按键只有两个键值(事件)就是 PressedReleased),而不需要像 button 独立按键那样给每个每个物理按键分配控制软件按键在屏幕上显示的中心坐标来区分控制。

3.1 修改输入设备初始化函数

因为这里需要对接的是 keypad(矩阵键盘),所以我们将 lv_port_indev.c 模板文件中
void lv_port_indev_init() 初始化函数与 keypad 无相关的内容(比如触摸屏,鼠标,编码器等)先注释(或使用条件编译进行隔离编译),留下需要的 keypad 部分,如下图所示。
20220109215718.jpg
从图中可以看出 keypad 的初始化与前面讲解的 button 按键的初始化的步骤是相同的,图中指定的读取回调函数 keypad_read 是矩阵键盘键值读取函数,这个函数也是接下来需要进行完善的地方,其他的与 button 均类似这里不再详细说明。

3.2 修改键盘键值读取相关函数

/*------------------
 * Keypad
 * -----------------*/

/*Initialize your keypad*/
static void keypad_init(void)
{
    /*Your code comes here*/
}

/*Will be called by the library to read the mouse*/
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static uint32_t last_key = 0;

    /*Get the current x and y coordinates*/
    mouse_get_xy(&data->point.x, &data->point.y);

    /*Get whether the a key is pressed and save the pressed key*/
    uint32_t act_key = keypad_get_key();
    if(act_key != 0) {
        data->state = LV_INDEV_STATE_PR;

        /*Translate the keys to LVGL control characters according to your key definitions*/
        switch(act_key) {
        case 1:
            act_key = LV_KEY_NEXT;
            break;
        case 2:
            act_key = LV_KEY_PREV;
            break;
        case 3:
            act_key = LV_KEY_LEFT;
            break;
        case 4:
            act_key = LV_KEY_RIGHT;
            break;
        case 5:
            act_key = LV_KEY_ENTER;
            break;
        }

        last_key = act_key;
    } else {
        data->state = LV_INDEV_STATE_REL;
    }

    data->key = last_key;
}

/*Get the currently being pressed key.  0 if no key is pressed*/
static uint32_t keypad_get_key(void)
{
    /*Your code comes here*/

// ---------------------- add by zhbi -------------------
    // 这是我添加获取按键值相关的操作
    if (!F1_KEY_STATUS() == 0) {
        return LV_KEY_PREV;   //和 LV_KEY_NEXT 对应 
    } else if (F2_KEY_STATUS() == 0) {
        return LV_KEY_LEFT;   //自己添加和 LV_KEY_HOME 对应
    } else if (F3_KEY_STATUS() == 0) {
        return LV_KEY_ENTER;   //和 LV_KEY_ENTER 对应 
    } else if (F4_KEY_STATUS() == 0) {
        return LV_KEY_RIGHT;   //和 LV_KEY_ENTER 对应 
    }
// ------------------------------------------------------

    return 0;
}

注意这里主要编写完善 static uint32_t keypad_get_key(void) 这个判断某个物理按键是否被按下的函数,这个函数可以通过读取不同的硬件 IO 来区分每一个物理按键,**这里需要实现的就是获取硬件 IO 的电平,并将电平状态对应为某个按键键值(事件)并将事件返回给 lvgl

3.3 创建被控按键

extern lv_indev_t * indev_keypad;

lv_group_t * group = lv_group_create();
lv_indev_set_group(indev_keypad, group);

lv_obj_t * container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, 320, 240);
lv_obj_center(container);

lv_obj_t * button = lv_btn_create(container);
lv_obj_set_size(button, 60, 35);
// lv_obj_center(button);
lv_obj_set_pos(button, 50, 125);
lv_obj_add_event_cb(button, btn_event_cb_1, LV_EVENT_ALL, NULL);

lv_group_add_obj(group, button);

这里需要使用 lv_group_create() 函数创建一个控件(或者叫做对象)组(group),这是 keypad 矩阵键盘与 button 按键不同的地方,下面详细说明为什么需要创建组。
我们可以试着想象这样的一个场景,当我们的屏幕上存在较多的显示组件需要控制但是具备的物理按键较少时,这时我们就无法使用按键来控制所有的控件修改控件的内容,这时候我们就可以创建一个控件组,并将需要被控制的控件都添加到这个控件组中,lvgl 会在这个控件组中进行控件轮流焦点转换来实现选中不同的控件,这时我们就可以专门使用一个按键进行组中控件的焦点轮流转换,其他按键则用于对控件内容的修改等操作。

3.4 group 的概念

通过上述的对接我们知道 相比 buttonkeypad 比较特殊,光移植完还不行,需要使用的话,还需要 indev group

需要使用按键控制一组对象,需要将要使用键盘或编码器控制的对象添加到组中。在每一组中都有一个被聚焦的对象接收按下的按键产生的事件或编码器产生的动作。例如,如果一个文本区域是集中的,您按键盘上的某个字母,键将被发送并插入到文本区域。类似地,如果一个滑块被聚焦,并且你按下向左或向右的箭头,滑块的值将会改变。这些操作的前提是需要将输入设备与组关联起来。一个输入设备只能将密钥发送给一个组,但是一个组也可以接收来自多个输入设备的数据。要创建组,请使用 lv_group_t * g = lv_group_create(); 创建好组之后向组添加对象,添加对象使用 lv_group_add_obj(g, obj)。使用 lv_indev_set_group(indev, group) 将组与输入设备关联,其中 indevlv_indev_drv_register() 的返回值。

group 相关有一些预定义的键有特殊的含义,如下:

LV_KEY_NEXT 下一个对象
LV_KEY_PREV 关注前一个对象
LV_KEY_ENTER 触发LV_EVENT_PRESSED/CLICKED/LONG_PRESSED等事件
LV_KEY_UP 增加价值或向上移动
LV_KEY_DOWN 降低价值或向下移动
LV_KEY_RIGHT 增加价值或向右移动
LV_KEY_LEFT 减少数值或向左移动
LV_KEY_ESC 关闭或退出(例如关闭下拉列表)
LV_KEY_DEL 删除(例如文本区域右边的一个字符)
LV_KEY_BACKSPACE 删除左边的字符(例如在文本区域)
LV_KEY_HOME 转到开始/顶部(例如在文本区域)
LV_KEY_END 转到最后(例如在文本区域)
最重要的特殊键是 LV_KEY_NEXT/PREVLV_KEY_ENTERLV_KEY_UP/DOWN/LEFT/RIGHT
read_cb 函数中,您应该将一些键转换为这些特殊的键,以便在组中导航并与所选对象交互。只使用LV_KEY_LEFT/RIGHT 就足够了,因为大多数对象都可以用它们完全控制。对于编码器,您应该只使用LV_KEY_LEFT,LV_KEY_RIGHTLV_KEY_ENTER

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐