Android触控之驱动层与应用层

本文出自:【InTheWorld的博客】

触控技术算是现代智能硬件的一大特征。作为一种输入方式,触摸系统与其他输入设备(如键盘)其实非常相似。写这篇blog的初衷并不是要实现触屏驱动移植。而是看《Android内核剖析》等书的时候,发现他们都没有仔细分析触摸系统的整个流程。在这篇博客中,我打算把Android触控技术从硬件驱动到应用层大致分析一下。

本文以GT810触屏为例进行分析,虽然我手头并没有这个板子。分析原理当然还是要从硬件分析起来了,首先简单地看下电路图吧!

image

就是这么简单,重点就在I2C接口和中断CAP_INT。其中I2C总线的作用是数据传输,可以实现触摸屏的初始化以及触摸数据的传输。CAP_INT连接到CPU的中断PIN脚上,用于通知CPU来读取触摸数据。有人画了一张不错的流程图,我贴在下面:

gt810

驱动代码的一大特征就是格式统一,毕竟是架构是内核规定好的。所以,在熟悉了套路之后,我分析驱动代码的方式就是——“找输入输出”。因为驱动是一个中间层,弄清楚输入和输出的类型,思路就会清晰很多。Android的触控系统基本上都是多点触控,所以在分析触控驱动之前,我们要先熟悉一下多点触控方面的知识。

多点触控的一大难度表现在不同手指的区分。简单来说,一次多点的滑动操作,我们期望得到的是多个轨迹线,而不仅仅是一系列的孤立点。然而,每次硬件扫描的结果都是一组孤立的触摸点,所谓的一次完整的触摸过程其实就由多组有顺序关系的触摸点构成的。因此,还需要把硬件扫描的结果进一步进行处理。高级一点的触摸屏会把轨迹的处理放在硬件中做,这样一来,驱动程序就不需要做太多的事情了。如果驱动程序直接返回带轨迹信息的触摸事件,那么这种驱动被称为B类触摸驱动,反之就被称为A类驱动。

/******************************************************* 
功能: 触摸屏工作函数 由中断触发,接受1组坐标数据,校验后再分析输出
参数: ts: client私有数据结构体
return:执行结果码,0表示正常执行
********************************************************/
static void goodix_ts_work_func( struct work_struct *work )
{
    static struct point_node pointer[ MAX_FINGER_NUM ];
    static uint8_t finger_last = 0;    //last time fingers' state

    struct point_node * p = NULL;
    uint8_t read_position = 0;
    uint8_t point_data[ READ_BYTES_NUM ]={ 0 };
    uint8_t finger, finger_current;                //record which finger is changed
    uint8_t check_sum = 0;
    unsigned int x, y;
    int count = 0;
    int ret = -1; 
    
    struct goodix_ts_data *ts = container_of(work, struct goodix_ts_data, work);
    ret = i2c_read_bytes( ts->client, point_data, sizeof( point_data ) );
    
#ifndef GOODIX_MULTI_TOUCH
    if( pointer[0].state == FLAG_DOWN ){
        input_report_abs( ts->input_dev, ABS_X, pointer[0].x );
        input_report_abs( ts->input_dev, ABS_Y, pointer[0].y );
    } 
    input_report_abs( ts->input_dev, ABS_PRESSURE, pointer[0].pressure);
    input_report_key( ts->input_dev, BTN_TOUCH, ( ( pointer[0].state == FLAG_INVALID) ? FLAG_UP : pointer[0].state ) );
#else
    /* ABS_MT_TOUCH_MAJOR is used as ABS_MT_PRESSURE in android. */
    for( count = 0; count < MAX_FINGER_NUM; count++ ){
        p = &pointer[count];
        if( p->state == FLAG_INVALID ){
            continue;
        }

        if( p->state == FLAG_DOWN )
            input_report_abs( ts->input_dev, ABS_MT_POSITION_X, p->x );
            input_report_abs( ts->input_dev, ABS_MT_POSITION_Y, p->y );
            dev_dbg( &(ts->client->dev), "Id:%d, x:%d, y:%d\n", p->id, p->x, p->y );
        }

        input_report_abs( ts->input_dev, ABS_MT_TRACKING_ID, p->id);
        input_report_abs( ts->input_dev, ABS_MT_TOUCH_MAJOR, p->pressure );
        input_report_abs( ts->input_dev, ABS_MT_WIDTH_MAJOR, p->pressure );
        input_mt_sync( ts->input_dev );
    }
#endif
    input_sync( ts->input_dev );
}

上面这段代码删减了一部分错误处理的代码,在GOODIX_MULTI_TOUCH已定义的编译选项下,可以明显看到 TRACKING_ID,这个track id其实就是触摸轨迹编号。此外,如果该track id的触摸状态是按下的,还需要向输入子系统汇报触摸点的坐标。然后,Linux应用程序或者Android FrameWork就可以通过触摸屏设备文件获得多点触控的输入事件。在Android应用程序中,处理触摸事件的方式是通过实现View的onTouchEvent(MotionEvent)接口,至于onInterceptTouchEvent()接口,我们暂且按下不表。

应用程序要实现多点触控,只需要从onTouchEvent()的参数中获取触摸信息,然后进行手势分析,最后调用对应功能即可。在单点触摸的程序中,我们常常使用下面的方法获得触摸信息:

 public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN) {
            //当手指按下的时候
            x1 = event.getX();
            y1 = event.getY();
        }
        if(event.getAction() == MotionEvent.ACTION_UP) {
            //当手指离开的时候
            x2 = event.getX();
            y2 = event.getY();
        }
        //do something with the points
        return super.onTouchEvent(event);
    }

在多点触控的情况下,MotionEvent有以下一些方法,来获取多个触摸轨迹的信息:

 
    \\获得触摸轨迹数目
    public int getPointerCount();

    \\获取触摸轨迹的track id
    public int getPointerId(int pointerIndex);

    \\获取某条触摸轨迹的X、Y坐标
    public float getX(int pointerIndex);    
    public float getY(int pointerIndex);

通过以上这些api,程序最终可以得到每一条触摸信息,至于如何实现多点触控操作,就看你的想象力了!本文完~

发表评论