Linux3.4.2的觸摸屏驅(qū)動分析與編寫

觸摸屏使用過程:

  1. 觸摸屏某點被按下,產(chǎn)生INT_TC中斷;
  2. 在中斷處理程序中,打開定時器
  3. 定時器時間到,啟動ADC轉換,得到x和y坐標;
  4. ADC結束,產(chǎn)生ADC中斷;
  5. 、在ADC中斷處理函數(shù)里,上報(input_event),啟
  6. 抬起,松開屏幕

1、編寫基本框架(s3c_ts.c)

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>

static struct input_dev *s3c_ts_dev;
static int s3c_ts_init(void)
{
    /* 1. 分配一個input_dev結構體 */
    s3c_ts_dev = input_allocate_device();

    /* 2. 設置 */
    /* 2.1 能產(chǎn)生哪類事件 */
    set_bit(EV_KEY, s3c_ts_dev->evbit);
    set_bit(EV_ABS, s3c_ts_dev->evbit);

    /* 2.2 能產(chǎn)生這類事件里的哪些事件 */
    set_bit(BTN_TOUCH, s3c_ts_dev->keybit);

    input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);


    /* 3. 注冊 */
    input_register_device(s3c_ts_dev);

    /* 4. 硬件相關的操作 */
    /* 4.1 使能時鐘(CLKCON[15]) */
    /* 4.2 設置S3C2440的ADC/TS寄存器 */
    return 0;
}

static void s3c_ts_exit(void)
{
    input_unregister_device(s3c_ts_dev);
    input_free_device(s3c_ts_dev);
}

module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");

2、分析內(nèi)核自帶觸摸屏驅(qū)動

先打開JZ2440開發(fā)板原理圖,查看觸摸屏的四根引腳(TSYM、TSYP、TSXM、TSXP)分別接在ADC的——AIN4、5、6、7上面。

image-20210724162954342

通過S3C2440手冊搜索發(fā)現(xiàn),這些引腳不需要配置什么寄存器就可以使用。

看一下內(nèi)核自帶的觸摸屏驅(qū)動(drivers/input/touchscreen/s3c2410_ts.c),找到probe函數(shù),看做了什么事情,

ts.clock = clk_get(dev, "adc");
if (IS_ERR(ts.clock)) {
    dev_err(dev, "cannot get adc clock source\n");
return -ENOENT;
}

clk_enable(ts.clock);

這里有個使能時鐘的函數(shù):

Tips:在內(nèi)核啟動的時候,為了省電,會把一些不相關的模塊給關掉。

怎么關?就是通過設置CLKCON寄存器或者是(Clock Gating Control Register),我們在要用任何模塊之前必須把對應的位置1(打開模塊時鐘)。

內(nèi)核中,就是通過clk_getclk_enable使能模塊時鐘的。

然后,再看芯片手冊上的ADC和觸摸屏接口那一章:

    The 10-bit CMOS ADC (Analog to Digital Converter) is a recycling type device with 8-channel analog inputs. It converts the analog input signal into 10-bit binary digital codes at a maximum conversion rate of 500KSPS with 2.5MHz A/D converter clock. A/D converter operates with on-chip sample-and-hold function and power down mode is supported. 
    Touch Screen Interface can control/select pads (XP, XM, YP, YM) of the Touch Screen for X, Y position conversion. Touch Screen Interface contains Touch Screen Pads control logic and ADC interface logic with an interrupt generation logic.
這里說的就是這里面有個10位的mos ADC轉換器,有8路信道。在ADC工作頻率為2.5MHz時,最大轉換頻率是500KSPS,同時,因為Power Supply Voltage: 3.3V (最大輸入電壓是3.3v ),所以如果ADC的輸入電壓是3.3V的話,輸出就是10個1(0x3ff),如果是0V的話,輸出就是0。每個刻度就是3.3v/10位  10位的話就是1024 就是3.3V/1024 最小刻度是3mv

然后是我們的ADC轉換時間

