本人用的觸摸屏IC是FocalTech公司的ft5306,是一款i2c的電容屏多點觸控晶元。對於它的整體驅動官方已經給了,我們就觸摸屏和按鍵部分的代碼做相關說明。說明其中應該註意的地方。 對於所有的input設備,報告input事件時候都分這麼幾部分,首先在probe文件中設置設備發送的事件類型、按 ...
本人用的觸摸屏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, //中斷引腳
.reset=SABRESD_DISP0_RST_B, //複位引腳
};
觸摸屏驅動初始化
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'可能代表別的意思,這個配置文件就是,做這些映射的。一般情況下這個文件也不用修改。使用預設的就可以。這個文件的訪問順序:
到了這裡 我們的觸摸屏已經完成了,燒寫以後應該可以正常使用了。
在這裡分享一個小技巧,getevent 這個工具,在/dev/input/目錄下使用這個命令,會首先得到系統中所有input設備的描述,然後會得到,kernel發送的所有input事件,當我們寫完驅動以後,可以用這個命令將發送的事件列印出來,看驅動寫的是否正確。