1. 程式人生 > >基於Android系統的多點觸控式螢幕(MultiTouchScreen)驅動

基於Android系統的多點觸控式螢幕(MultiTouchScreen)驅動

理論:

輸入子系統由來

在Linux中, 應用層對於輸入裝置(滑鼠、鍵盤、觸控式螢幕等)的操作無非都是open、read、write、ioctl,然後呼叫驅動層的xxx_open、xxx_read、xxx_write、xxx_ioctl去操作具體的硬體輸入裝置。如果按照傳統的思路,每個輸入裝置都按照這個套路寫這些open、read等,是不是太過於累贅了。所以Linux就定義了一套標準,來標準化這些輸入裝置驅動,這個標準就叫做輸入子系統。通過這個標準,寫驅動的人不在重複的寫xxx_open、xxx_read等通用程式碼,而只用完成各個輸入裝置不同的部分(註冊不同的輸入裝置, 上報不同的輸入事件,操作不同的硬體等)。

輸入子系統框架

在Linux中,輸入子系統是由輸入子系統裝置驅動層、輸入子系統核心層(Input Core)和輸入子系統事件處理層(Event Handler)組成。其中裝置驅動層提供對硬體各暫存器的讀寫訪問和將底層硬體對使用者輸入訪問的響應轉換為標準的輸入事件,再通過核心層提交給事件處理層;而核心層對下提供了裝置驅動層的程式設計介面,對上又提供了事件處理層的程式設計介面;而事件處理層就為我們使用者空間的應用程式提供了統一訪問裝置的介面和驅動層提交來的事件處理。所以這使得我們輸入裝置的驅動部分不在用關心對裝置檔案的操作,而是要關心對各硬體暫存器的操作和提交的輸入事件。下面用圖形來描述一下這三者的關係吧!(轉)
在這裡插入圖片描述

Linux輸入子系統的結構體:
應用空間:
 open(“/dev/input/eventx”)
核心空間:
輸入子系統裝置驅動
  向輸入系統核心註冊一個具體型別的輸入裝置,檢測輸入事件的發生,並上報輸入事件到輸入子系統事件處理層。
輸入子系統核心
  提供各個輸入裝置的註冊介面,裝置驅動上報事件到事件處理層的橋樑。
輸入子系統事件處理層
  接收裝置驅動上報的輸入事件,提供應用層訪問裝置統一的介面(open, read等),將事件通過介面傳遞到使用者空間。
底層硬體:
滑鼠 鍵盤 觸控式螢幕等
通過輸入系統的這個框架發現,我們需要做的事情就是對於不同的輸入裝置,寫出對應的裝置驅動,並向輸入系統核心註冊這個輸入裝置當檢測到有輸入事件發生,將輸入事件上報到輸入子系統事件處理層就OK了。

具體操作:

首先,對觸控式螢幕資料的採集是採用i2c介面或者spi介面,本例子以i2c介面為例,那麼首先要熟悉i2c驅動的框架。

1. 註冊一個i2c_driver結構體,去匹配i2c_device機構體(i2c_device可以通過新增board_info,裝置樹等方式建立,這裡不做詳細介紹)

/* 分配/設定i2c_driver */
static struct i2c_driver mts_driver = {
    .driver = {
        .name   = "my_mts",
        .owner  = THIS_MODULE,
    },
    .id_table   = mts_id_table,
    .probe      = mts_probe,
    .remove     = mts_remove,
};

static const struct i2c_device_id mts_id_table[] = {
    { "my_mts", 0 },
    {}
};

/* 註冊i2c_driver */
i2c_add_driver(&mts_driver);

2. 在i2c的probe函式中分配、設定、註冊一個input_dev結構體。

