天天看點

android 電容屏——驅動調試之多點觸摸驅動程式分析篇

  本人用的觸摸屏IC是FocalTech公司的ft5306,是一款i2c的電容屏多點觸控晶片。對于它的整體驅動官方已經給了,我們就觸摸屏和按鍵部分的代碼做相關說明。說明其中應該注意的地方。

對于所有的input裝置,報告input事件時候都分這麼幾部分,首先在probe檔案中設定裝置發送的事件類型、按鍵類型、設定裝置一些屬性資訊。然後在發送事件時候要根據probe的設定來發送事件,否則就會被判為無效忽略掉。

一、觸摸屏部分

1.裝置配置

對于觸摸屏,必須支援的事件類型有以下這麼三個

__set_bit(EV_SYN, input_dev->evbit);  //裝置同步,每次觸摸完成以後都要發送一個同步事件,來表明這次觸摸已經完成

__set_bit(EV_ABS, input_dev->evbit); //絕對坐标事件,觸摸屏每次發送的坐标都是絕對坐标,不同于滑鼠的相對坐标

__set_bit(EV_KEY, input_dev->evbit); //按鍵事件,每次觸摸都有一個BTN_TOUCH的按鍵事件

觸摸屏必須支援的按鍵類型

__set_bit(BTN_TOUCH, input_dev->keybit);//touch類型按鍵

觸摸屏屬性設定

input_mt_init_slots(input_dev, CFG_MAX_TOUCH_POINTS);//報告最大支援的點數

input_set_abs_params(input_dev,ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);//将觸摸點看成一個橢圓,它的長軸長度。這個是可選項,并不影響正常使用。

input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ft5x0x_ts->x_max, 0, 0);//x坐标取值範圍

input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ft5x0x_ts->y_max, 0, 0);//y坐标取值範圍

2.事件發送

  我們知道每次觸摸完成後都必須發送一個同步事件(EV_SYN)來表明這次觸摸的完成。 那麼對于多點觸控的螢幕事件發送分為兩種方法,一是每次事件同步前包括多個點,一是每次事件同步前僅包含一個點。

先來看包含多個點的

static void ft5x0x_report_value(struct ft5x0x_ts_data *data)  
{  
    struct ts_event *event = &data->event;  
    int i;  
    int uppoint = 0;    //已經擡起的點數  
  
    for (i = 0; i < event->touch_point; i++)  //循環處理 緩存中的所有點  
    {  
        input_mt_slot(data->input_dev, event->au8_finger_id[i]);  //發送點的ID  
          
        if (event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2)  //如果點按下  
        {  
            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,  true);  //手指按下  
            input_report_abs(data->input_dev,ABS_MT_POSITION_X,event->au16_x[i]); //x坐标     
            input_report_abs(data->input_dev, ABS_MT_POSITION_Y,event->au16_y[i]);    //y坐标  
            input_report_abs(data->input_dev,ABS_MT_TOUCH_MAJOR,event->pressure); //觸摸點長軸長度  
        }  
        else  
        {  
            uppoint++;                              //沒有按下,則表明這個手指已經擡起  
            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,false);   //報告手指擡起  
        }  
          
  
    }  
  
        if(event->touch_point == uppoint)              
        {  
            input_report_key(data->input_dev, BTN_TOUCH, 0); //所有手指都擡起了 發送BTN_TOUCH 擡起事件  
  
        }  
        else  
        {  
            input_report_key(data->input_dev, BTN_TOUCH, event->touch_point > 0);//還有手指沒擡起,發送BTN_TOUCH 按下的事件   
  
        }  
        input_sync(data->input_dev); //sync 裝置同步  
          
}  
           

然後是每次同步僅發送一個點

static ft5x0x_report_value(struct ft5x0x_ts_data *data)  
  
