Linux驱动学习笔记----------触摸屏驱动

关于 触摸屏的驱动,其实也是input输入子系统,只不过是一个升级版罢了。
我所说的都是基于韦老大所说,再加上自己的理解。

这里关于输入子系统再做一些补充:
—————–入子系统体系—————
设备事件层: Linux_Dir/drivers/input/evdev.c(提供handler) 提供输入设备产生的原始数据并上报给应用程序,这适用于 所有输入设备, 触摸屏也不例外 编写基于输入子系统的驱动时只需:

1.分配input_dev

2.设置能产生什么事件和这类事件的哪些事件

3. input_register_device注册

4. 硬件操作

Linux驱动学习笔记----------触摸屏驱动_第1张图片

测出来的值是电压值(也就是我们在后续代码中打印出来的X 与 Y 的值,其实是电压值,与LCD 本身的坐标没有关系的)

编写代码前,韦老大为我们分析了触摸屏的使用过程:
Linux驱动学习笔记----------触摸屏驱动_第2张图片

了解了触摸屏的使用过程之后我们开始代码操作
老规矩,输入子系统的基本步骤:

static int s3c_ts_init(void)
{
/*分配并设置一个 input_dev 结构体*/
    struct clk* clk;

    /* 1. 分配一个input_dev结构体 */
    s3c_ts_dev = input_allocate_device();

    /* 2. 设置 */
    /* 2.1 能产生哪类事件 */
    set_bit(EV_KEY, s3c_ts_dev->evbit);//按键事件
    set_bit(EV_ABS, s3c_ts_dev->evbit);//绝对位移事件

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

接下来是硬件相关的操作:
硬件相关操作之前我们应该依据手册进行查找和匹配,由于触摸屏涉及到AD转换,因此我们打开2440 datasheet,查看ADC那一章节
Linux驱动学习笔记----------触摸屏驱动_第3张图片
开头给主要我们介绍了它是10 位 CMOS ADC(模/数转换器)是一个 8 通道模拟输入的再循环类型设备。其转换模拟输入信号为 10 位二 进制数字编码,最大转换率为 2.5MHz A/D 转换器时钟下的 500 KSPS。A/D 转换器支持片上采样-保持功能和掉电 模式的操作。
触摸屏接口可以控制/选择触摸屏 X、Y 方向的引脚(XP,XM,YP,YM)的变换。触摸屏接口包括触摸屏引 脚控制逻辑和带中断发生逻辑的 ADC 接口逻辑。
分辨率:10 位
供电电压:3.3V
这里我们用到ADC因此需要使能ADC的时钟通过clk = clk_get(NULL, “adc”);
clk_enable(clk);进行
Linux驱动学习笔记----------触摸屏驱动_第4张图片
然后才进行硬件上的设置:
bit[15]是只读位,就不用管它
bit[14]: 预分频使能
bit[13:6]:预分频系数,我们需要查看PCLK ,通过在终端输入dmesg进行查看。设置是50MHZ,公式是ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
在手册中有提到喔:
Linux驱动学习笔记----------触摸屏驱动_第5张图片
bit[5:3]:模拟输入通道暂时不用选择
bit[2]:我们要设置为0,设置为1的话,它会进入省电模式
bit[1]: 启动ADC的方式,我们这里设置为0
bit[0]:不用设置,默认0,之后在启动之后,会进行配置的。
Linux驱动学习笔记----------触摸屏驱动_第6张图片

这里补充一些关于各个时钟的介绍:
扩充:
FCLK is used by ARM920T ,内核时钟,主频。
HCLK is used for AHB bus, which is used by the ARM920T, the memory controller, the interrupt controller, the LCD controller, the
DMA and USB host block. 也就是为AHB总线上的外设提供时钟信号,包括USB时钟。 AHB总线用于连接高速外设。
PCLK is used for APB bus, which is used by the peripherals such as WDT, IIS, I2C, PWM timer, MMC interface,ADC, UART,
GPIO, RTC and SPI. 也就是为APB总线上的外设提供时钟信号,即IO接口时钟,串口的时钟设置就是从PCLK来的。APB总线用于连接低速外设。

为了方便对各个寄存器进行配置,根据手册我们创建一个结构体。
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};

Linux驱动学习笔记----------触摸屏驱动_第7张图片
另外我们需要注册一个中断request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, “ts_pen”, NULL); 那我们怎么让触摸屏按下产生中段,松开又产生中断呢?
因此,根据手册我们可以知道,触摸屏是有很多工作模式的,在这里我们选择的是等待中断模式:等待中断模式设置值为 rADCTSC=0xd3;

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

    enter_wait_pen_down_mode();//等待触摸屏按下

    return 0;
}

补充下相关函数

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

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();
    }
    return IRQ_HANDLED;
}

这样就基本实现一个简单的按下松开的打印信息操作。(这里需要讲内核中原来的触摸屏驱动给关掉)
1. make menuconfig 去掉原来的触摸屏驱动程序
-> Device Drivers
-> Input device support
-> Generic input layer
-> Touchscreens
<> S3C2410/S3C2440 touchscreens
代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

#include 
#include 
MODULE_LICENSE("GPL");
struct s3c_ts_regs {
    unsigned long adccon;
    unsigned long adctsc;
    unsigned long adcdly;
    unsigned long adcdat0;
    unsigned long adcdat1;
    unsigned long adcupdn;
};

static struct input_dev *s3c_ts_dev;
static volatile struct s3c_ts_regs *s3c_ts_regs;

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

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();
    }
    return IRQ_HANDLED;
}

static int s3c_ts_init(void)
{
    struct clk* clk;

    /* 1. 分配一个input_dev结构体 */
    s3c_ts_dev = input_allocate_device();

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

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

    enter_wait_pen_down_mode();

    return 0;
}

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

module_init(s3c_ts_init);
module_exit(s3c_ts_exit);

接下来,将开始坐标测量和ADC转换。

首先,我们先分析下一段代码:

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

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

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

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();//从新进入等待松开模式,使得adcdat0的15位变成松开模式。
    return IRQ_HANDLED;
}

这里,我们做了几个事情:
1. 我们进入了测量xy坐标模式这里写图片描述
xy的值会放在adcdat0
这里写图片描述
也就是说我们只要把bit2,bit3设置为1 。
2.获取到坐标值之后,我们就启动adc转换 也就是将0位置1.
3.进入到adc中断,把xy的坐标值给打印出来。

注意: 在测量结束之后还应该再次进入 等待松开模式,这样才能进行连续的点击,否则,只能进行唯一一次的点击,之后就没有效果了

但是这里还有两个问题,就是当我们在长按,或者滑动触摸屏的时候,它没有反应,还是原来的值。因此,我们应该在程序中加入定时器进一步完善我们的驱动。
第二个问题,就是我们的电压值,有的时候变化还是比较大的,原因就是在我们按下后会产生中断,而启动adc是需要时间的,但如果在启动之前我又松开了,那这个值不就是不可靠了吗?因此我们需要设置下延迟时间,并且在adc中断中进行判断,判断是否松开了,如果松开了,我们就丢弃那个值。

    static struct timer_list ts_timer;
    /* 优化措施1: 
     * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
     */
    s3c_ts_regs->adcdly = 0xffff;

    /* 优化措施5: 使用定时器处理长按,滑动的情况
     * 
     */
    init_timer(&ts_timer);
    ts_timer.function = s3c_ts_timer_function;
    add_timer(&ts_timer);
static void s3c_ts_timer_function(unsigned long data)
{
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开,就不管了 */
        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/*否则就再次进入呗,重新启动adc*/
    {
        /* 测量X/Y坐标 */
        enter_measure_xy_mode();
        start_adc();
    }
}
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完成时, 发现触摸笔已经松开, 则丢弃此次结果 ,从你按下到ADC的启动是要时间的 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        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);
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;   
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)
        {
            /* 优化措施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);
                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;
}

至此,大体上的驱动就完成了,一些细节上的优化措施这里就不提了,直接上韦老师代码吧

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

#include 
#include 

struct s3c_ts_regs {
    unsigned long adccon;
    unsigned long adctsc;
    unsigned long adcdly;
    unsigned long adcdat0;
    unsigned long adcdat1;
    unsigned long adcupdn;
};

static struct input_dev *s3c_ts_dev;
static volatile struct s3c_ts_regs *s3c_ts_regs;

static struct timer_list ts_timer;

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

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

static void start_adc(void)
{
    s3c_ts_regs->adccon |= (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;
}

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


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
    {
        //printk("pen down\n");
        //enter_wait_pen_up_mode();
        enter_measure_xy_mode();
        start_adc();
    }
    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完成时, 发现触摸笔已经松开, 则丢弃此次结果 ,从你按下到ADC的启动是要时间的 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;

    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        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);
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;   
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)
        {
            /* 优化措施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);
                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;
}

static int s3c_ts_init(void)
{
    struct clk* clk;

    /* 1. 分配一个input_dev结构体 */
    s3c_ts_dev = input_allocate_device();

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

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

    /* 优化措施1: 
     * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
     */
    s3c_ts_regs->adcdly = 0xffff;

    /* 优化措施5: 使用定时器处理长按,滑动的情况
     * 
     */
    init_timer(&ts_timer);
    ts_timer.function = s3c_ts_timer_function;
    add_timer(&ts_timer);

    enter_wait_pen_down_mode();//一开始,我们要等待触摸屏按下。

    return 0;
}

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

module_init(s3c_ts_init);
module_exit(s3c_ts_exit);


MODULE_LICENSE("GPL");


你可能感兴趣的:(linux驱动)