Linux驱动之触摸屏程序编写
Posted andy_fly
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动之触摸屏程序编写相关的知识,希望对你有一定的参考价值。
本篇博客分以下几部分讲解
所谓的电阻式触摸屏,只不过是在LCD屏幕上贴了一层膜,这层膜的大小与LCD的尺寸刚好相同,它分为上下两层膜(假设上层为X膜,下层为Y膜),按下膜的不同位置,会产生不同的电压值,这样根据不同的电压值可以确定触点的位置,这就是触摸屏的基本原理。其实是利用了最简单的电阻分压原理。
下面的图是四线式电阻触摸屏的原理图,它的四根线都接到芯片的IO口上
1、如图14.4是什么都不设置的触摸屏的情况,其中S1、S2、S3、S4是由芯片内部寄存器控制的,四线电阻的四根线其实等效于接到了这个四个开关上了。
2、如图14.5是将触摸屏设置为等待中断的情况,可以看到只要触摸板被按下,Y_ADC点的电压就会发生变化,然后就会产生一个中断事件报告CPU有按键按下待处理。
3、确定按键被按下后,通过设置触摸屏ADC相关寄存器,设置为自动测量坐标模式,首先会进入测量X坐标模式,如图14.6可以将Y_ADC的值测量出来
4、接着进入测量Y坐标模式,如图14.7可以将X_ADC的值测量出来。x,y坐标测量出来后会存放在相应寄存中,然后又会产生一个中断,等待CPU读取x,y的坐标值
5、CPU确认读取玩x,y坐标的值之后就进入等待松开模式,当Y_ADC的电压为VCC时就认为松开,并进入相应中断。等待CPU处理。
触摸对于linux内核来说也是一个输入的器件,与按键一样,只不过比按键的值丰富了许多。在Linux驱动之输入子系统简析已经介绍过了输入子系统的框架,这里再简单回顾一下。
输入子系统按框架可以分为设备驱动层、事件层、以及核心层。
整个调用过程如下:
app_read->evdev_read->kbtab_irq->input_report_key->input_event->evdev_event->evdev_read
应用层 事件层 设备层 核心层 核心层 事件层 事件层
如果要自己添加一个输入子系统的设备,只需要添加设备层的文件即可。
1、在里面添加设备层input_dev结构并初始化
2、编写中断处理程序
其实编写触摸屏的驱动程序就是编写一个输入子系统的设备层。它需要用的结构体有
1、struct input_dev结构体
struct input_dev { void *private; const char *name;//设备名字 const char *phys;//文件路径,比如 input/buttons const char *uniq; struct input_id id; unsigned long evbit[NBITS(EV_MAX)];//表示支持哪类事件,常用于以下几种事件(可以多选) //EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件 //EV_KEY 键盘事件 //EV_REL (relative)相对坐标事件,比如鼠标 //EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应 unsigned long keybit[NBITS(KEY_MAX)];//存放支持的键盘按键值 //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)、BTN_TOUCH(触摸屏的按键) unsigned long relbit[NBITS(REL_MAX)];//存放支持的相对坐标值 unsigned long absbit[NBITS(ABS_MAX)];//存放支持的绝对坐标值 unsigned long mscbit[NBITS(MSC_MAX)]; unsigned long ledbit[NBITS(LED_MAX)]; unsigned long sndbit[NBITS(SND_MAX)]; unsigned long ffbit[NBITS(FF_MAX)]; unsigned long swbit[NBITS(SW_MAX)]; ... ... int absmax[ABS_MAX + 1];//绝对坐标的最大值 int absmin[ABS_MAX + 1];//绝对坐标的最小值 int absfuzz[ABS_MAX + 1];//绝对坐标的干扰值,默认为0, int absflat[ABS_MAX + 1];//绝对坐标的平焊位置,默认为0 ... ... };
2、struct timer_list结构体(用于触摸屏的长按处理)
struct timer_list { struct list_head entry; unsigned long expires;//期望定时器执行的jiffies值,假设需要定时10ms,那么应该设置expires = jiffies + HZ/100(10ms为100hz) void (*function)(unsigned long);//定时器定时时间到了之后的回调函数 unsigned long data;//回调函数传入的数据 struct tvec_t_base_s *base; #ifdef CONFIG_TIMER_STATS void *start_site; char start_comm[16]; int start_pid; #endif };
触摸屏程序用到的函数众多大致分为以下几类:
1、输入子系统相关的函数
struct input_dev *input_allocate_device(void);//分配一个struct input_dev结构体,返回的是struct input_dev * inline void set_bit(int nr, volatile unsigned long *addr);//这是一个内联函数,在调用的时候展开,功能为设置*addr的nr位为1 inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);//设置绝对位移的参数 //struct input_dev *dev表示哪个输入子系统设备的绝对位移参数 //axis表示哪个坐标的绝对位移参数,可以设置为ABS_X、ABS_Y、ABS_PRESSURE等,位于include/linux/input.h中 //min绝对位移坐标的最小值 //max绝对位移坐标的最大值 //fuzz绝对位移坐标的干扰值,默认为0 //flat绝对位移坐标的平焊位置,默认为0 int input_register_device(struct input_dev *dev);//注册输入子系统设备驱动,输入参数为struct input_dev * void input_unregister_device(struct input_dev *dev);//反注册输入子系统的设备驱动,输入参数为struct input_dev * void input_free_device(s3c_ts_input);//释放分配的input_dev结构,,输入参数为struct input_dev * static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);//上传绝对位移事件 //struct input_dev *dev表示哪个输入子系统设备的事件上传 //code表示绝对位移事件的哪类事件,可以取值ABS_PRESSURE、ABS_X、ABS_Y //value表示事件的按键值 static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);//上传按键事件 //struct input_dev *dev表示哪个输入子系统设备的事件上传 //code表示按键事件的哪类事件,可以取值BTN_TOUCH等 //value表示事件的按键值 static inline void input_sync(struct input_dev *dev);//上传同步事件,表示这次事件数据已经传送完成了
2、中断相关的函数
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id);//注册一个中断 //返回0表示注册成功,反之注册失败 //irq表示注册的中断号:有IRQ_TC、IRQ_ADC等等,位于include\\asm-arm\\arch-s3c2410irqs.h //handler表示中断出现后的回调函数,函数原型为typedef irqreturn_t (*irq_handler_t)(int, void *); //irqflags表示中断类型,可以设置为IRQF_SHARED、IRQF_DISABLED、IRQF_SAMPLE_RANDOM。位于include\\linux\\interrupt.h //char *devname表示中断的名称 //void *dev_id传给中断回调函数的参数 void free_irq(unsigned int irq, void *dev_id);//释放中断 //irq表示中断号 //void *dev_id为传给中断回调函数的参数
3、定时器相关的函数
void fastcall init_timer(struct timer_list *timer);//初始化一个定时器*timer inline void add_timer(struct timer_list *timer);//将定时器*timer将入内核 int mod_timer(struct timer_list *timer, unsigned long expires);//更改定时器*timer的定时值
程序的大致思路为:
1、分配一个input_dev结构体
2、设置input_dev结构体
3、注册input_dev结构体
4、硬件相关的操作
a、使能ADC时钟
b、设置S3C2440的ADC/TS寄存器
c、注册INT_TC中断
d、注册INT_ADC中断
f、设置一个定时器用于长按处理
程序流程:
1、首先触摸屏位于等待按下状态;
2、待按键按下后,进入INTC_TC中断,在中断里面设置为INT_ADC中断等待ADC转换完成;
3、转换完成后进入INT_ADC中断:在里面上报X、Y坐标值;改变定时器值,使得10ms后调用定时器处理函数;设置为等待松开模式。
4、若按键在10ms内还未松开,定时器处理函数里面将触摸屏设置为INT_ADC中断模式,继续转到第3步。
5、若在10ms内松开,则进入INTC_TC中断,在中断里面设置为等待按键模式
下面是完整程序
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <asm/io.h> //含有iomap函数iounmap函数 #include <asm/uaccess.h>//含有copy_from_user函数 #include <linux/device.h>//含有类相关的处理函数 #include <linux/fb.h> //含有fb_info结构体定义 //#include <asm/dma-mapping.h> //含有dma_free_writecombine宏定义 //#include <linux/dma-mapping.h> //含有dma_free_writecombine宏定义 #include <linux/platform_device.h>//含有平台设备总线模型相关变量 #include <linux/mm.h> #include <linux/slab.h> #include <linux/clk.h> #include <asm-generic/errno-base.h> //含有各种错误返回值 #include <linux/input.h> //含有输入子系统相关的类型 #include <linux/irq.h> //含有IRQ_HANDLED\\IRQ_TYPE_EDGE_RISING #include <asm/irq.h> //含有IRQT_BOTHEDGE触发类型 #include <linux/interrupt.h> //含有request_irq、free_irq函数 struct 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_input; //新建一个输入子系统的设备层结构 static struct ts_regs *ts_reg; //分配一个adc触摸相关的寄存器 static struct timer_list ts_timer; //定义一个定时器处理长按的情况 static void enter_wait_pen_down_mode(void)//进入等待触摸笔按下的模式 { ts_reg->adctsc = 0xd3;//设置等待按下 模式中断 } static void enter_wait_pen_up_mode(void)//进入等待触摸笔松开的模式 { ts_reg->adctsc = 0x1d3;//设置等待松开 模式中断 } static void enter_measure_xy_mode(void) { ts_reg->adctsc = ((1<<2) | (1<<3));//设置自动测量adc模式,上拉电阻必须去掉 ts_reg->adccon |= (1<<0);//启动adc转换 } static irqreturn_t pen_up_down_irq(int irq, void *dev_id) { if (ts_reg->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();//进入自动转换adc模式等待ADC转换完成进入ADC中断 } // if (ts_reg->adcdat1 & (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_filter_ts(int x[], int y[])//adc软件滤波 { #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 = (avr_x > x[2])?(avr_x - x[2]):(x[2] - avr_x); det_y = (avr_y > y[2])?(avr_y - y[2]):(y[2] - avr_y); 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 = (avr_x > x[3])?(avr_x - x[3]):(x[3] - avr_x); det_y = (avr_y > y[3])?(avr_y - y[3]):(y[3] - avr_y); if((det_x >ERR_LIMIT) || (det_y >ERR_LIMIT)) return 0; return 1; } static irqreturn_t adc_irq(int irq, void *dev_id) { static int x[4],y[4]; static int cnt=0; int xvalue; int yvalue; int i; /*优化措施2,如果ADC完成时,发现触摸笔已经松开,则丢弃此次结果*/ if (ts_reg->adcdat0 & (1<<15))//如果已经松开了 { cnt = 0;//不能漏 input_report_abs(s3c_ts_input,ABS_PRESSURE,0); //压力为0 input_report_key(s3c_ts_input,BTN_TOUCH,0); //0表示松开 input_sync(s3c_ts_input); //事件已经处理完 enter_wait_pen_down_mode();//直接进入等待按下模式 } else { /*优化措施3:多次测量求平均值*/ x[cnt] = ts_reg->adcdat0&0x3ff; y[cnt] = ts_reg->adcdat1&0x3ff; if(++cnt==4)//先++,再判断 { if(s3c_filter_ts(x,y))//如果差值小于10 { xvalue = 0; yvalue = 0; for(i=0;i<4;i++) { xvalue = x[i] + xvalue; yvalue = y[i] + yvalue; } xvalue /= 4; yvalue /= 4; input_report_abs(s3c_ts_input,ABS_X,xvalue); input_report_abs(s3c_ts_input,ABS_Y,yvalue); input_report_abs(s3c_ts_input,ABS_PRESSURE,1);//压力值为1 input_report_key(s3c_ts_input,BTN_TOUCH,1); //1表示按下 input_sync(s3c_ts_input);//事件已经处理完毕 //printk("x = %d ,y = %d\\n",xvalue,yvalue); mod_timer(&ts_timer,jiffies+HZ/100);// //enter_wait_pen_up_mode();//进入等待松开模式 } //else//如果差值大于10则丢弃 //{ enter_wait_pen_up_mode();//进入等待松开模式 //} cnt = 0; } else { enter_measure_xy_mode();//继续进入自动测量模式 } } return IRQ_HANDLED; } static void s3c_ts_timer_funcitin(unsigned long t) { if (ts_reg->adcdat0 & (1<<15))//如果已经松开了 { input_report_abs(s3c_ts_input,ABS_PRESSURE,0); //压力为0 input_report_key(s3c_ts_input,BTN_TOUCH,0); //0表示松开 input_sync(s3c_ts_input); //事件已经处理完毕 enter_wait_pen_down_mode();//直接进入等待按下模式 } else { enter_measure_xy_mode();//继续进入自动测量模式 } } static int s3c_ts_init(void) { struct clk *adc_clock; int ret; /* 1、分配一个input_dev结构体 */ s3c_ts_input = input_allocate_device();//在设备层分配一个input_dev结构 if (!s3c_ts_input) return -ENOMEM; /* 2、设置它 */ /* 2.1 能产生哪些事件 */ set_bit(EV_KEY, s3c_ts_input->evbit); //按键事件 set_bit(EV_ABS, s3c_ts_input->evbit);//绝对位移事件 /* 2.2 能产生这类事件里的哪些事件 */ set_bit(BTN_TOUCH, s3c_ts_input->keybit);//按键事件里的BTN_TOUCH事件 input_set_abs_params(s3c_ts_input, ABS_X, 0, 0x3FF, 0, 0); input_set_abs_params(s3c_ts_input, ABS_Y, 0, 0x3FF, 0, 0); input_set_abs_params(s3c_ts_input, ABS_PRESSURE, 0, 1, 0, 0); /* 3、注册 */ input_register_device(s3c_ts_input);//注册设备驱动 /* 4、硬件相关的操作 */ /* 4.1 使能时钟(CLKCON[15])*/ //为了省电,内核将不相关的外设时钟关掉 用的是PCLK时钟 adc_clock = clk_get(NULL, "adc"); clk_enable(adc_clock);//使能ACD时钟 /* 4.2 设置S3C2440的ADC/TS寄存器 */ ts_reg = ioremap(0x58000000, sizeof(struct ts_regs));//将ADC相关的寄存器的物理地址转换为虚拟地址 /*预分配使能、预分配系数=49+1;所以ADC时钟为1M*/ ts_reg->adccon = (1<<14) | (49<<6); /*优化措施1,设置ADCDLY为最大值,这使得电压稳定后再发出IRQ_TC中断*/ ts_reg->adcdly = 0xffffffff; //设置adc延时值,等待数值稳定后再读取相应的值 /*注册INT_TC中断*/ ret = request_irq(IRQ_TC, pen_up_down_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL); if(ret) return -1; /*注册INT_ADC中断*/ ret = request_irq(IRQ_ADC,adc_irq,IRQF_SAMPLE_RANDOM,"adc",NULL); if(ret) return -1; /*优化措施4:使用定时器处理长按滑动的情况*/ init_timer(&ts_timer); ts_timer.function = s3c_ts_timer_funcitin; add_timer(&ts_timer); /*进入等待中断模式*/ enter_wait_pen_down_mode();//等待进入按下中断 return 0; } static void s3c_ts_exit(void) { input_unregister_device(s3c_ts_input);//反注册 input_free_device(s3c_ts_input);//释放分配的input_dev结构 iounmap(ts_reg); free_irq(IRQ_TC, NULL);//释放触摸中断 free_irq(IRQ_ADC, NULL);//释放adc中断 } module_init(s3c_ts_init); module_exit(s3c_ts_exit); MODULE_LICENSE("GPL");
1、make menuconfog 去掉原来的触摸屏驱动程序
Device Drives
Input device supoport
Generic input layer
Touchscreens
<>S3c2410/s3c2440 touchscreens
2、make uImage
3、重启开发版,进入UBOOT,下载编译完成的uImage_nots文件到0x30000000地址处:nfs 30000000 192.168.1.5:/work/nfs_root/uImage_nots。然后bootm 30000000进入此内核
4、进入系统后挂接网络文件系统mount -t nfs -o nolock,vers=2 192.168.1.5:/work/nfs_root/first_fs /mnt
5、切换到/mnt目录。操作的就是服务器上的目录
6、制作tslib库,tslib库是介于触摸屏驱动程序与应用程序之间的接口。
a、从网上下载tslib-1.4.tar.gz安装包
b、tar xzf tslib-1.4.tar.gz解压
c、cd tslib切换到tslib目录
d、./autogen.sh运行
e、mkdir tmp新建一个temp目录
f、echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
g、./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp将安装目录设置为新建的tmp目录
h、make编译;make install安装
i、cd tmp切换到安装目录;cp * -rf /work/nfs_root/first_fs将当前所有文件都拷贝到网络文件系统下
7、使用tslib库,tslib库已经拷贝到了/mnt目录下
8、先安装10th_ts_drv.ko
9、修改 /etc/ts.conf第1行(去掉#号和第一个空格):
# module_raw input
改为:
module_raw input
10、配置环境变量
export TSLIB_TSDEVICE=/dev/event0 //使用的输入设备 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 //使用的显示设备
11、运行ts_calibrate程序进行触摸屏校验
12、运行ts_test可以进行写字、画图等操作。
以上是关于Linux驱动之触摸屏程序编写的主要内容,如果未能解决你的问题,请参考以下文章
Linux——Linux驱动之iMX6ULL平台下多点触摸屏驱动开发实战(MT协议多点触摸API基于框架的触摸驱动编写触摸芯片驱动)
Linux——Linux驱动之iMX6ULL平台下多点触摸屏驱动开发实战(MT协议多点触摸API基于框架的触摸驱动编写触摸芯片驱动)