{  
    struct ts_event *event = &data->event;  
    int i;  
  
    for (i = 0; i < event->touch_point; i++)  //循環處理 緩存中的所有點  
    {  
        input_mt_slot(data->input_dev, event->au8_finger_id[i]);  //發送點的ID  
        if (event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2)  //如果點按下  
        {  
            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,  true);  //手指按下  
            input_report_abs(data->input_dev,ABS_MT_POSITION_X,event->au16_x[i]); //x坐标     
            input_report_abs(data->input_dev, ABS_MT_POSITION_Y,event->au16_y[i]);    //y坐标  
            input_report_abs(data->input_dev,ABS_MT_TOUCH_MAJOR,event->pressure); //觸摸點長軸長度  
        }  
        else  
        {  
            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,false);   //手指擡起  
        }  
          
  
        input_mt_report_pointer_emulation(input_dev, true);//用模拟點的方法,來告知此次觸摸已經完成。  
        input_sync(data->input_dev); //sync 裝置同步  
          
    }  
}  
           

這兩種方法都可以,但是建議選擇上面那種,效率比較高。

二、觸摸按鍵部分

對于觸摸按鍵的發送可以分為兩種方法,一是android提供的 virtualkey's 架構方法,一種是直接報告key event的方法。我們一一來看

1.報告key event方法 

在probe中添加所支援的按鍵類型,本人用的觸摸屏上有三個按鍵是以 

報告支援事件類型

__set_bit(EV_SYN, input_dev->evbit); 

__set_bit(EV_KEY, input_dev->evbit); 

報告支援的按鍵

__set_bit(KEY_HOME, input_dev->keybit);   

__set_bit(KEY_BACK, input_dev->keybit);  

__set_bit(KEY_MENU, input_dev->keybit);

觸摸屏上的三個按鍵對應的坐标

(KEY_BACK)  120:1400   (KEY_HOME) 360:1400(KEY_MENU)  500:1400

key event的報告方法很簡單隻要報告相應的key 和裝置同步sync就可以了

