当前位置: 首页 > news >正文

LVGL输入设备管理

LVGL版本:8.1

原理框图

       在LVGL中,官方定义了四种输入设备(除了未定类型):

//lv_hal_invdv.h
typedef enum {LV_INDEV_TYPE_NONE,    /* 未定类型 */LV_INDEV_TYPE_POINTER, /* 触摸屏、鼠标 */LV_INDEV_TYPE_KEYPAD,  /* 键盘 */LV_INDEV_TYPE_BUTTON,  /* 硬件按键,和屏幕上的某个部件或点联动 */LV_INDEV_TYPE_ENCODER, /* 三个键值的解码器,通常为“左”、“右”和“按下” */
} lv_indev_type_t;

       这些输入设备都是LVGL统一管理的,其代码结构都是类似的,可以用以下原理图来表示:

       输入设备管理中,最核心的就是LVGL定时查询设备状态并获取数据,在图上为红色字体的lv_indev_reader,该模块承上启下,收集设备的数据并向上传递。原理图层级从上到下分别是:

  • 用户层(User):把这层定义为用户注册设备驱动以及用户操作设备和显示之间的交互行为,其中特定输入设备仅归属于一个显示器,而显示器不绑定任何输入设备。
  • 核心层(Core):reader定时(timing)采集输入设备的状态和数据,并整理成LVGL所需要的数据逻辑,再以事件形式分发到相关对象,如定义了光标,还需要在显示器上绘制光标图案。
  • 事件驱动层(Event):连接LVGL核心和操作系统的部分,通过设备驱动提供的接口读取特定格式的数据,并初步分类为有意义的输入设备状态和数据,如鼠标、键盘等类型的数据。
  • 设备驱动层(Device):通常不属于LVGL的部分,和硬件的设备驱动模块相关联,如Linux系统下使用设备驱动和输入设备之间传递数据。

源码逻辑

       LVGL查询输入设备的方式是轮询的,周期默认LV_INDEV_DEF_READ_PERIOD设置为30ms,即33Hz刷新率。在用户使用驱动注册新的输入设备时,就为新设备创建了定时器任务lv_indev_read_timer_cb()

//lv_hal_indev.c
lv_indev_t * lv_indev_drv_register(lv_indev_drv_t * driver)
{if(driver->disp == NULL) driver->disp = lv_disp_get_default();  //设备和显示器绑定if(driver->disp == NULL) {LV_LOG_WARN("lv_indev_drv_register: no display registered hence can't attach the indev to ""a display");return NULL;}lv_indev_t * indev = _lv_ll_ins_head(&LV_GC_ROOT(_lv_indev_ll));  //取下一个设备指针用于注册新设备if(!indev) {LV_ASSERT_MALLOC(indev);return NULL;}lv_memset_00(indev, sizeof(lv_indev_t));indev->driver = driver;  //为新设备"安装"驱动indev->proc.reset_query  = 1;indev->driver->read_timer = lv_timer_create(lv_indev_read_timer_cb, LV_INDEV_DEF_READ_PERIOD, indev);  //为新设备创建定时器任务return indev;
}

       该定时器任务就是上面原理图中的lv_indev_rader,其作用就是读取输入设备的数据,并组织成LVGL所需要的数据逻辑进行处理,起到承上启下的作用。

//lv_indev.c
void lv_indev_read_timer_cb(lv_timer_t * timer)
{INDEV_TRACE("begin");lv_indev_data_t data;indev_act = timer->user_data;  //这里的user_data就是输入设备if(indev_act->driver->disp == NULL) return; /* 设备没有绑定到任何显示器,就没有意义 */indev_proc_reset_query_handler(indev_act);    /* 如果有reset请求,优先处理并复位 */if(indev_act->proc.disabled) return;bool continue_reading;do {_lv_indev_read(indev_act, &data);  //从设备中读取数据continue_reading = data.continue_reading;indev_proc_reset_query_handler(indev_act);  /* 在读取函数中,有可能设置了复位,在这里还要处理一次 */indev_obj_act = NULL;indev_act->proc.state = data.state;  //状态只有按下和松开两种,仅对有按键的设备有效/* 记录上一次活跃的时间 */if(indev_act->proc.state == LV_INDEV_STATE_PRESSED) {indev_act->driver->disp->last_activity_time = lv_tick_get();}else if(indev_act->driver->type == LV_INDEV_TYPE_ENCODER && data.enc_diff) {indev_act->driver->disp->last_activity_time = lv_tick_get();}if(indev_act->driver->type == LV_INDEV_TYPE_POINTER) {indev_pointer_proc(indev_act, &data);}else if(indev_act->driver->type == LV_INDEV_TYPE_KEYPAD) {indev_keypad_proc(indev_act, &data);}else if(indev_act->driver->type == LV_INDEV_TYPE_ENCODER) {indev_encoder_proc(indev_act, &data);}else if(indev_act->driver->type == LV_INDEV_TYPE_BUTTON) {indev_button_proc(indev_act, &data);}indev_proc_reset_query_handler(indev_act);  /* 如果处理过程中产生了复位请求,还要再做一次reset */} while(continue_reading);/* 结束输入设备处理函数,将活跃输入设备指针清空 */indev_act     = NULL;indev_obj_act = NULL;INDEV_TRACE("finished");
}

       其中读取设备数据的函数_lv_indev_read()