image-20210724170349732

如果PCLK是50MHZ,而ADC最大工作頻率是2.5MZ,所以要設置分頻系數(shù),把這個頻率給降低下來

然后下面就是它提供的例子 the prescaler value is 49, ADC的工作頻率就是A/D converter freq. = 50MHz/(49+1) = 1MHz .

轉換時間就需要Conversion time = 1/(1MHz / 5cycles) = 1/200kHz = 5us (1MHZ的5個周期 就是5us )

  • 再看下接口模式
  1. 正常的轉換模式(Normal Conversion Mode):正常轉換模式就是一般的ADC操作,比如說你想測量某個電壓。

  2. 分離的xy坐標轉換模式(Separate X/Y Position Conversion Mode) :這種模式分為兩個部分。一種是測量X坐標,一種是測量Y坐標。

進入X或Y坐標模式需要采取的措施是:

1.設置0x69到TSCONn寄存器

2.通過設置TSADCCONn開始轉換

3.X坐標轉換結束后能被中斷給通知

4.從TSDATXn讀出坐標轉換數(shù)據(jù)

  1. 自動(連續(xù))的XY轉換模式(Auto (Sequential) X/Y Position Conversion Mode):當你進入這個模式之后,它會自動的幫你即轉換x坐標也轉換Y坐標

  2. 等待中斷模式,就是等待按下產(chǎn)生中斷模式:若想在我們按下觸摸屏后讓它產(chǎn)生中斷,就要進入該模式。當觸摸筆按下的時候,觸摸屏會產(chǎn)生INT_PENn這個中斷。

    • 怎么進入這個模式呢?設置rADCTSC=0xd3 就可以了。

3、硬件相關代碼編寫

3.1 使能時鐘

//函數(shù)體外定義全局變量clk
struct clk* clk;