static void ft5x0x_report_value(struct ft5x0x_ts_data *data)  
{  
    struct ts_event *event = &data->event;  
    int i;  
    for (i = 0; i < event->touch_point; i++)  
    {  
        if (event->au16_y[i]==1400)  
        {  
            if(event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2)  
            {  
  
                switch(event->au16_x[i])  
                {  
                case 120:  
                    input_report_key(data->input_dev, KEY_BACK, 1);  
                    break;  
                case 360:   
                    input_report_key(data->input_dev, KEY_HOME, 1);  
                    break;  
                case 500:   
                    input_report_key(data->input_dev, KEY_MENU, 1);  
                    break;    
                default: break;  
                }  
                  
            }  
            else  
            {  
  
                switch(event->au16_x[i])  
                {  
                case 120:  
                    input_report_key(data->input_dev, KEY_BACK, 0);  
                    break;  
                case 360:   
                    input_report_key(data->input_dev, KEY_HOME, 0);  
                    break;  
                case 500:   
                    input_report_key(data->input_dev, KEY_MENU, 0);  
                    break;    
                default: break;  
                }  
                  
            }  
            input_sync(data->input_dev);  
            return;  
        }  
}  
           

對于這種方法有一個bug,就是事件發送上去,系統并不認為是觸摸屏發送的按鍵,系統的 觸屏震動回報 并不起作用。這并不符合标準的android觸摸裝置标準。具體怎麼破本人比較菜沒有找到方法,大神們誰知道 求破。

2.virtualkeys方法

virtualkeys是android提供的架構使用起來簡單友善,推薦大家使用。直接上代碼

static ssize_t ft5x06_virtual_keys_show(struct kobject *kobj,       //按鍵的配置  
                    struct kobj_attribute *attr, char *buf)  
{  
    return sprintf(buf,  
        __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":120:1400:8:8"   //鍵類型:鍵值:按鍵區域中心x坐标:按鍵區域中心y坐标:按鍵區域寬:按鍵區域高  
        ":" __stringify(EV_KEY) ":"  
                    __stringify(KEY_HOME) ":360:1400:8:8"  
        ":" __stringify(EV_KEY) ":"  
                    __stringify(KEY_MENU) ":500:1400:8:8"  
        "\n");  
}  
  
static struct kobj_attribute ft5x06_virtual_keys_attr = {  
    .attr = {  
        .name = "virtualkeys.Ft5x0x_Touch_Screen",  //這裡的名字必須為virtualkeys.裝置名字  否則系統不會識别  
        .mode = S_IRUGO,  
    },  
    .show = &ft5x06_virtual_keys_show,  
};  
  
static struct attribute *ft5x06_properties_attrs[] = {  
    &ft5x06_virtual_keys_attr.attr,  
    NULL,  
};  
  
static struct attribute_group ft5x06_properties_attr_group = {  
    .attrs = ft5x06_properties_attrs,  
};  
  
static void ft5x06_virtual_keys_init(void)  
{  
    struct kobject *properties_kobj;  
    int ret;  
  
    properties_kobj = kobject_create_and_add("board_properties", NULL);//添加目錄board_properties  
  
    if (properties_kobj)  
        ret = sysfs_create_group(properties_kobj,//生成/sys/board_properties/virtualkeys.Ft5x0x_Touch_Screen虛拟按鍵配置檔案  
            &ft5x06_properties_attr_group); //可以使用 cat /sys/board_properties/virtualkeys.Ft5x0x_Touch_Screen指令來檢視配置是否正确  
    if (!properties_kobj || ret)  
        pr_err("failed to create board_properties\n");  
}  
           

然後将ft5x06_virtual_keys_init()加入到 觸摸屏的init 或者probe 函數中,這樣觸摸鍵就可以使用了。

三、觸摸屏驅動流程

i2c中加入平台初始化代碼

static struct ft5x0x_platform_data  ft5x0x_platform_i2c_data = {  
    .x_max=540,  
    .y_max=960,  
    .irq= SABRESD_CHARGE_FLT_1_B,<span style="white-space:pre"> </span>//中斷引腳  
    .reset=SABRESD_DISP0_RST_B,<span style="white-space:pre">   </span>//複位引腳  
};  
           

觸摸屏驅動初始化

static int __init ft5x0x_ts_init(void)  
{  
    int ret;  
    ret = i2c_add_driver(&ft5x0x_ts_driver);  
    if (ret) {  
        printk(KERN_WARNING "Adding ft5x0x driver failed "  
               "(errno = %d)\n", ret);  
    } else {  
        pr_info("Successfully added driver %s\n",         
            ft5x0x_ts_driver.driver.name);  
    }  
    return ret;  
}  
           

probe函數

#define VIRTUAL_LI      0  
#define EVENT_LI        1  
#define TOUCH_KEY       VIRTUAL_LI    
static int ft5x0x_ts_probe(struct i2c_client *client,  
               const struct i2c_device_id *id)  
{  
    。。。。。。。。。。  
    ft5x0x_ts = kzalloc(sizeof(struct ft5x0x_ts_data), GFP_KERNEL);//配置設定參數記憶體  
  
    ..........  
    i2c_set_clientdata(client, ft5x0x_ts);參數位址傳給i2c 核心  
  
    初始化一些參數  
    ft5x0x_ts->irq = client->irq;   
    ft5x0x_ts->client = client;  
    ft5x0x_ts->pdata = pdata;  
    ft5x0x_ts->x_max = pdata->x_max - 1;  
    ft5x0x_ts->y_max = pdata->y_max - 1;  
    ft5x0x_ts->pdata->reset = FT5X0X_RESET_PIN;  
    ft5x0x_ts->pdata->irq = ft5x0x_ts->irq;  
.....................  
    err = request_threaded_irq(client->irq, NULL, ft5x0x_ts_interrupt,   //注冊讀取資料中斷  
                   IRQF_TRIGGER_FALLING, client->dev.driver->name,  
                   ft5x0x_ts);  
    。、、、、、、、、、、、、  
    input_dev = input_allocate_device();//配置設定裝置  
...........................................  
    __set_bit(EV_SYN, input_dev->evbit);  //注冊裝置支援event類型  
    __set_bit(EV_ABS, input_dev->evbit);  
    __set_bit(EV_KEY, input_dev->evbit);  
    __set_bit(BTN_TOUCH, input_dev->keybit);  
#if TOUCH_KEY == EVENT_LI               //如果使用event key的方法  
    __set_bit(KEY_HOME, input_dev->keybit);     
    __set_bit(KEY_BACK, input_dev->keybit);    
    __set_bit(KEY_MENU, input_dev->keybit);  
#endif    
    input_mt_init_slots(input_dev, CFG_MAX_TOUCH_POINTS);   //裝置屬性  
    input_set_abs_params(input_dev,ABS_MT_TOUCH_MAJOR,  
                 0, PRESS_MAX, 0, 0);  
    input_set_abs_params(input_dev, ABS_MT_POSITION_X,  
                 0, ft5x0x_ts->x_max, 0, 0);  
    input_set_abs_params(input_dev, ABS_MT_POSITION_Y,  
                 0, ft5x0x_ts->y_max, 0, 0);  
  
      
  
    input_dev->name ="Ft5x0x_Touch_Screen";//lijianzhang  
    err = input_register_device(input_dev);         //注冊這個input裝置  
    。。。。。。。。。。。  
#if TOUCH_KEY == VIRTUAL_LI     //如果使用虛拟鍵盤設定  
  
    ft5x06_virtual_keys_init();       
#endif  
。。。。。。。。。。。。。。。。。。。。。  
}  
           

中斷處理

static irqreturn_t ft5x0x_ts_interrupt(int irq, void *dev_id)  
{  
    struct ft5x0x_ts_data *ft5x0x_ts = dev_id;  
    int ret = 0;  
    disable_irq_nosync(ft5x0x_ts->irq);  
    ret = ft5x0x_read_Touchdata(ft5x0x_ts); //讀取資料  
    if (ret == 0)  
        ft5x0x_report_value(ft5x0x_ts);//報告資料  
  
    enable_irq(ft5x0x_ts->irq);  
  
    return IRQ_HANDLED;  
}  
           

報告事件

static void ft5x0x_report_value(struct ft5x0x_ts_data *data)  
{  
    struct ts_event *event = &data->event;  
    int i;  
    int uppoint = 0;  
  
    /*protocol B*/    
    for (i = 0; i < event->touch_point; i++)  
    {  
#if TOUCH_KEY == EVENT_LI       //如果使用 key event方法  
        if (event->au16_y[i]==1400)  
        {  
            if(event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2)  
            {  
  
                switch(event->au16_x[i])  
                {  
                case 120:  
                    input_report_key(data->input_dev, KEY_BACK, 1);  
                    break;  
                case 360:   
                    input_report_key(data->input_dev, KEY_HOME, 1);  
                    break;  
                case 500:   
                    input_report_key(data->input_dev, KEY_MENU, 1);  
                    break;    
                default: break;  
                }  
                  
            }  
            else  
            {  
  
                switch(event->au16_x[i])  
                {  
                case 120:  
                    input_report_key(data->input_dev, KEY_BACK, 0);  
                    break;  
                case 360:   
                    input_report_key(data->input_dev, KEY_HOME, 0);  
                    break;  
                case 500:   
                    input_report_key(data->input_dev, KEY_MENU, 0);  
                    break;    
                default: break;  
                }  
                uppoint++;  
                  
            }  
            input_sync(data->input_dev);  
            return;  
        }  
  
#endif  
      
        input_mt_slot(data->input_dev, event->au8_finger_id[i]);  
          
          
        if (event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2)  
        {  
            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,  true);  
            input_report_abs(data->input_dev,ABS_MT_POSITION_X,event->au16_x[i]);     //lijianzhang  
            input_report_abs(data->input_dev, ABS_MT_POSITION_Y,event->au16_y[i]);  
            input_report_abs(data->input_dev,ABS_MT_TOUCH_MAJOR,event->pressure);  
        }  
        else  
        {  
            uppoint++;  
            input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,  false);  
        }  
          
  
    }  
  
        if(event->touch_point == uppoint)  
        {  
            input_report_key(data->input_dev, BTN_TOUCH, 0);  
        }  
        else  
        {  
            input_report_key(data->input_dev, BTN_TOUCH, event->touch_point > 0);  
        }  
        input_sync(data->input_dev);  
          
}  
           

