本人用的觸摸屏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事件,當我們寫完驅動以後,可以用這個指令将發送的事件列印出來,看驅動寫的是否正确。