/* 4.1 使能時鐘(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);

3.2 設置S3C2440的ADC控制寄存器

//函數(shù)體外定義寄存器結構體
struct s3c_ts_regs {
    unsigned long adccon;
    unsigned long adctsc;
    unsigned long adcdly;
    unsigned long adcdat0;
    unsigned long adcdat1;
    unsigned long adcupdn;
};

//僅在本文件內(nèi)使用的靜態(tài)寄存器指針變量
static volatile struct s3c_ts_regs *s3c_ts_regs;

//在初始化函數(shù)內(nèi)對寄存器結構體進行地址映射
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));

3.3 設置ADC控制寄存器

  • ADCCON
image-20210725154619162

PRESCEN(bit[14]) : =1 A/D converter prescaler enable

PRSCVL(bit[13:6]): =49 A/D converter prescaler value,最大值為2.5MHz,在此我們?nèi)?MHz,所以ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz

SEL_MUX(bit[5:3]):模擬信號輸入信道選擇,若為普通的AD轉換,則可在此選擇AIN0-3,我們先不設

STDBM(bit[2]):省電模式選擇位,默認為0,我們用的是非省電模式,所以在此可不用設置

READ_START(bit[1]):通過讀操作自動啟動AD轉換,我們在此不選該功能

ENABLE_START(bit[0]): 通過置1手動開啟AD轉換(轉換完成后自動清零),先設為0

s3c_ts_regs->adccon = (1<<14)|(49<<6);

3.4 編寫觸摸中斷處理函數(shù)(pen_down_up_irq)并注冊中斷(INT_TC)

當觸摸屏被按下時,AD轉換進入等待中斷模式(通過設置ADCTSC=0xd3)

image-20210725165523856
等待中斷模式等效電路

當觸摸屏沒有被按下時,由于上拉電阻的原因,Y_ADC處于高電平狀態(tài);當被按下時,Y_ADC的電壓由于聯(lián)通到y(tǒng)軸接地而變?yōu)榈碗娖?,此低電平就是中斷的觸發(fā)信號,使之產(chǎn)生pen down事件。

image-20210725202306346

Tips

左邊的圖為讀取x坐標時的等效電路圖:測X_ADC時,S1(XP_SEN Enable)、S2(YP_SEN Disable)、S3(XM_SEN Enable),S4(YM_SEN Disable)、S5(PULL_UP Disable);

測Y_ADC時,S1(XP_SEN Disable)、S2(YP_SEN Enable)、S3(XM_SEN Disable斷開,S4(YM_SEN Enable)、S5(PULL_UP Disable)接通;

//調(diào)用該函數(shù)進入等待觸摸屏按下模式
static void enter_wait_pen_down_mode(void)
{
    s3c_ts_regs->adctsc = 0xd3;
}

//調(diào)用該函數(shù)進入等待觸摸屏松開模式
static void enter_wait_pen_up_mode(void)
{
    s3c_ts_regs->adctsc = 0x1d3;
}

//觸摸屏中斷處理函數(shù)V1.0
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    if (s3c_ts_regs->adcdat0 & (1<<15)) //Stylus up 松開
    {
        printk("pen up\n");
        enter_wait_pen_down_mode(); //送開的話,就進入等待下次按下的模式
    }
    else    //Stylus down 按下
    {       
        printk("pen down\n");
        enter_wait_pen_up_mode();   //按下的話,就進入等待松開的模式   
    }
    
    return IRQ_HANDLED;
}

在初始化函數(shù)中,注冊中斷處理函數(shù):

request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);

3.5 編寫退出函數(shù)

static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    iounmap(s3c_ts_regs);
    input_unregister_device(s3c_ts_dev);
    input_free_device(s3c_ts_dev);
}

4、測試

4.1 編譯無觸摸屏驅(qū)動的內(nèi)核映像文件

//虛擬機(上位機)命令行下
cd linux3.4.2
make menuconfig //去掉原來的LCD驅(qū)動程序
make uImage

依次進入:

  • Device Drivers——>
    • Input device support——>
      • Touchscreens——>
        • < > S3C2410/S3C2440 touchscreens

4.2 使用新內(nèi)核啟動并加載自己的觸摸屏驅(qū)動

//Uboot命令行
tftp 30000000 uImage 或者 nfs 30000000 虛擬機IP:網(wǎng)絡文件系統(tǒng)目錄/uImage
bootm 30000000

//新內(nèi)核啟動后的命令行下
insmod s3c_ts.ko

按下/松開觸摸屏進行測試

//新內(nèi)核啟動后的命令行下
pen down
pen up
#
#
pen down
pen up

5、拓展——按下時啟動ADC,顯示坐標

修改觸摸屏中斷處理函數(shù),當觸摸屏被按下時,進入TC中斷處理函數(shù),自動測量模式并啟動ADC轉換;

AD轉換完成后產(chǎn)生ADC中斷,并在中斷處理函數(shù)中完成相應任務(例如顯示x、y點坐標)后,進入等待觸摸屏松開模式。

5.1 修改觸摸屏(TC)中斷處理函數(shù)

//觸摸屏中斷處理函數(shù)V2.0
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    if (s3c_ts_regs->adcdat0 & (1<<15)) //Stylus up 松開
    {
        printk("pen up\n");         //觸摸屏被松開
        enter_wait_pen_down_mode(); //松開的話,就進入等待下次按下的模式
    }
    else    //Stylus down 按下
    {       
        //printk("pen down\n");
        //enter_wait_pen_up_mode(); 
        enter_measure_xy_mode();    //按下的話,就進入測量的模式
        start_adc();                //啟動ADC轉換
        enter_wait_pen_up_mode();   //進入等待觸摸屏被松開
    }
    
    return IRQ_HANDLED;
}

5.2 編輯進入自動測量模式函數(shù)

static void enter_measure_xy_mode(void)
{
    s3c_ts_regs->adctsc = (1<<3)|(1<<2);    //斷開上拉電阻,打開AUTO_PST
}

5.3 編輯啟動ADC函數(shù)

static void start_adc(void)
{
    s3c_ts_regs->adccon |= (1<<0);
}

5.4 編輯ADC中斷處理函數(shù)

image-20210725203338218
//在初始化文件中編寫ADC中斷處理函數(shù)
static irqreturn_t adc_irq(int irq, void *dev_id)
{
   static int cnt = 0;
   printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
   return IRQ_HANDLED;
}

5.5 注冊ADC中斷處理函數(shù)

//在初始化函數(shù)中對ADC中斷進行注冊
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);

5.6 修改退出函數(shù)

static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);
    iounmap(s3c_ts_regs);
    input_unregister_device(s3c_ts_dev);
    input_free_device(s3c_ts_dev);
}

5.7 測試

修改完成后,重新編譯生成s3c_tc.ko驅(qū)動文件,下載安裝后測試:

rmmod s3c_tc    //卸載舊驅(qū)動
insmod s3c_tc.ko    //安裝新驅(qū)動

//碰觸觸摸屏
#
#
adc_irq cnt = 1, x = 330, y = 667
pen_up
adc_irq cnt = 2, x = 239, y = 739
pen_up
adc_irq cnt = 3, x = 215, y = 779
#
#

測試發(fā)現(xiàn):當我們滑動觸摸屏時,其輸出不能連續(xù)顯示。且輸出的坐標也不是太精確!

6、優(yōu)化——支持滑動操作、坐標更精確

6.1 使電壓穩(wěn)定后再進行轉換——設置ADC延時

//修改初始化函數(shù),添加ADCDLY延時
static int s3c_ts_init(void)
{
    struct clk* clk;
    
    /* 1. 分配一個input_dev結構體 */
    s3c_ts_dev = input_allocate_device();

    /* 2. 設置 */
    /* 2.1 能產(chǎn)生哪類事件 */
    set_bit(EV_KEY, s3c_ts_dev->evbit);
    set_bit(EV_ABS, s3c_ts_dev->evbit);

    /* 2.2 能產(chǎn)生這類事件里的哪些事件 */
    set_bit(BTN_TOUCH, s3c_ts_dev->keybit);

    input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);


    /* 3. 注冊 */
    input_register_device(s3c_ts_dev);

    /* 4. 硬件相關的操作 */
    /* 4.1 使能時鐘(CLKCON[15]) */
    clk = clk_get(NULL, "adc");
    clk_enable(clk);
    
    /* 4.2 設置S3C2440的ADC/TS寄存器 */
    s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));

    /* bit[14]  : 1-A/D converter prescaler enable
     * bit[13:6]: A/D converter prescaler value,
     *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
     * bit[0]: A/D conversion starts by enable. 先設為0
     */
    s3c_ts_regs->adccon = (1<<14)|(49<<6);

    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);

    /* 優(yōu)化措施1: 
     * 設置ADCDLY為最大值, 這使得電壓穩(wěn)定后再發(fā)出IRQ_TC中斷
     */
    s3c_ts_regs->adcdly = 0xffff;

    enter_wait_pen_down_mode();
    
    return 0;
}

