您的位置:首页 > 运维架构 > Linux

嵌入式Linux系统的电子书阅读器项目4——Input Event System

2016-02-11 17:37 666 查看
1.输入事件系统(Input EventSystem)架构与API
1). 输入事件系统框架设计
输入事件系统的框架如图1所示。



图1输入事件系统框架
类似其它几个子系统,输入事件子系统也是采用一个核心层+可装载组件的模式,
并且将底层实际输入设备(如 touchscreen)
所产生的原始数据(raw input data)封装层格式化的输入事件(input event)提供给Book
Engine访问。
这样就隐藏了事件输入设备的复杂性与多样性。作为一个学习型项目,在输入事件系统的核心函数采用
可配置的多种实现方法,可让爱好者学习比较各种方法的优劣。
2).输入事件系统框架API概述
input eventsystem 的API声明位于include/myinpu.h头文件中。
输入事件 input_ev:
输入事件的封装结构声明如下

typedef structinput_ev {
struct timeval time;
int type;  /* stdin, touchsceen */
int val;   /*  */
}input_ev;


time——事件发生的系统时间
type——判断是哪个具体设备产生的输入事件(目前只有 stdin/touchscreen),相关的宏值如下:

#defineINPUT_TYPE_STDIN        0
#defineINPUT_TYPE_TSC               1


val——经过抽象化的输入事件值,目前支持以下几个宏值:

#defineINPUT_VAL_UP                 0
#defineINPUT_VAL_DOWN               1
#defineINPUT_VAL_EXIT               2
#defineINPUT_VAL_UNKNOWN      -1
该值是输入设备(inputdev)通过获取原始输入值(如键盘“U”、"N"、touchscreen的向右滑动10%x坐标)

经过设备模块组件内部的get_input_ev()对原始值进行逻辑处理和转换,提交给input event system core层的。
INPUT_VAL_UP——表示向上翻页事件,INPUT_VAL_DOWN——表示向下翻页事件,INPUT_VAL_EXIT——
表示退出电子书事件,INPUT_VAL_UNKNOWN——表示获取了未知值,需要异常处理。
这样就把底层输入设备不同的输入特性和采用值封装起来,提供了统一的抽象input event接口。

核心描述符 input_dev:
核心描述符声明如下,

typedef structinput_dev{
char *name;
union input_method mthd;
int (*init_dev)(void);
int (*exit_dev)(void);
int (*get_input_ev)(struct input_ev*pev);
struct input_dev *next;
}input_dev;
mthd是一个联合体,根据不同是输入策略(如select机制,多线程等)实际实现方法,根据配置,由宏开关隔开,

根据实际需要配置成select文件描述符,或者多线程下的线程id。
next:
系统所加载的input_dev列表,注意,加载的输入设备不一定被激活使能。
加载之后,只有通过enable_input_dev_set()函数使能的设备,才能产生输入响应。
init_dev() 与 exit_dev():
实际输入设备的初始化与退出函数,需要根据设备的特性在不同输入设备组件函数中实现,
在enable_input_dev_set()与 disable_input_dev_set()函数中被调用。
get_input_ev():
实际输入设备组件的输入事件获取函数,将原始输入值经过一点逻辑处理,转换成input_ev
然后提交给input eventsystem core层。
get_rt_input_ev():
该API被用户调用,获取input_ev()的。它会从所有被enble的input
dev中探测是否有输入事件产生,
然后返回结果,不同实现方法的差异,在下一节再叙述。
enable_input_dev_set()与 disable_input_dev_set():
该对函数根据输入参数列表中input dev的name使能/关闭输入加载到系统中的input_dev,只有使能的
input dev才会有输入响应。
3).输入事件系统函数多种实现方法比较
input eventsystem 的函数实现位于myinput/myinput.c文件中。
其它函数和之前的系统类似,都是链表相关的加载,调试操作,最重要的函数是get_rt_input_ev()
get_rt_input_ev()的实现方法:
轮询(query)方法:
轮询方法的get_rt_input_ev()函数实现方法如下。它是轮流调用各个输入设备的get_input_ev()函数,
有输入事件发生,返回0表示成功,否则query完所有设备之后,没有任何输入事件,则返回-1。

int ret;
struct input_dev *tmp_dev = idev_h;
while(tmp_dev){
//printf("use inputdev: %s\n",tmp_dev->name);
if(tmp_dev->get_input_ev(pev)== 0)
return 0;
tmp_dev =tmp_dev->next;
}
return -1;
该方法,适合单个enable输入设备进行初次项目验证与调试。主要缺点有:

1.CPU占用率高(轮询的老毛病)
2.当不同input_dev的get_input_ev()函数实现机制不同时(如stdin采用非阻塞机制,而tscreen设备用阻塞机制),
则容易导致非阻塞机制的设备处于饥饿状态,电子书应用进程会阻塞在tscreen设备直到获取输入值,
这样stdin设备的输入将得不到响应。这样,上层接口就得考虑底层组件的实现机制。
所以这并不是正式release项目的一个好机制,只是做初次调试而已。
Select 机制:
select机制的get_rt_input_ev()函数实现方法如下。它在每个input_dev组件的init_dev()函数的实现中,将设备描述符fd
加入了input event system core的fd_set中,来通过select机制查询input_ev是否发生。

