在有一些场合中,如野外情况,可能我们会选择使用物理按键来控制LVGL,而不是使用触摸屏。所以本篇文章就以物理键盘为例来介绍一下如何自定义输入设备与LVGL进行交互。
1 输入设备类型
对于键盘、触摸屏、编码器等输入设备,都可以与LVGL进行交互。我们只需要将输入设备注册到LVGL的输入设备结构体lv_indev_drv_t
中即可。输入设备的类型,有以下几种选择:
typedef enum {
LV_INDEV_TYPE_NONE, /**< Uninitialized state*/
LV_INDEV_TYPE_POINTER, /**< Touch pad, mouse, external button*/
LV_INDEV_TYPE_KEYPAD, /**< Keypad or keyboard*/
LV_INDEV_TYPE_BUTTON, /**< External (hardware button) which is assigned to a specific point of the screen*/
LV_INDEV_TYPE_ENCODER, /**< Encoder with only Left, Right turn and a Button*/
} lv_indev_type_t;
先来分析一下这四种输入设备有什么区别,具体源码请参考lv_indev_read_timer_cb
函数:
(1)LV_INDEV_TYPE_POINTER
主要用于触摸屏、鼠标,所以它的输入处理函数indev_pointer_proc
就是判断当前触摸屏返回的位置处是不是有组件存在,若有且触摸释放了,则会执行对应组件的Clicked动作。
(2)LV_INDEV_TYPE_KEYPAD
它的输入处理函数indev_keypad_proc
中,就根据输入的不同按键(Prev
,Next
,ESC
,Enter
等)对Keypad
所绑定的group内的组件进行操作,如焦点的切换、进度条的滑动。比如说对于一个Table来说,里面的数据很多,有一个滑动条,这个滑动条就受LV_KEY_UP
和LV_KEY_Down
键控制;而对于一个Tabview来说,Tab的切换则是受LV_KEY_LEFT
和LV_KEY_RIGHT
键控制。而Group中的组件之间的焦点切换则是通过LV_KEY_PREV
和LV_KEY_NEXT
键切换。
(3)LV_INDEV_TYPE_BUTTON
就是一个按键,适用于GUI中焦点永远在某一个组件上的情况。按下物理按键就代表GUI中的按键按下。
(4)LV_INDEV_TYPE_ENCODER
一个编码器应该只能左右旋转,也就是支持LV_KEY_LEFT
和LV_KEY_RIGHT
。但在LVGL的源码中,编码器除了左右外,还保留了LV_KEY_ENTER
和LV_KEY_ESC
两个按键触发的代码,用户可以根据自己的情况触发这两个按键,适用于编码器和按键的组合情况。
2 物理键盘实现
经过上面的分析,我们知道对于物理键盘应该选择LV_INDEV_TYPE_KEYPAD
,这样我们就可以完成各个组件焦点的切换、滑动条上下移动等操作。
2.1 输入设备驱动注册
在函数lv_port_indev_init
中,默认有触摸设备LV_INDEV_TYPE_POINTER
的注册代码,这里我们仿照触摸设备,写一个物理按键的注册代码:
static lv_indev_drv_t keypad_drv;
lv_indev_drv_init(&keypad_drv);
keypad_drv.type = LV_INDEV_TYPE_KEYPAD;
/* 回调函数:通过这个函数读取输入设备的值 */
keypad_drv.read_cb = keypad_read;
indev_keypad = lv_indev_drv_register(&keypad_drv);
通过前面的分析,我们知道LV_KEY_PREV
和LV_KEY_NEXT
两个按键可以切换焦点,而焦点是基于组中的组件进行切换的,最后还需要绑定Keypad
输入设备对应的组。
group = lv_group_create();
lv_indev_set_group(indev_keypad, group);
lv_group_set_default(group);
2.2 按键驱动的实现
首先我们要实现按键的驱动,比如物理按键有四个,左、右、退出和确定,分别对应LV_KEY_LEFT
、LV_KEY_RIGHT
、LV_KEY_ESC
和LV_KEY_ENTER
。
对于硬件上来说,在按键处接了电容,所以不用消抖。我们就打开硬件上按键所对应引脚的上升沿/下降沿中断,然后在中断中返回我们的按键即可。这里我们默认数字0为没有任何按键按下,数字1~4分别对应物理按键的四种,在中断中将这个数字赋值给curKey
变量。然后定义一个按键读取函数,代码如下所示:
static uint8_t curKey
uint8_t button_read()
{
uint8_t tmp = curKey;
curKey = 0;
return tmp;
}
我们注意到前面注册输入设备驱动的时候,还有一个回调函数:keypad_drv.read_cb = keypad_read
,LVGL就会周期性调用这个函数来判断是否有按键按下,按键的扫描频率可以通过修改下面的宏定义改变。
#define LV_INDEV_DEF_READ_PERIOD 10
现在我们需要通过keypad_read
函数将我们的物理按键和与LVGL中的左、右、退出和确定对应起来,代码如下:
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
uint32_t act_key = button_read();
if(act_key != 0) {
data->state = LV_INDEV_STATE_PR;
switch(act_key)
{
case 1:
act_key = LV_KEY_LEFT;
break;
case 2:
act_key = LV_KEY_RIGHT;
break;
case 3:
act_key = LV_KEY_ESC;
break;
case 4:
act_key = LV_KEY_ENTER;
default:
break;
}
last_key = act_key;
} else {
data->state = LV_INDEV_STATE_REL;
}
data->key = last_key;
}
上面的函数就是在有按键按下时,赋值对应的LVGL按键宏给data->key
,而在没有按键按下时,data->key
就等于上一次按下的键。而data->state
变量则是判断按键按下后是否松开,比如对于按键的回调函数来说,可以设置Clicked
或Released
时触发GUI中的按键,对于Clicked
来说,按下不松开就触发按键回调函数,而Released
则需要按下后放开才会触发按键回调函数。