在Zephyr下使用LVGL指南一文中提到过对lvgl的输入Zephyr原生只移植了KSCAN,Zephyr将Kscan做为lvgl的pointer类型输入,但对于实际应用情况这远远不够,我们知道lvgl输入设备类型支持下面4种,这几乎已经涵盖了常用的输入形式
- LV_INDEV_TYPE_POINTER: 指针,触摸屏
- LV_INDEV_TYPE_KEYPAD:键盘
- LV_INDEV_TYPE_BUTTON:外部实体按键
- LV_INDEV_TYPE_ENCODER:旋转编码器
本文以旋转编码器为例,说明如何为Zephyr内的LVGL增加新的输入设备。
配置使用旋转编码器
Zephyr本身并不支持旋转编码器驱动,如何添加请参考Zephyr添加旋转编码器驱动。本文默认已经添加该驱动,只说如何配置使用:
- 设备树指定输入设备硬件信息
1 | input_encoder: rotary_encoder { |
- 添加配置项
1
2
3
4
5# 旋转编码器使用的是传感器API的抽象,因此要启用传感器
CONFIG_SENSOR=y
# 启用旋转编码器
CONFIG_ROTARY_ENCODER=y
因为我们使用旋转编码器做为LVGL的输入,因此不能不要开启 CONFIG_LVGL_POINTER_KSCAN
LVGL使用旋转编码器
旋转编码器的驱动是以Zephyr的传感器驱动抽象,读取旋转量,因此用Zephyr的传感器驱动来实现lvgl的LV_INDEV_TYPE_ENCODER访问接口并完成注册即可。该代码可以写在Zephyr应用程序中,而无需改动Zephyr对lvgl的移植代码文件。
实现lvgl输入驱动
lvgl的输入驱动是以callback注册,形式如下1
bool (*read_cb)(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
旋转编码器的数据结果如下,只会使用enc_diff和state1
2
3
4
5
6
7
8typedef struct {
lv_point_t point; /**< For LV_INDEV_TYPE_POINTER the currently pressed point*/
uint32_t key; /**< For LV_INDEV_TYPE_KEYPAD the currently pressed key*/
uint32_t btn_id; /**< For LV_INDEV_TYPE_BUTTON the currently pressed button*/
int16_t enc_diff; /**< For LV_INDEV_TYPE_ENCODER number of steps since the previous read*/
lv_indev_state_t state; /**< LV_INDEV_STATE_REL or LV_INDEV_STATE_PR*/
} lv_indev_data_t;
- enc_diff: 为旋转变化步长,顺时针为正,逆时针为负
- state: 为旋转编码器按键,有按下和释放两个状态
旋转部分的驱动如下注释:1
2
3
4
5
6
7
8
9
10
11
12
13
14//读取旋转编码器设备树中的lable name
#if DT_NODE_HAS_STATUS(DT_INST(0, rotary_encoder), okay)
#define ENCODER_DEV_NAME DT_LABEL(DT_INST(0, rotary_encoder))
#endif
//通过lable获取device
dev_rotary = device_get_binding(ENCODER_DEV_NAME);
//读取当前旋转的度数作为初始化值,以后作为比较
sensor_channel_get(dev_rotary, SENSOR_CHAN_ROTATION, &val);
init_level = val.val1;
//注册旋转编码器callback,在旋转发生时会触发callback
sensor_trigger_set(dev_rotary, NULL, encoder_callback);
callback的内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18static void encoder_callback(const struct device *dev,
struct sensor_trigger *trigger)
{
struct sensor_value val;
sensor_channel_get(dev, SENSOR_CHAN_ROTATION, &val);
//读取当前度数,和上一次的做比较,判断是正转还是翻转,将结果保存在enc_diff中
if(init_level > val.val1){
enc_diff = -1;
}else{
enc_diff = 1;
}
//更新上一次的度数
init_level = val.val1;
printk("encoder rotation at %d\n", enc_diff);
}
由于添加的旋转编码器驱动只支持旋转量,因此还需要另外添加按键的驱动, 按键驱动的本质就是在下降沿触发中断时读取GPIO:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//读取按键的设备树信息
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS(SW0_NODE, okay)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,
{0});
static struct gpio_callback button_cb_data;
//配置按键连接的GPIO为输入
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button.port->name, button.pin);
return;
}
//为防抖考虑,配置按键GPIO为双边沿触发中断
ret = gpio_pin_interrupt_configure_dt(&button,
GPIO_INT_EDGE_BOTH);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button.port->name, button.pin);
return;
}
//注册一个delay work 用于防抖
k_work_init_delayable(&button_timer, button_timeout);
//注册中断处理函数,中断时调用button_pressed
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback(button.port, &button_cb_data);
对于机械按键一般有10ms左右的抖动,因此button_pressed内并不是直接读GPIO,而是通知delay work在10ms以后调用button_timeout读取GPIO上的电平1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void button_pressed(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
//中断触发时,通知delay work 10ms之后读GPIO
k_work_reschedule(&button_timer, K_MSEC(10));
}
static void button_timeout(struct k_work *work)
{
//根据读出的level判断按键的状态,状态保存在state。
int val = gpio_pin_get(button.port, button.pin);
printk("Button pressed at %d\n", val);
if(val > 0){
state = LV_INDEV_STATE_PR;
}else{
state = LV_INDEV_STATE_REL;
}
}
现在我们有了旋转的状态enc_diff和按压的状态state,我们按照read_cb的形式写出lvgl读按键的callback函数1
2
3
4
5
6
7
8
9
10
11bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
(void) indev_drv; /*Unused*/
//printk("encoder read\n");
data->state = state;
data->enc_diff = enc_diff;
enc_diff = 0; //读完后将enc_diff设为0,表示旋转无变化
//返回false表示没有下一个按键了,这取决于输入驱动的实现,lvgl如果发现返回值为true会一直调用read_cb,直到返回false为止
return false;
}
注册lvgl输入驱动
注册驱动非常简单,使用lvgl的接口将写好的read_cb注册给lvgl即可:1
2
3
4
5
6
7
8
9
10
11 lv_indev_drv_t indev_drv;
//初始化驱动结构,指定输入设备类型和read_cb
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_ENCODER;
indev_drv.read_cb = encoder_read;
//注册输入驱动
encoder_indev = lv_indev_drv_register(&indev_drv);
if (encoder_indev == NULL) {
printk("Failed to register input device.\r\n");
}
最后
从前文可以看到,LVGL的输入是个非常独立的部分,即使Zephyr没有实现的输入类型我们也可以简单的在应用中添加。