6.2 多次ADC轉換,求平均值

從按下至松開的這段期間,進行多次AD轉換,并求其平均數(shù)。

static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4];
    int adcdat0, adcdat1;
    
    /* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15)) /* 已經(jīng)松開 */
    {
        cnt = 0;
        enter_wait_pen_down_mode();
    }
    else
    {
        /* 優(yōu)化措施3: 多次測量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)   //如果測量次數(shù)已經(jīng)到了4次,則打印出平均值,并等待觸摸屏被松開
        {
            printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
            cnt = 0;
            enter_wait_pen_up_mode();               
        }
        else    //如果測量次數(shù)不夠4次,則再度啟動adc轉換
        {
            enter_measure_xy_mode();
            start_adc();
        }       
    }
    
    return IRQ_HANDLED;
}

6.3 添加軟件過濾功能

6.3.1 編寫軟件過濾函數(shù)

把四次測量的值放入數(shù)組中,從頭至尾方向,依次使前兩項的平均值和第3項的值進行比較,若絕對值在允許誤差范圍內(nèi),則表示過濾成功,返回1,否則失敗,返回0

static int s3c_filter_ts(int x[], int y[])
{
#define ERR_LIMIT 10

    int avr_x, avr_y;
    int det_x, det_y;

    avr_x = (x[0] + x[1])/2;
    avr_y = (y[0] + y[1])/2;

    det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
    det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);

    if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
        return 0;

    avr_x = (x[1] + x[2])/2;
    avr_y = (y[1] + y[2])/2;

    det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
    det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);

    if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
        return 0;
    
    return 1;
}

6.3.2 修改adc中斷處理函數(shù)

static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4];
    int adcdat0, adcdat1;
    
    
    /* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已經(jīng)松開 */
        cnt = 0;
        enter_wait_pen_down_mode();
    }
    else
    {
        /* 優(yōu)化措施3: 多次測量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)
        {
            /* 優(yōu)化措施4: 軟件過濾 */
            if (s3c_filter_ts(x, y))
            {           
                printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
            }
            cnt = 0;
            enter_wait_pen_up_mode();
        }
        else
        {
            enter_measure_xy_mode();
            start_adc();
        }       
    }
    
    return IRQ_HANDLED;
}