//lv_hal_indev.c
void _lv_indev_read(lv_indev_t * indev, lv_indev_data_t * data)
{lv_memset_00(data, sizeof(lv_indev_data_t));/* For touchpad sometimes users don't set the last pressed coordinate on release.* So be sure a coordinates are initialized to the last point */if(indev->driver->type == LV_INDEV_TYPE_POINTER) {data->point.x = indev->proc.types.pointer.last_raw_point.x;data->point.y = indev->proc.types.pointer.last_raw_point.y;}/*Similarly set at least the last key in case of the user doesn't set it on release*/else if(indev->driver->type == LV_INDEV_TYPE_KEYPAD) {data->key = indev->proc.types.keypad.last_key;}/*For compatibility assume that used button was enter (encoder push)*/else if(indev->driver->type == LV_INDEV_TYPE_ENCODER) {data->key = LV_KEY_ENTER;}if(indev->driver->read_cb) {INDEV_TRACE("calling indev_read_cb");indev->driver->read_cb(indev->driver, data);  //输入事件驱动函数,即读取设备数据函数}else {LV_LOG_WARN("indev_read_cb is not registered");}
}

       在函数的最后,使用输入事件驱动读取数据read_cb(),在注册设备驱动时会传入该函数,可以使用如下的通用读取函数evdev_read()来读取不同类型的输入设备。

void evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{struct input_event in;while(read(evdev_mouse_fd, &in, sizeof(struct input_event)) > 0) {if(in.type == EV_REL) {   //相对位置if(in.code == REL_X)   //相对X位移#if EVDEV_SWAP_AXESevdev_root_y += in.value;#elseevdev_root_x += in.value;#endifelse if(in.code == REL_Y)  //相对Y位移#if EVDEV_SWAP_AXESevdev_root_x += in.value;#elseevdev_root_y += in.value;#endif} else if(in.type == EV_ABS) {  //绝对位置if(in.code == ABS_X)  //绝对X位置#if EVDEV_SWAP_AXESevdev_root_y = in.value;#elseevdev_root_x = in.value;#endifelse if(in.code == ABS_Y)  //绝对Y位置#if EVDEV_SWAP_AXESevdev_root_x = in.value;#elseevdev_root_y = in.value;#endifelse if(in.code == ABS_MT_POSITION_X)#if EVDEV_SWAP_AXESevdev_root_y = in.value;#elseevdev_root_x = in.value;#endifelse if(in.code == ABS_MT_POSITION_Y)#if EVDEV_SWAP_AXESevdev_root_x = in.value;#elseevdev_root_y = in.value;#endifelse if(in.code == ABS_MT_TRACKING_ID)if(in.value == -1)evdev_button = LV_INDEV_STATE_REL;else if(in.value == 0)evdev_button = LV_INDEV_STATE_PR;} else if(in.type == EV_KEY) {   //按键类型if(in.code == BTN_MOUSE || in.code == BTN_TOUCH) {   //鼠标按键或触摸按下,都属于POINTERif(in.value == 0)evdev_button = LV_INDEV_STATE_REL;else if(in.value == 1)evdev_button = LV_INDEV_STATE_PR;} else if(drv->type == LV_INDEV_TYPE_KEYPAD) {  //键盘类型#if USE_XKBdata->key = xkb_process_key(in.code, in.value != 0);#elseswitch(in.code) {case KEY_BACKSPACE:data->key = LV_KEY_BACKSPACE;break;case KEY_ENTER:data->key = LV_KEY_ENTER;break;case KEY_PREVIOUS:data->key = LV_KEY_PREV;break;case KEY_NEXT:data->key = LV_KEY_NEXT;break;case KEY_UP:data->key = LV_KEY_UP;break;case KEY_LEFT:data->key = LV_KEY_LEFT;break;case KEY_RIGHT:data->key = LV_KEY_RIGHT;break;case KEY_DOWN:data->key = LV_KEY_DOWN;break;case KEY_TAB:data->key = LV_KEY_NEXT;break;default:data->key = 0;break;}#endif /* USE_XKB */if (data->key != 0) {/* Only record button state when actual output is produced to prevent widgets from refreshing */data->state = (in.value) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;}evdev_key_val = data->key;evdev_button = data->state;return;}}}if(drv->type == LV_INDEV_TYPE_KEYPAD) {/* No data retrieved */data->key = evdev_key_val;data->state = evdev_button;return;}if(drv->type != LV_INDEV_TYPE_POINTER)return ;/*Store the collected data*/#if EVDEV_CALIBRATEdata->point.x = map(evdev_root_x, EVDEV_HOR_MIN, EVDEV_HOR_MAX, 0, drv->disp->driver->hor_res);data->point.y = map(evdev_root_y, EVDEV_VER_MIN, EVDEV_VER_MAX, 0, drv->disp->driver->ver_res);
#elsedata->point.x = evdev_root_x;data->point.y = evdev_root_y;
#endifdata->state = evdev_button;if(data->point.x < 0)data->point.x = 0;if(data->point.y < 0)data->point.y = 0;if(data->point.x >= drv->disp->driver->hor_res)data->point.x = drv->disp->driver->hor_res - 1;if(data->point.y >= drv->disp->driver->ver_res)data->point.y = drv->disp->driver->ver_res - 1;return ;
}

       输入事件input_event是一种通用的Linux数据结构,

struct input_event {struct timeval time;__u16 type;    //类型__u16 code;    //类型下面的具体代码__s32 value;   //值,通常为0/1
};

       以自用的鼠标为例(测试了两款鼠标都是一致的输入数据):

左移动:type:2 code:0 value:-1(相对位置:横坐标X)  +  type:0 code:0 value:0(SYNC)
右移动:type:2 code:0 value:1(相对位置:横坐标X)  +  type:0 code:0 value:0(SYNC)
上移动:type:2 code:1 value:-1(相对位置:纵坐标Y)  +  type:0 code:0 value:0(SYNC)
下移动:type:2 code:1 value:1(相对位置:纵坐标Y)  +  type:0 code:0 value:0(SYNC)

滚轮上:type:2 code:8 value:1(相对位置:REL_WHEEL)  +  type:0 code:0 value:0(SYNC)
滚轮下:type:2 code:8 value:-1(相对位置:REL_WHEEL)  +  type:0 code:0 value:0(SYNC)
滚轮按下:type:4 code:4 value:0x90003(杂项:MSC_SCAN)  +  type:1 code:0x112 value:0x1(BTN-MIDDLE 按下)  +  type:0 code:0 value:0(SYNC)
滚轮松开:type:4 code:4 value:0x90003(杂项:MSC_SCAN)  +  type:1 code:0x112 value:0x0(BTN-MIDDLE 松开)  +  type:0 code:0 value:0(SYNC)

左键按下:type:4 code:4 value:0x90001(杂项:MSC_SCAN)  +  type:1 code:0x110 value:0x1(BTN-LEFT 按下)  +  type:0 code:0 value:0(SYNC)
左键松开:type:4 code:4 value:0x90001(杂项:MSC_SCAN)  +  type:1 code:0x110 value:0x0(BTN-LEFT 松开)  +  type:0 code:0 value:0(SYNC)
右键按下:type:4 code:4 value:0x90002(杂项:MSC_SCAN)  +  type:1 code:0x111 value:0x1(BTN-RIGHT 按下)  +  type:0 code:0 value:0(SYNC)
右键松开:type:4 code:4 value:0x90002(杂项:MSC_SCAN)  +  type:1 code:0x111 value:0x0(BTN-RIGHT 松开)  +  type:0 code:0 value:0(SYNC)

       可以对照上面的evdev_read()函数,并结合Linux的输入事件代码文件“input-event-codes.h”进行学习。

       在读取了输入设备数据后,就可以分门别类地进行数据处理了,这里还是以鼠标设备为例:

//lv_indev.c
static void indev_pointer_proc(lv_indev_t * i, lv_indev_data_t * data)
{lv_disp_t * disp = i->driver->disp;/* 保存当前点位置,在下一次读取设备数据时会进行比较 */i->proc.types.pointer.last_raw_point.x = data->point.x;i->proc.types.pointer.last_raw_point.y = data->point.y;//根据旋转配置进行调整if(disp->driver->rotated == LV_DISP_ROT_180 || disp->driver->rotated == LV_DISP_ROT_270) {data->point.x = disp->driver->hor_res - data->point.x - 1;data->point.y = disp->driver->ver_res - data->point.y - 1;}if(disp->driver->rotated == LV_DISP_ROT_90 || disp->driver->rotated == LV_DISP_ROT_270) {lv_coord_t tmp = data->point.y;data->point.y = data->point.x;data->point.x = disp->driver->ver_res - tmp - 1;}/* 简单的范围检测 */if(data->point.x < 0) LV_LOG_WARN("X is %d which is smaller than zero", data->point.x);if(data->point.x >= lv_disp_get_hor_res(i->driver->disp)) LV_LOG_WARN("X is %d which is greater than hor. res",data->point.x);if(data->point.y < 0) LV_LOG_WARN("Y is %d which is smaller than zero", data->point.y);if(data->point.y >= lv_disp_get_ver_res(i->driver->disp)) LV_LOG_WARN("Y is %d which is greater than hor. res",data->point.y);//如果定义了光标,则要重新根据位置绘制光标if(i->cursor != NULL &&(i->proc.types.pointer.last_point.x != data->point.x || i->proc.types.pointer.last_point.y != data->point.y)) {lv_obj_set_pos(i->cursor, data->point.x, data->point.y);}i->proc.types.pointer.act_point.x = data->point.x;i->proc.types.pointer.act_point.y = data->point.y;if(i->proc.state == LV_INDEV_STATE_PRESSED) {indev_proc_press(&i->proc);   //按下按键的处理函数}else {indev_proc_release(&i->proc);   //释放按键的处理函数}i->proc.types.pointer.last_point.x = i->proc.types.pointer.act_point.x;i->proc.types.pointer.last_point.y = i->proc.types.pointer.act_point.y;
}

       其中按下按键时的处理函数indev_proc_press(),需要判断当前鼠标的位置是否存在LVGL组件,如果存在,则要发送按下事件LV_EVENT_PRESSED给对应的组件,进一步的,还要根据组件对象的Bubble标志决定要不要继续向上冒泡。在这里就不进一步分析了(超出本文讨论范围)。

相关文章:

  • Dinky 安装部署并配置提交 Flink Yarn 任务
  • 11. CSS从基础样式到盒模型与形状绘制
  • C++学习之路,从0到精通的征途:继承
  • 基于脑功能连接组和结构连接组的可解释特定模态及交互图卷积网络|文献速递-深度学习医疗AI最新文献
  • 在虚拟机Ubuntu18.04中安装NS2教程及应用
  • 大白话解释联邦学习
  • hadoop3.x单机部署
  • Mysql索引优化
  • Spring Boot之Web服务器的启动流程分析
  • 【android bluetooth 框架分析 02】【Module详解 7】【VendorSpecificEventManager 模块介绍】
  • 使用光标测量,使用 TDR 测量 pH 和 fF
  • AI 模型训练轻量化技术在军事领域的实战应用与技术解析
  • ​​华为云服务器:智能算力网格​
  • Vue 3.5 新特性深度解析:全面升级的开发体验
  • MQTT协议详解:物联网通信的轻量级解决方案
  • idea Maven 打包SpringBoot可执行的jar包
  • 【YOLO模型】参数全面解读
  • 微信小程序 密码框改为text后不可见,需要点击一下
  • uni-app学习笔记五-vue3响应式基础
  • 云原生|kubernetes|kubernetes的etcd集群备份策略
  • 王毅谈中拉论坛十年成果
  • 法治课|争议中的“行人安全距离”于法无据,考量“注意义务”才更合理
  • 江西吉水通报一男子拒服兵役:不得考公,两年内经商、升学等受限
  • 网信部门曝光网络谣言典型案例,“AI预测彩票号码百分百中奖”等在列
  • 国务院新闻办公室发布《新时代的中国国家安全》白皮书
  • 美英达成贸易协议,美股集体收涨