這裡驅動流程做了簡略的說明,關鍵的代碼都已經貼出來了。與裝置相關代碼都是廠商給的沒有太實際參考價值.

從android input的流程分析我們知道,驅動編譯完成以後,要使觸摸屏工作,還需要三個檔案:觸摸屏配置檔案 (idc檔案,用來配置觸摸屏的一些屬性)、keylayout檔案(kl檔案,安卓層面的按鍵映射檔案)、characterMap檔案(kcm檔案,安卓層面的字元映射檔案)

我們一一來看這三個檔案

1.觸摸屏配置檔案

檔案所在目錄通路順序:

首先ANDROID_ROOT/usr/idc目錄下去找相應名字的檔案并傳回完整的路徑名,如果找不到就從ANDROID_DATA/system/devices/idc下面去找,這裡ANDROID_ROOT一般指的是/system目錄,ANDROID_DATA一般指/data目錄.

檔案名稱的查找順序首先是Vendor_XXXX_Product_XXXX_Version_XXXX.idc,然後是Vendor_XXXX_Product_XXXX.idc最後是DEVICE_NAME.idc

總結來看安卓為輸入裝置打開配置檔案依次會通路

/system/usr/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
/system/usr/idc/Vendor_XXXX_Product_XXXX.idc
/system/usr/idc/DEVICE_NAME.idc
/data/system/devices/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
/data/system/devices/idc/Vendor_XXXX_Product_XXXX.idc
/data/system/devices/idc/DEVICE_NAME.idc
           