6.4 支持滑動操作

6.4.1 定義一個定時器

static struct timer_list ts_timer;

6.4.2 修改初始化函數(shù),添加定時器

static int s3c_ts_init(void)
{
    struct clk* clk;
    
    /* 1. 分配一個input_dev結構體 */
    s3c_ts_dev = input_allocate_device();

    /* 2. 設置 */
    /* 2.1 能產(chǎn)生哪類事件 */
    set_bit(EV_KEY, s3c_ts_dev->evbit);
    set_bit(EV_ABS, s3c_ts_dev->evbit);

    /* 2.2 能產(chǎn)生這類事件里的哪些事件 */
    set_bit(BTN_TOUCH, s3c_ts_dev->keybit);

    input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);


    /* 3. 注冊 */
    input_register_device(s3c_ts_dev);

    /* 4. 硬件相關的操作 */
    /* 4.1 使能時鐘(CLKCON[15]) */
    clk = clk_get(NULL, "adc");
    clk_enable(clk);
    
    /* 4.2 設置S3C2440的ADC/TS寄存器 */
    s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));

    /* bit[14]  : 1-A/D converter prescaler enable
     * bit[13:6]: A/D converter prescaler value,
     *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
     * bit[0]: A/D conversion starts by enable. 先設為0
     */
    s3c_ts_regs->adccon = (1<<14)|(49<<6);

    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);

    /* 優(yōu)化措施1: 
     * 設置ADCDLY為最大值, 這使得電壓穩(wěn)定后再發(fā)出IRQ_TC中斷
     */
    s3c_ts_regs->adcdly = 0xffff;

    /* 優(yōu)化措施5: 使用定時器處理長按,滑動的情況
     * 
     */
    init_timer(&ts_timer);
    ts_timer.function = s3c_ts_timer_function;
    add_timer(&ts_timer);   //在出口函數(shù)中要刪除它

    enter_wait_pen_down_mode();
    
    return 0;
}

6.4.3 修該ADC中斷處理函數(shù),添加啟動定時器處理長按、滑動的情況

添加的代碼,見優(yōu)化措施5:

static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4];
    int adcdat0, adcdat1;
    
    
    /* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已經(jīng)松開 */
        cnt = 0;
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
        input_sync(s3c_ts_dev);
        enter_wait_pen_down_mode();
    }
    else
    {
        // printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
        /* 優(yōu)化措施3: 多次測量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)
        {
            /* 優(yōu)化措施4: 軟件過濾 */
            if (s3c_filter_ts(x, y))
            {           
                printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
            }
            cnt = 0;
            enter_wait_pen_up_mode();

            /* 優(yōu)化措施5:啟動定時器處理長按/滑動的情況 */
            mod_timer(&ts_timer, jiffies + HZ/100); //添加一個10ms的定時器,時間到了就進入定時器處理函數(shù)
        }
        else
        {
            enter_measure_xy_mode();
            start_adc();
        }       
    }
    
    return IRQ_HANDLED;
}