int ret;
struct input_dev *tmp_dev = idev_h;
fd_set tmp_rd_set = idev_fd_set;
ret = select(max_fd_val,&tmp_rd_set, NULL, NULL, NULL);
if(ret > 0){
while(tmp_dev){
if(FD_ISSET(tmp_dev->mthd.fd,&tmp_rd_set))
if(tmp_dev->get_input_ev(pev)== 0)
return0;
tmp_dev =tmp_dev->next;
}
}
return -1;
采用select机制,就能解决轮询(query)方法中所叙述的2个问题,不管底层input_dev的get_input_ev()函数如何实现,

只要select函数中的tmp_rd_set集中的描述符有输入事件发生,则select函数将返回,而不会产生一个设备处于饥饿状态。
但是该方法也有缺点——使得电子书应用进程在select函数处阻塞,影响系统整体效率。

多线程机制:
多线程机制的get_rt_input_ev()函数实现方法如下,它为每个输入设备在每个input_dev组件的init_dev()时,开启一个线程,
然后在用户调用get_rt_input_ev()函数时,等待产生input_ev的线程提交条件信号,然后去获取该input_dev所产生的input_ev的值。

pthread_mutex_lock(&input_mutex);
pthread_cond_wait(&input_cond,&input_mutex);
memcpy(pev, &sh_iev,sizeof(struct input_ev));
pthread_mutex_unlock(&input_mutex);
return 0;


采用多线程的方法,CPU占用较低,处理相对高效,也不会出现某设备由于实现机制而产生的饥饿问题。
2.输入事件系统(Input EventSystem)模块组件
1). stdin dev
stdin_dev 组件的实现的源代码在myinput/stdin_dev.c文件中。类似于其它子系统,stdin_dev主要也是填充
实现核心input_dev描述符,实现填充代码如下:

static structinput_dev stdin_dev = {
.name          = "stdin_dev",
.init_dev      = init_stdin_dev,
.exit_dev      = exit_stdin_dev,
.get_input_ev =get_stdin_dev_ev,
};
init_stdin_dev():

由于stdin_dev设备主要是利用tty终端的标准输入stdin关联键盘的"U"/"N"/"Q"三键控制翻页与结束,
该函数作为stdin_dev的初始化函数,首选要把终端的标准输入stdin设置为non-canon模式,缓冲设为最小。
这个函数所使用的Linux系统终端相关的函数和结构的用法细节,可以参考《Unix环境高级编程一书》。
exit_stdin_dev():
该函数也是调用Linux系统终端相关API,将终端tty的状态恢复正常模式。
get_stdin_dev_ev():
该函数获取终端stdin的原始输入,并把如果遇到U/N/Q三个键输入,则input_ev的val值设为对应的
INPUT_VAL_UP/INPUT_VAL_DOWN/INPUT_VAL_EXIT,然后将输入事件提交给input system core。

2). touchscreen
Smart210 开发板触屏特性与使用方法:
Smart210 开发板触屏是7寸多点触控电容触屏,而不是@韦东山老师视频里用的电阻屏。并且,由于Smart210
使用了goodix公司非开源的电容触屏驱动,用过一个叫onewire的驱动,封装了触摸屏的event输入事件,因而韦东山
老师的源代码,需要一定修改才能在在Smart210开发板上使用。
Smart210 开发板的原版文件系统(Linux 3.0.8内核),其实在usr/lib 和 usr/lib/ts已经安装好了tslib的库,交叉工具链中也有tslib相关头文件,
它的库与原版的tslib的差异截图如下:



图2 Smart210 tslib环境变量的差异



图3 Smart210 tslib ts.config 差异



图4 Smart210 tslib 动态链接库 lib/ts 下的文件差异

图2所示为Smart210 开发板tslib相关环境的配置,注意红线处,Smart210 所用的 TSLIB_TSDEVICE 不是 通常的/dev/input/event* ,
而应该是goodix芯片电容触屏1wire模式的的专用非开源驱动设备。
图3 是ts.config 的差异,其中黄线处,module_raw 所用的库,和韦东山视频里配置的不同,Smart210 开发板有专门为它的触摸屏
设备在应用层封装了一个底层驱动使用的库,该库所在路径如图4黄线所示。该库是tslib原版代码编译出来所没有的。
Smart210 开发板只有使用了该配置,配对了相关的device和module_raw,应用层程序才能正常获取数据工作,
以上就是Smart210 电容触屏和韦东山视频里JZ2440 电阻屏配置上有差异的部分。
touchsreen模块组件的实现
touchsreen模块组件在 myinput/tscreen.c文件中实现。该组件使用了开源的tslib触摸屏库,相关源代码可以在 tslib开源库代码下载 下载到。
tslib的需要先安装配置,相关的教程可以参考这篇文章tslib编译安装方法
touchsreen组件也需要填充input_dev结构体,其中 init_tscreen_dev()主要是调用tslib的API进行相关的初始化,可以参考
API使用教程。
get_tscreen_ev():
该函数获取touchscreen的原始输入数据(raw input),并将其转换为input_ev对应的值,然后提交。
input_ev事件的与触屏输入的关系如图5所示



图5 input_ev与触屏动作的对应关系
通过图5可知,该函数将向左滑动10%触屏x轴横坐标定义为向上翻页,向右滑动10%定义为向下翻页。该函数即处理相关逻辑,并判断手是否一直在触屏之上,有无中途离开。is_out_of_time()函数则是用来延时消除抖动等误操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: