触摸屏驱动

触摸屏驱动是用输入子系统那一套实现的。
先介绍一下电阻触摸屏的原理。触摸屏巧妙的使用了欧姆定律,根据分压原理,根据电阻的大小再得到电压的大小,再通过adc输出数字量。如下图:
这里写图片描述
要注意,LCD和触摸屏是完全不同的东西,它们只是恰好大小相同,恰好放在一起。触摸屏得到的只是电压的大小,与LCD的坐标完全没有关系。至于触摸屏和LCD如何建立联系,稍后会介绍。

可以先想像一下触摸屏的使用过程:
①按下,产生中断;
②在中断处理程序中,启动adc转换x,y坐标;
③adc工作结束,产生adc中断;
④在adc中断处理函数里,上报事件(input_event),并启动定时器
⑤定时器时间到,启动adc。
⑥松开
引入定时器的目的是为了处理长按和滑动。

先写一个驱动程序的框架,即入口函数,出口函数,以及修饰。在入口函数中要完成一下操作:
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的操作

首先是分配一个input_dev结构体:

static struct input_dev *s3c_ts_dev;
s3c_ts_dev = input_allocate_device();

第二个是设置,包括两个方面,即:

/* 能产生哪类事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);

/* 能产生这类事件里的哪些事件 */
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);

也就是能够产生按键类事件,还有绝对位移;可以产生触摸的事件,参数为x坐标,y坐标,以及压力方向(只有0和1)。为什么最大值是3FF呢?因为这个触摸屏采用的adc转换器是10位的。

然后是注册

input_register_device(s3c_ts_dev);

接下来是硬件相关的操作。
内核启动过程中,为了省电,把很多暂时用不到的模块都给屏蔽了,通过操作寄存器CLKCON可以使能adc&touch screen。因此首先要使能时钟(CLKCON[15])

    clk = clk_get(NULL, "adc");
    clk_enable(clk);

然后要设置S3C2440的ADC/TS寄存器,对寄存器进行操作之前,要先进行映射,具体如何映射和上一节一样,不再赘述。adc的时钟要求低于2.5MHz,但mini2440的PCLK为50MHz,所以要进行分频。

    /* 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);
    enter_wait_pen_down_mode();

pen_down_up_irq()是中断处理函数,具体代码如下:

static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    //ADCDAT0的bit15,如果为0表示按下,1表示松开
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        printk("pen up\n");
        enter_wait_pen_down_mode();
    }
    else
    {
        printk("pen down\n");
        enter_wait_pen_up_mode();
    }
    return IRQ_HANDLED;
}

enter_wait_pen_down_mode和enter_wait_pen_up_mode如何实现呢?在2440手册中有提示,进入等待中断模式的时候,可以往ADCTSC寄存器中写“0xd3”,ADCTSC寄存器的bit8为0的时候,表示检测按下的中断,1表示松开的中断。

static void enter_wait_pen_down_mode(void)
{
    s3c_ts_regs->adctsc = 0xd3;
}

static void enter_wait_pen_up_mode(void)
{
    s3c_ts_regs->adctsc = 0x1d3;
}

然后在出口函数中按相反方向注销注册分配的东西,这样,一个最简单的触摸屏驱动程序就写好了。加载驱动后,但按下触摸屏会显示“pen down”,松开的时候会显示“pen up”。此时只能识别触摸笔按下或者松开。

接下来要对这个驱动程序进行改进。
我们可以在检测到触摸笔按下的时候,得到电压值,并启动adc

static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        printk("pen up\n");
        enter_wait_pen_down_mode();
    }
    else
    {
        //printk("pen down\n");
        //enter_wait_pen_up_mode();
        enter_measure_xy_mode();
        start_adc();

    }
    return IRQ_HANDLED;
}

其中,enter_measure_xy_mode()可以使触摸屏转换到自动测量xy坐标的模式,需要在adctsc寄存器的bit2写1,同时要求上拉电阻失能,即bit3写1:

static void enter_measure_xy_mode(void)
{
    s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}

在adccon的bit0写1,可以启动adc开始工作

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

当adc工作完成之后,会进入adc中断处理函数,在这个中断处理函数中,我们就可以把xy值打印出来,它们分别存放在adcdat0和adcdat1 的bit[0-9]中,打印完成之后别忘了让它进入等待松开模式,以便连续操作

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, s3c_ts_regs->adcdat0 & 0x3ff, s3c_ts_regs->adcdat1 & 0x3ff);
    enter_wait_pen_up_mode();
    return IRQ_HANDLED;
}

把驱动编译,加载之后,可以看到能够打印出xy的值。

接下来我们进行第一个优化,把值变得更精确一点,可以等待电压稳定时再去获取电压值,在入口函数中设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断

    s3c_ts_regs->adcdly = 0xffff;

第二个优化,在adc中断处理函数中,如果发现adc启动完成后触摸笔已经松开,要舍弃此次数据:

static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    int adcdat0, adcdat1;
    /* 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        enter_wait_pen_down_mode();
    }
    else
    {
        printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
        enter_wait_pen_up_mode();
    }

    return IRQ_HANDLED;
}

第三个优化,我们可以采用多次测量求平均值:

static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4];
    int adcdat0, adcdat1;


    /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        cnt = 0;
        enter_wait_pen_down_mode();
    }
    else
    {
        // printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 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
        {
            enter_measure_xy_mode();
            start_adc();
        }       
    }

    return IRQ_HANDLED;
}

第四个优化,软件过滤。由于x和y各有四个值,我们可以把前两个值取平均值,与第三个值相比较,然后把中间两个值取平均值,与第四个值比较,只要其中一个差值大于某一个限定,就舍弃这组数据。把这个函数放在adc中断处理函数里,当cnt等于4时,添加如果满足过滤条件之后再打印出坐标。

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;
}

第五个优化,处理长按和滑动。可以添加一个定时器。当发现触摸笔按下之后,如果在10ms之后还没有松开,那么再次启动adc读取数据。调用时机在软件过滤之后(mod_timer(&ts_timer, jiffies + HZ/100);)。当然在入口函数要注册一个定时器,

static void s3c_ts_timer_function(unsigned long data)
{
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        enter_wait_pen_down_mode();
    }
    else
    {
        /* 测量X/Y坐标 */
        enter_measure_xy_mode();
        start_adc();
    }
}

到此为止,这个驱动程序基本就算完成了,我们只需要把所有的打印语句换成上报事件就可以了。当按下时,

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);
                input_sync(s3c_ts_dev);

松开时,

input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
        input_sync(s3c_ts_dev);

这样驱动程序就算完成了。

若想使触摸屏和LCD结合起来,需要借助talib库,安装好之后就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值