6.4.4 編輯定時器處理函數(shù)

當10ms的定時器已經(jīng)到了的話,

static void s3c_ts_timer_function(unsigned long data)
{
    if (s3c_ts_regs->adcdat0 & (1<<15)) /* 如果已經(jīng)松開,則等待下次按下 */
    {
        enter_wait_pen_down_mode();
    }
    else
    {
        /* 測量X/Y坐標 */
        enter_measure_xy_mode();
        start_adc();
    }
}

6.4.5 修改出口函數(shù)

static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);
    iounmap(s3c_ts_regs);
    input_unregister_device(s3c_ts_dev);
    input_free_device(s3c_ts_dev);
    del_timer(&ts_timer);       //刪除定時器
}

6.4.6 編譯測試

重新編譯安裝驅(qū)動后,使用觸摸屏滑動或長按,觀察輸出數(shù)據(jù):坐標可以連續(xù)輸出!

7、將printk函數(shù)替換成上報事件,得到完整觸摸屏驅(qū)動

7.1 修改adc中斷處理函數(shù)

注釋掉printk函數(shù),添加上報事件語句:

static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4];
    int adcdat0, adcdat1;
    
    
    /* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已經(jīng)松開 */
        cnt = 0;
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);  //壓力為0
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);     //無觸摸屏按鍵類事件
        input_sync(s3c_ts_dev);
        enter_wait_pen_down_mode();
    }
    else
    {
        // printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
        /* 優(yōu)化措施3: 多次測量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)
        {
            /* 優(yōu)化措施4: 軟件過濾 */
            if (s3c_filter_ts(x, y))
            {           
                //printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
                input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
                input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
                //此處僅需表示松開或按下兩種壓力狀態(tài),若是繪圖板,則會有多個壓力值
                input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
                input_report_key(s3c_ts_dev, BTN_TOUCH, 1);     //觸摸屏按鍵類事件
                input_sync(s3c_ts_dev);
            }
            cnt = 0;
            enter_wait_pen_up_mode();

            /* 啟動定時器處理長按/滑動的情況 */
            mod_timer(&ts_timer, jiffies + HZ/100);
        }
        else
        {
            enter_measure_xy_mode();
            start_adc();
        }       
    }
    
    return IRQ_HANDLED;
}

7.2 修改TC中斷處理函數(shù)

static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        //printk("pen up\n");
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
        input_sync(s3c_ts_dev);
        enter_wait_pen_down_mode();
    }
    else
    {
        //測量xy坐標
        enter_measure_xy_mode();
        start_adc();
    }
    return IRQ_HANDLED;
}

7.3 編譯測試

7.3.1 使用hexdump命令測試

rmmod s3c_tc
ls /dev/event*  //安裝新驅(qū)動前查看原來有哪些
ls:/dev/event*:No such file or directory
insmod s3c_tc.ko
ls /dev/event*  //安裝完新驅(qū)動后,查看新增加的是哪個event?
/dev/event0

hexdump /dev/event0

輸出如下:

image-20210725225531509

其輸出結果的前4行分別對應adc_irq處理函數(shù)中的4個上報事件:

input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);              input_report_key(s3c_ts_dev, BTN_TOUCH, 1); 

前面的秒和微妙代表后面事件發(fā)生的時間。

第一行代表上報事件input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);

type:代表EV_ABS宏的取值為3,即絕對位移;

code:代表ABS_X宏的取值為0,即x方向;

value:4個字節(jié)表示x坐標值。

第4行代表上報事件input_report_key(s3c_ts_dev, BTN_TOUCH, 1);

type:代表EV_ABS宏的取值為1,即按鍵;

