Linux下的触摸屏驱动.doc
Linux下的触摸屏驱动一触摸屏理论概述对于触摸屏驱动,我们主要需要掌握触摸屏驱动代码和应用层测试代码。下面讲的是基于Mini2440的触摸屏驱动,现在的驱动我们都将设备和驱动分离,挂在平台设备总线上,让设备和驱动去匹配。而我们在linu2.6.32.2内核版本中的触摸屏驱动仍然没有将设备和驱动分离,这样就不存在匹配问题,这种现象其实我们并不陌生,在我们学习驱动的前期,都会研究简单字符驱动代表LED驱动,那个驱动就是把设备和驱动写在了一起。总结下,驱动和设备可以分离也可以不分离,建议分离,而本触摸屏驱动没有分离设备和驱动,有兴趣可以将设备和驱动进行分离。先说明下触摸屏的工作原理,当有人在触摸屏上按下触笔时,触摸屏的四个引脚会产生不同的电压值,这样触摸屏控制器就能检测到这种变化,从而产生INT_TC中断,表示触笔按下。然后在得到CPU指示的情况下,触摸屏控制器可以根据四个引脚上产生的不同电压值进行AD转换,从而计算出X和Y坐标的数值,并在将这两个值保持到其内部寄存器后,发出INT_ADC中断,表示坐标转换已完成,从而软件就可以读取按下触笔的位置。二触摸屏驱动分析本驱动分析很有特点,我对触摸屏驱动的分析是按照整个触摸事件的执行顺序进行代码分析的,有的函数由于每次被执行完成的任务不同,所以需要多次分析。同时,我把整个触摸事件的来龙去脉也都说的很清楚了。驱动分析/driver/input/touchscreen/s3c2410_ts.cstatic int _init s3c2410ts_init(void)struct input_dev *input_dev;adc_clock = clk_get(NULL, "adc"); /获取时钟if (!adc_clock) printk(KERN_ERR "failed to get adc clock sourcen");return -ENOENT;clk_enable(adc_clock); /使能时钟base_addr=ioremap(S3C2410_PA_ADC,0x20); /物理地址转为虚拟地址if (base_addr = NULL) printk(KERN_ERR "Failed to remap register blockn");return -ENOMEM;s3c2410_ts_connect(); /触摸屏端口配置/使能预分频,分频系数为0xffiowrite32(S3C2410_ADCCON_PRSCEN| S3C2410_ADCCON_PRSCVL(0xFF),base_addr+S3C2410_ADCCON);iowrite32(0xffff, base_addr+S3C2410_ADCDLY); /延时/检查光标按下中断信号,等待中断iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);input_dev = input_allocate_device(); /分配input设备if (!input_dev) printk(KERN_ERR "Unable to allocate the input device !n");return -ENOMEM;dev = input_dev;/支持按键事件、坐标事件dev->evbit0 = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);dev->keybitBITS_TO_LONGS(BTN_TOUCH) = BIT(BTN_TOUCH);/对于X轴范围是0-ox3ff,数据误差是0,中心平滑位置是0input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);dev->name = s3c2410ts_name;dev->id.bustype = BUS_RS232;dev->id.vendor = 0xDEAD;dev->id.product = 0xBEEF;dev->id.version = S3C2410TSVERSION;/申请AD转换中断if(request_irq(IRQ_ADC,stylus_acTIon,IRQF_SHARED|IRQF_SAMPLE_RANDOM,"s3c2410_acTIon", dev) printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !n");iounmap(base_addr);return -EIO;/申请触摸中断if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,"s3c2410_acTIon", dev) printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !n");iounmap(base_addr);return -EIO;printk(KERN_INFO "%s successfully loadedn", s3c2410ts_name);input_register_device(dev);return 0;下面是这个模块加载函数中调用的一个配置端口函数staTIc inline void s3c2410_ts_connect(void)/将触摸屏用到的四个端口配置成触摸屏模式s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON);s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON);s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON);s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON);我们来分析两种情况,第一种情况,如果没有按下触摸屏驱动中定义了一个定时器static struct timer_list touch_timer =TIMER_INITIALIZER(touch_timer_fire, 0, 0);因为这个定时器的期限时间设置为0,这表示当驱动加载后就会执行一次定时函数touch_timer_firestatic void touch_timer_fire(unsigned long data)unsigned long data0;unsigned long data1;int updown;data0 = ioread32(base_addr+S3C2410_ADCDAT0); /读取X坐标data1 = ioread32(base_addr+S3C2410_ADCDAT1); /读取Y坐标updown = (!(data0 /触摸屏是否被按下,如果按下updowm=1if (updown) if (count != 0) long tmp; tmp = xp;xp = yp;yp = tmp; xp >>= 2;yp >>= 2;input_report_abs(dev, ABS_X, xp);input_report_abs(dev, ABS_Y, yp);input_report_key(dev, BTN_TOUCH, 1);input_report_abs(dev, ABS_PRESSURE, 1);input_sync(dev);xp = 0; yp = 0;count = 0;iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); else /没有被按下count = 0; /初始化count为0,表示当前AD转换没发生input_report_key(dev, BTN_TOUCH, 0); /向input子系统报告未按下input_report_abs(dev, ABS_PRESSURE, 0);input_sync(dev);iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); /等待按键中断if (OwnADC) /OwnADC是获取一把锁标示,在此为0OwnADC = 0;up(第二种情况,如果触摸屏被按下,首先触发触摸中断,执行stylus_updown函数static irqreturn_t stylus_updown(int irq, void *dev_id)unsigned long data0;unsigned long data1;int updown;if (down_trylock(">OwnADC = 1; /表示获得锁data0 = ioread32(base_addr+S3C2410_ADCDAT0); /读取X轴数据data1 = ioread32(base_addr+S3C2410_ADCDAT1); /读取Y轴数据updown = (!(data0 /触摸屏是否被按下,按下updowm=1if (updown) touch_timer_fire(0); / 如果触摸屏被按下,执行touch_timer_fire else /去抖动操作,释放锁OwnADC = 0;up(return IRQ_HANDLED;下面我们第二次分析touch_timer_fire,而这次主要是因为触摸中断中调用了这个函数,假设当前触摸屏被按下后,坐标值还没进行AD转换static void touch_timer_fire(unsigned long data)unsigned long data0;unsigned long data1;int updown;data0 = ioread32(base_addr+S3C2410_ADCDAT0);data1 = ioread32(base_addr+S3C2410_ADCDAT1);updown = (!(data0 if (updown) /触摸屏被按下if (count != 0) /count是全局变量,统计AD转换次数,目前未AD转换long tmp; tmp = xp;xp = yp;yp = tmp; xp >>= 2;yp >>= 2;input_report_abs(dev, ABS_X, xp);input_report_abs(dev, ABS_Y, yp);input_report_key(dev, BTN_TOUCH, 1);input_report_abs(dev, ABS_PRESSURE, 1);input_sync(dev);xp = 0; /虽然触摸屏被按下,但是未完成AD转换yp = 0;count = 0;/自动连续测量X和Y坐标iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);/AD转换开始且该位在开始后清零iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); else count = 0;input_report_key(dev, BTN_TOUCH, 0);input_report_abs(dev, ABS_PRESSURE, 0);input_sync(dev);iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);if (OwnADC) OwnADC = 0;up(现在我们知道,如果触摸屏被按下,但是AD还没转换完毕,那么我们会开启AD转换,自动测量X和Y坐标,这样就会触发AD转换中断,执行AD转换的中断处理程序。其实当我们的触摸屏被按下,当X和Y轴获取电压值,然后就会进行AD转换,执行AD转换的中断处理程序。好了,我们该看看AD转换的中断代码了。static irqreturn_t stylus_action(int irq, void *dev_id)unsigned long data0;unsigned long data1;if (OwnADC) /只有触摸屏被按下,相应了触摸中断该标志才为1data0 = ioread32(base_addr+S3C2410_ADCDAT0); /读取X坐标data1 = ioread32(base_addr+S3C2410_ADCDAT1); /读取Y坐标xp += data0 /叠加X坐标yp += data1 /叠加Y坐标count+; /统计AD转换次数if (count >= 2; /因为对同一个点采样四次,这里对X轴取平均yp >>= 2; /因为对同一个点采样四次,这里对Y轴取平均input_report_abs(dev, ABS_X, xp); /报告X坐标input_report_abs(dev, ABS_Y, yp); /报告Y坐标input_report_key(dev, BTN_TOUCH, 1); /报告触摸事件input_report_abs(dev, ABS_PRESSURE, 1);input_sync(dev); /同步xp = 0; /清零yp = 0;count = 0;/自动连续测量X和Y坐标iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);/AD转换开始且该位在开始后清零iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); else count = 0;input_report_key(dev, BTN_TOUCH, 0);input_report_abs(dev, ABS_PRESSURE, 0);input_sync(dev);iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);if (OwnADC) OwnADC = 0;up(这里首先解释下,为什么要对X和Y坐标值交换,因为我们的X35LCD屏是240*320的,为了满足用户“X轴长Y轴短”的习惯,在此进行X和Y坐标值交换,当然在此不交换,不会影响触摸屏驱动的运行。我们这次分析这个定时器touch_timer_fire,最终发现我们不但向input子系统报告了我们的坐标值,还对坐标变量和统计AD转换次数变量清零,最后还打开了AD转换开关,既然这次触摸事件已经结束,那么这里怎么还打开AD转换开关呢?其实,如果你足够细心,你会发现,虽然AD开关打开了,然后会执行AD转换的中断处理程序,一旦进入AD转换,肯定也是转换四次,然后在下一个节拍到来时,执行定时程序,最关键的是,此时我们的触摸屏按键已经释放了,在这一的背景下,我们再次跟踪定时函数touch_timer_firestatic void touch_timer_fire(unsigned long data)unsigned long data0;unsigned long data1;int updown;data0 = ioread32(base_addr+S3C2410_ADCDAT0); /读取X坐标data1 = ioread32(base_addr+S3C2410_ADCDAT1); /读取Y坐标updown = (!(data0 /触摸屏是否被按下,如果按下updowm=1if (updown) if (count != 0) long tmp; tmp = xp;xp = yp;yp = tmp; xp >>= 2;yp >>= 2;input_report_abs(dev, ABS_X, xp);input_report_abs(dev, ABS_Y, yp);input_report_key(dev, BTN_TOUCH, 1);input_report_abs(dev, ABS_PRESSURE, 1);input_sync(dev);xp = 0; yp = 0;count = 0;iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); else /没有被按下count = 0; /初始化count为0,表示当前AD转换没发生input_report_key(dev, BTN_TOUCH, 0); /向input子系统报告未按下input_report_abs(dev, ABS_PRESSURE, 0); /触摸屏是抬起状态input_sync(dev);iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); /等待按键中断if (OwnADC) /OwnADC是获取一把锁标示,在此为1OwnADC = 0; /该锁标志为0,表示释放该锁up( /释放锁好了,一旦释放了锁,那么我们触摸屏事件的一个生命周期才真正算分析结束了,不过在此还有一个问题没有解决,就是当我们触摸屏按键被释放后,其实也会产生一个按键中断,执行触摸中断程序,不过在这个程序中起初是为了获得一个信号量,由于信号量是等待锁,所以,程序一直在试图获取这个信号量,当我们的信号量在上面这个touch_timer_fire中被释放后,我们就会再次获取这个信号量,继续跟踪这个触摸中断函数stylus_updownstatic irqreturn_t stylus_updown(int irq, void *dev_id)unsigned long data0;unsigned long data1;int updown;if (down_trylock(">OwnADC = 1;data0 = ioread32(base_addr+S3C2410_ADCDAT0);data1 = ioread32(base_addr+S3C2410_ADCDAT1);updown = (!(data0 if (updown) /因为是释放操作,所以updowm=0touch_timer_fire(0); else OwnADC = 0;up( /释放锁return IRQ_HANDLED;好了,这样我们就真正结束了一次触摸时间的周期。总结下触摸屏控制的整个运行过程:Step1:软件开启INT_ADC和INT_TS中断,设置ADCCON以确定AD转换需要的时钟频率,设置ADCDLY以确定从得到命令到开始转换坐标的延时时长,设置ADCTSC使得触摸屏处于等待触笔按下状态Step2:当触笔按下,产生INT_TC中断,执行stylus_updown,stylus_updown首先判断中断产生的原因是不是触笔按下,是的话就调用定时函数touch_timer_fire,在touch_timer_fire中设置ADCTSC使得触摸屏准备进行自动X/Y轴转换状态,然后设置ADCCON启动坐标转换,结束stylus_updown。Step3:触摸屏在延迟指定时间后开始转换X/Y坐标,并将转换的结果保存到ADCDAT0和ADADAT1中,完成后发出INT_ADC中断,表示转换完成。Step4:进入INT_ADC中断处理程序stylus_action,获取X/Y轴坐标,然后进行四次坐标转换以求平均值,最后设置ADCTSC使得触摸屏处于等待触笔释放状态,同时当下一个节拍到来时,调用touch_timer_fire向input子系统报告坐标。Step5:当触笔释放,还会产生INT_TC中断,进入其中断处理程序,得到触笔释放的消息,最后设置ADCTSC使得触摸屏处于下一次等待触笔按下状态。三触摸屏驱动测试由于mini2440的触摸屏驱动是基于input子系统的,而input子系统给用户层提供的是input_event结构体,我们主要是在应用层接收这个结构体,然后对其类型进行分类,取出我们需要的数值。struct input_event struct timeval time;unsigned short type; /支持的类型,如EV_ABSunsigned short code; /支持的具体事件,如坐标事件的ABS_Xunsigned int value; /值;测试触摸屏驱动的应用层代码如下#include#include#include#include#includeint main(int argc, char *argv)int fd = -1;int num;size_t rb;int version;char name20;struct input_event ev;int i=0;if (fd = open("/dev/input/event0", O_RDONLY) arm-linux-gcc test.c o test超级终端:./test测试结果:(触笔按下触摸屏)event=ABS_X, value=505event=ABS_Y, value=334event=ABS_PRESSURE, value=1