我們驅動裡并沒有寫版本号等這些資訊,是以我們裝置通路的idc檔案會是/system/usr/idc/DEVICE_NAME.idc。是以我們在這個目錄下增加檔案Ft5x0x_Touch_Screen.idc.對于idc檔案的内容,下面是我使用的idc檔案的具體内容,僅供參考

touch.deviceType = touchScreen  
touch.orientationAware = 1  
  
touch.size.calibration = none  
touch.orientation.calibration = none   
           

2.key layout檔案

key layout檔案是android層面的按鍵映射檔案,通過這個檔案,使用者可以對kernel發送上來的按鍵功能進行重新定義。也就是說,kernel發送上來一個home鍵,你可以在這裡把它映射成一個back鍵或者其他的。一般情況下不會修改這個檔案,是以我麼完全可以使用預設的配置檔案

這個檔案通路順序

/system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/system/usr/keylayout/DEVICE_NAME.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
/data/system/devices/keylayout/DEVICE_NAME.kl
/system/usr/keylayout/Generic.kl
/data/system/devices/keylayout/Generic.kl
           

這裡不用修改是以不用做改變

3.characterMap檔案

characterMap檔案是android層面的字元映射檔案,比如:你摁下了一個'e'鍵,平時代表'e',shift+'e'代表'E',casplk+'e'代表'E',alt+'e'可能代表别的意思,這個配置檔案就是,做這些映射的。一般情況下這個檔案也不用修改。使用預設的就可以。這個檔案的通路順序:

/system/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/system/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/system/usr/keychars/DEVICE_NAME.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX.kcm
/data/system/devices/keychars/DEVICE_NAME.kcm
/system/usr/keychars/Generic.kcm
/data/system/devices/keychars/Generic.kcm
/system/usr/keychars/Virtual.kcm
/data/system/devices/keychars/Virtual.kcm
           

到了這裡 我們的觸摸屏已經完成了,燒寫以後應該可以正常使用了。

在這裡分享一個小技巧,getevent 這個工具,在/dev/input/目錄下使用這個指令,會首先得到系統中所有input裝置的描述,然後會得到,kernel發送的所有input事件,當我們寫完驅動以後,可以用這個指令将發送的事件列印出來,看驅動寫的是否正确。

繼續閱讀