code:代表ABS_X宏的取值為014a,即BTN_TOUCH;

value:4個字節(jié)表示按鍵值1。

7.3.2 使用tslib測試

  • 安裝tslib
tar xzf tslib-1.4.tar.gz

cd tslib

./autogen.sh
提示錯誤:./autogen.sh: 4: autoreconf: not found
解決方法:sudo apt-get install autoconf automake libtool

mkdir tmp

echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache

./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp

make

make install
  • 復制tmp目錄下的所有文件到開發(fā)板的根目錄中
cp tmp  /nfs_root/first_fs/ts_dir -rfd
cd /mnt/ts_dir
cp * / -rfd
  • 安裝驅(qū)動程序
# insmod cfbcopyarea.ko
# insmod cfbfillrect.ko
# insmod cfbimgblt.ko
# insmod s3c_lcd.ko //也可以使用內(nèi)核自帶的LCD驅(qū)動
                    //如果發(fā)生段錯誤,則重新編譯lcd驅(qū)動后,再加載
# ls /dev/ev*
ls:/dev/event*:No such file or directory
# insmod s3c_ts.ko
# ls /dev/ev*
/dev/event0         //這是觸摸屏設備
#
# ls /dev/fb0       //這是LCD設備
                    
  • 測試

    • 修改/etc/ts.conf第一行(去掉#號和第一個空格)
    # module_raw input
    改為:
    module_raw input
    
    • 添加環(huán)境變量
    # export TSLIB_TSDEVICE=/dev/event0       //指定tc觸摸屏設備
    # export TSLIB_CALIBFILE=/etc/pointercal  //校驗文件
    # export TSLIB_CONFFILE=/etc/ts.conf      //配置文件
    # export TSLIB_PLUGINDIR=/lib/ts          //插件
    # export TSLIB_CONSOLEDEVICE=none
    # export TSLIB_FBDEVICE=/dev/fb0          //指定lcd顯示屏
    
    # ts_calibrate                            //開始校驗
    錯誤提示:selected device is not a touchscreen I understand
    
image-20210726233245064
  • 解決辦法:看到這個錯誤的提示,我們應該想到tslib源碼里面肯定有這個錯誤提示的條件,這樣就讓我們有了思路,我們可以去找到這個文件看看他錯誤判斷的條件是啥?

    • 查找“selected device is not a touchscreen I understand”出處:
    cd tslib/plugins
    grep -nr "selected device is not a touchscreen I understand"
    匹配到二進制文件 .libs/input.so
    匹配到二進制文件 .libs/input-raw.o
    input-raw.c:61:       fprintf(stderr, "selected device is not a touchscreen I understand\n");
    

    可以看出,該錯誤提示語句出自input-raw.c之手!

    • 打開input-raw.c文件,找出相關段落
     47 static int check_fd(struct tslib_input *i)
     48 {
     49     struct tsdev *ts = i->module.dev;
     50     int version;
     51     u_int32_t bit;
     52     u_int64_t absbit;
     53 
     54     if (! ((ioctl(ts->fd, EVIOCGVERSION, &version) >= 0) &&
     55         (version == EV_VERSION) &&
     56         (ioctl(ts->fd, EVIOCGBIT(0, sizeof(bit) * 8), &bit) >= 0) &&
     57         (bit & (1 << EV_ABS)) &&
     58         (ioctl(ts->fd, EVIOCGBIT(EV_ABS, sizeof(absbit) * 8), &absbit) >= 0) &&
     59         (absbit & (1 << ABS_X)) &&
     60         (absbit & (1 << ABS_Y)) && (absbit & (1 << ABS_PRESSURE)))) {
     61         fprintf(stderr, "selected device is not a touchscreen I understand\n");
     62         return -1;
     63     }
     64 
     65     if (bit & (1 << EV_SYN))
     66         i->using_syn = 1;
     67 
     68     return 0;
     69 }
    

    tslib通過EVIOCGVERSION來獲取驅(qū)動的版本號,然后再通過EVIOCGBIT來判斷設備是否為觸摸屏,最后獲取觸摸屏的X軸(ABS_X),Y軸(ABS_Y),以及壓力(ABS_PRESSURE)。

    其中只要有一項內(nèi)容不正確,tslib都會認為該設備不是觸摸屏,而打印出“selecteddevice is not a touchscreen I understand”錯誤。

    EVIOCGBIT ioctl處理 4個參數(shù) ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是個特殊 case,表示返回設備支持的所有的type features)。 max_bytes表示返回的最大字節(jié)數(shù)。 bitfield域是指向保存結果的內(nèi)存指針。 return value表示保存結果的實際字節(jié)數(shù),如果調(diào)用失敗,則返回負值。

    這里用bit和EV_ABS相與來判斷是不是EV_ABS事件,判斷是這個事件后再判斷這個事件是否又X,Y軸和壓力值。


    image-20210726233040736
    • 核查內(nèi)核(linux-3.4.2/include/linux/input.h)與編譯tslib的編譯器(/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/input.h)中的EV_VERSION版本號是否一致。

    內(nèi)核中的定義為: #define EV_VERSION 0x010001

    編譯器中的定義: #define EV_VERSION 0x010000

    兩者不等,所以只要將內(nèi)核中的版本號改為0x010000再重新編譯內(nèi)核,或者將編譯器中的改為0x010001,然后再重新編譯tslib庫也行。