static int mts_probe(struct i2c_client *client, const struct i2c_device_id *id) {
ts_client = client;        //記錄i2c_client

/* 2.1分配input_dev結構體 */
struct input_dev * ts_dev = input_allocate_device();

/* 2.2設定這個input_dev結構體 */
//設定這個輸入裝置能產生哪類事件   --  能產生同步事件EV_SYN  和  絕對座標事件EV_ABS
set_bit(EV_SYN, ts_dev->evbit);
set_bit(EV_ABS, ts_dev->evbit);

//能產生這類事件中的哪些具體事件  -- X軸座標ABS_MT_POSITION_X  Y軸座標ABS_MT_POSITION_Y 和觸點的ID ABS_MT_TRACKING_ID    
set_bit(ABS_MT_TRACKING_ID, ts_dev->absbit);      //對於多點觸控式螢幕,每一個觸點都有一個ABS_MT_TRACKING_ID
set_bit(ABS_MT_POSITION_X,  ts_dev->absbit);
set_bit(ABS_MT_POSITION_Y,  ts_dev->absbit);

//這些事件的範圍
//支援多少個觸點,具體支援多少個,由具體硬體手冊決定
input_set_abs_params(ts_dev, ABS_MT_TRACKING_ID, 0, MTS_MAX_ID, 0, 0);

//X軸和Y軸的最大值,由硬體手冊看觸控式螢幕解析度決定    
input_set_abs_params(ts_dev, ABS_MT_POSITION_X, 0, MTS_MAX_X, 0, 0);      
input_set_abs_params(ts_dev, ABS_MT_POSITION_Y, 0, MTS_MAX_Y, 0, 0);

//Android系統會根據這個name來找相應的配置檔案,如果僅僅是Linux驅動則可有可無
//#define  FT5X0X_NAME   ft5x0x_ts
ts_dev->name = FT5X0X_NAME; 

/* 2.3註冊這個輸入裝置 */
input_register_device(ts_dev);

3. 硬體相關的操作:

//初始化一個工作佇列,中斷下半部使用
INIT_WORK(&mts_work, mts_work_func);

//註冊觸控式螢幕對應的外部中斷,中斷號可以通過gpio_to_irq獲取,如果是3.x以後的核心需要通過裝置樹獲取
request_irq(irq, mts_interrupt, IRQ_TYPE_EDGE_FALLING, "my_mts", NULL);
--------------------- 

4. 實現中斷處理函式,並使用中斷的底半部機制去處理中斷(上報ABS事件)

//中斷處理函式
irqreturn_t mts_interrupt(int irqno, void * dev_id)
{
    /* 通過i2c讀取中斷資料即座標
     * 但是i2c是低速裝置,所以用中斷下半部處理
     * */
    schedule_work(&mts_work);   //排程中斷下半部

    return IRQ_HANDLED;
}
1
2
3
4
5
6
7
8
9
10
//中斷的底半部,來讀取中斷資料(座標)
static void mts_work_func(void * t)
{
    int i;
    int ret;
    /* 讀取i2c裝置,獲得觸點資料,並上報 */
    /* 讀資料 */
    ret = mts_read_data();
    if (ret < 0)
        return;

    /* 如果沒有觸點只上報同步事件 */
    if (!ts.ts_points) {
        input_mt_sync(ts.ts_dev);
        input_sync(ts.ts_dev);
        return ;
    }

    for (i = 0; i < ts_points; i++){   /* 將每一個觸點進行上報 */
        input_report_abs(ts_dev, ABS_MT_POSITION_X, ts_events[i].x);     //第i個觸點的x座標
        input_report_abs(ts_dev, ABS_MT_POSITION_Y, ts_events[i].y);     //第i個觸點的y座標
        input_report_abs(ts_dev, ABS_MT_TRACKING_ID, ts_events[i].id);   //第i個觸點的ID

        /* 表示一個觸點上報完成 */
        input_mt_sync(ts_dev);
    }
    /* 表示某一時刻一次上報事件結束 */
    input_sync(ts_dev);
}
--------------------- 
//通過i2c讀取中斷資料
static int mts_read_data(void) {
    u8 buf[32] = { 0 };
    int ret;

    ret = i2c_read_data(ts_client, buf, 31);
    if (ret < 0) {
        return ret;
    }

    ts_points= buf[2] & (0xf);       //根據驅動IC手冊,讀取觸點的個數

    if (!ts_points) {                //如果觸點個數為0,則只上報同步事件
        input_mt_sync(ts_dev);
        input_sync(ts_dev);
        return 1;
    }

        //讀取每一個觸點的座標,id,並儲存到ts_event中,具體如何獲取buf中資料的格式,參考具體datasheet
    switch (ts_points) {
        case 5:
            ts_events[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
            ts_events[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
            ts_events[4].id= (s16)(buf[0x1d] >> 4);
        case 4:
            ts_events[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
            ts_events[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
            ts_events[3].id= (s16)(buf[0x17] >> 4);
        case 3:
            ts_events[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
            ts_events[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
            ts_events[2].id= (s16)(buf[0x11] >> 4);
        case 2:
            ts_events[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
            ts_events[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
            ts_events[1].id= (s16)(buf[0x0b] >> 4);
        case 1:
            ts_events[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
            ts_events[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
            ts_events[0].id= (s16)(buf[0x05] >> 4);
            break;
        case 0:   //沒有觸點
            return 0;
        default:
            return -1;
    }

    return 0;
}

//儲存資料的結構體
struct tiny4412_ts_event {
    int x;
    int y;
    int id;
};
struct tiny4412_ts_event ts_events[16];
--------------------- 
//i2c讀取資料
static int i2c_read_data(struct i2c_client * client, char *rxdata, int length) {
    int ret;
    struct i2c_msg msgs[] = {
        {
            .addr   = client->addr,
            .flags  = 0,
            .len    = 1,
            .buf    = rxdata,
        },
        {
            .addr   = client->addr,
            .flags  = I2C_M_RD,
            .len    = length,
            .buf    = rxdata,
        },
    };

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret < 0)
        pr_err("%s: i2c read error: %d\n", __func__, ret);

    return ret;
}

實驗:

為Android建立配置檔案(idc檔案),配置檔案的名字和註冊的input_dev的name一致 比如ft5x0x_ts.idc並放入Android檔案系統的/system/usr/idc/,裡面的內容為:touch.deviceType = touchScreen //表示是觸控式螢幕裝置,參考官方文件https://source.android.com/devices/input/touch-devices。
將my_mts.c放入linux-3.0.86/drivers/input/touchscreen/目錄,然後修改Makefile,將原有的觸控式螢幕驅動去掉,把新的編譯進去,最後make生成zImage
燒寫新的zImage,重啟裝置,觸控式螢幕幕,OK。