另外:若在觸摸屏驅(qū)動程序中沒有同時上報坐標值和壓力值也會引起類似的錯誤提醒。因為tslib同時判斷的幾個條件必須同時滿足,方才不會報錯。所以不管你的應用程序需不需要測試壓力值,在你的觸摸屏驅(qū)動程序中都要上報壓力值。即:

input_report_abs(s3c_ts_dev, ABS_X, x);
input_report_abs(s3c_ts_dev, ABS_Y, y);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
input_sync(s3c_ts_dev);

還有一點需要提醒的是,在設置tslib環(huán)境變量的時候,一定要正確設置觸摸屏(例如:event0)和顯示屏(例如:fb0)的文件名,特別是當內(nèi)核中安裝了多個event事件的時候。

  • 測試繼續(xù)
# ts_calibrate    
xres = 480, yres = 272                    //屏幕出現(xiàn)十字線,用于點擊校驗
Took 3 samples...
Top left : X = 713 Y = 806
。。。
Center : X = 477 Y = 524
...
Calibration constant:16972928 -18997 -162 ...

# ls /etc/pointercal
 /etc/pointercal
 
# ts_test         //十字架會隨著你的手指在觸摸屏上移動。
 215.787878:  124 234 1
 215.782448:  102 219 0
 。。。
 
# ts_         //按下tab命令補全鍵,顯示所有可執(zhí)行測試命令
 ts_harvest       ts_print_raw//打印觸摸點原始坐標
 ts_calibrate ts_print//打印觸摸點像素坐標
 ts_test
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 一、觸摸屏概述 觸摸屏作為一種輸入設備,是目前最簡單、方便、自然的一種人機交互方式。按照觸摸屏的工作原理和...
    JalynFang閱讀 3,643評論 0 4
  • 在事件處理層的函數(shù)都是通過input_register_handler()函數(shù)注冊到input_hander_li...
    ZebraWei閱讀 910評論 0 0
  • s3c2410_ts.c是三星公司提供的觸摸屏驅(qū)動。 入口函數(shù): 觸摸屏工作過程: 按下(作為一個高效的系統(tǒng),按下...
    VannessWu的飛屋環(huán)游記閱讀 490評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,834評論 28 54
  • 人工智能是什么?什么是人工智能?人工智能是未來發(fā)展的必然趨勢嗎?以后人工智能技術真的能達到電影里機器人的智能水平嗎...
    ZLLZ閱讀 4,098評論 0 5

友情鏈接更多精彩內(nèi)容