十三、Linux驱动之触摸屏驱动

1. 基本概念

    常用的触摸屏类型有两种:阻性触摸屏和容性触摸屏。阻性触摸屏是一种传感器,它将矩形区域中触摸点(X, Y)的物理位置转换为代表X坐标和Y坐标的电压。触摸屏包含上下叠合的两个透明层阻性材料,中间由一种弹性材料隔开。当触摸屏表面受到压力时,顶层和底层之间会产生触碰。所用的电阻式触摸屏都采用分压器原理来产生代表X坐标和Y坐标的电压。如下图所示,分压器是通过将两个电阻进行串联来实现的。上面的电阻R1连接正参考电压Vref,下面的电阻R2接地。连个电阻连接点处的电压测量值与下面那个电阻的阻值成正比。

四线触摸屏示意图如下:

    四线触摸屏包含两个阻性层。其中一层在屏幕的左右边各有一条垂直总线,另一层在屏幕的底部和顶部各有一条水平总线,见上图。为了X轴方向进行测量,将左侧总线偏置为0V,右侧总线偏置为Vref。将顶部或底部总线连接到ADC,当顶层和底层相接触时即可做一次测量。

2. 分析设备

2.1 引脚连接

    本人使用的是JZ2440v3开发板,该开发板CPU使用的是S3C2440A,使用的是4线触摸屏,该4线连接在2440AIN4~AIN7引脚上,如图:

2.2 s3c2440触摸屏接口

查看s3c2440手册ADC & TOUCH SCREEN INTERFACE触摸屏章节触摸屏A/D转换器和触摸屏接口框图如下:


    值得注意的是两个中断产生的时间,INT_TC在点击触摸屏时发生,INT_ADC在数模转换完成时发生。手册中还提到了A/D的转换时间,如下公式:

    ADC的工作频率最大为2.5MHZ,正确使用需要设置寄存器ADCCON->PRSCVL更改分频系数,对于JZ2440这里要设置ADC的工作频率为1MHZ(即设置ADCCON的bit6为49)
s3c2440手册提到了触摸屏接口的4种模式:
    1. 正常转换模式
    2. 分离式X/Y位置转换方式
    3. 自动X/Y位置转换模式
    4. 等待中断模式
从数据手册中我们可以得到设置模式相关信息:
    1. 设置为自动X/Y位置转换模式
        设置寄存器ADCTSC=0x0c,系统会把X坐标存入寄存器ADCDAT0,把Y坐标存入ADCDAT1。
    2. 设置为等待中断模式
        设置寄存器ADCTSC=0xd3(设置等待按下中断信号到来)
        设置寄存器ADCTSC=0x1d3(设置等待松开中断信号到来)

触摸屏工作流程:
    1. 选择XY坐标转换模式(独立转换/连续转换)
    2. 设置触摸屏到等待状态
    3. 如果中断发生,启动ADC转换(转换获得的坐标值会存入相应的寄存器中)
    4. 获得XY坐标,返回到步骤2

3. 编写代码

    对于触摸屏驱动,也是使用输入子系统框架进行编写,输入子系统相关内容在九、Linux驱动之输入子系统分析里详细分析了,内核已经写好了驱动事件处理部分(触摸屏即对应tsdev.c),所以我们只需要写具体的驱动设备部分,然后内核会将两部分自动连接。先整理代码整体流程。

3.1 代码流程

3.1.1 在init入口函数中

    1. 分配一个input_dev结构体
    2. 设置input_dev的成员
        2.1 设置input_dev->evbit,即设置支持按键事件,绝对位移事件
       
2.2 设置input_dev-> keybit,支持按键事件的触摸屏笔尖按下事件
       
2.3 设置input_dev-> absbit,支持ABS_X、ABS_Y、 ABS_PRESSURE
           
2.3.1 input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0);  //ADC是10位,所以第四个参数设置为3FF
           
2.3.2 input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0);  //ADC是10位,所以第四个参数设置为3FF
           
2.3.3 input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0);  //压力最多就是1
   
3. 注册input_dev驱动设备到内核中
    4. 设置触摸屏相关的硬件
        4.1 开启ADC时钟,使用clk_get ()和clk_enable()函数
       
4.2 ioremap获取寄存器地址,设置寄存器ADCCON =(1<<14)|(49<<6),设置时钟分频
       
4.3 设置寄存器ADCDLY=0xffff,ADC启动延时时间设为最大值,使触摸电压稳定后再发出IRQ_TC中断
       
4.4 开启IRQ_TC中断、开启IRQ_ADC中断
       
4.5 初始化定时器,增加触摸滑动功能
       
4.6 最后设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断,即设置触摸屏进入等待按下中断模式(当触摸屏被按下,进入IRQ_TC中断函数)

3.1.2 在出口函数中

    1. 注销内核里的input_dev
   
2. 释放input_dev
   
3. 释放中断、删除定时器、iounmap注销地址

3.1.3 在IRQ_TC中断函数中

    1. 若判断触摸屏当前为松开状态,设置寄存器ADCTSC =0XD3,即设置触摸屏为等待按下模式
   
2. 若判断触摸屏当前为按下状态,设置为XY自动转换模式,启动一次ADC转换(ADC转换成功后,会进入IRQ_ADC中断函数)

3.1.4 在IRQ_ADC中断函数中

    1. 若判断触摸屏当前为松开状态,设置寄存器ADCTSC =0XD3,即设置触摸屏为等待按下模式
    2. 若判断触摸屏当前为按下状态,获取ADCDAT0/1的低10位,计算出X/Y方向坐标值。多次测量(多次设置为XY自动转换模式并启动ADC转换,转换完成就会再次进入IRQ_ADC中断函数),测量4次后判断精度,满足则上报数据,否则设置成等待松开模式,10ms后启动定时器超时函数

3.1.5 在定时器超时函数中

    1. 若判断触摸屏当前为松开状态,设置寄存器ADCTSC =0XD3,即设置触摸屏为等待按下模式
   
2. 若判断触摸屏当前为按下状态,设置为XY自动转换模式,启动一次ADC转换(ADC转换成功后,会进入IRQ_ADC中断函数)

3.2 编写代码

触摸屏驱动程序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>
#include <asm/plat-s3c24xx/ts.h>
#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>

static struct input_dev *s3c_ts_dev;

struct s3c_ts_regs{
	unsigned long adccon;
	unsigned long adctsc;
	unsigned long adcdly;
	unsigned long adcdat0;
	unsigned long adcdat1;
	unsigned long adcupdn;
};
static  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;
}

/*进入XY自动转换模式*/
static void enter_measure_xy_mode(void)
{
	s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}

/*启动一次ADC转换*/
static void start_adc(void)
{
	s3c_ts_regs->adccon |=(1<<0); 
}

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);//上报压力值为0
		input_report_key(s3c_ts_dev, BTN_TOUCH, 0);//上报BTN_TOUCH按键值松开
		input_sync(s3c_ts_dev);//上报同步事件,通知系统有事件上报
		enter_wait_pen_down_mode();///*进入等待按下模式*/
	}
	else
	{
		/* 进入测量X/Y坐标模式并启动一次ADC */
		enter_measure_xy_mode();
		start_adc();
	}
}
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 irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
	if (s3c_ts_regs->adcdat0 & (1<<15))		//如果触摸屏当前为松开状态
	{
		input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);//上报压力值为0
		input_report_key(s3c_ts_dev, BTN_TOUCH, 0);//上报BTN_TOUCH按键值松开
		input_sync(s3c_ts_dev);//上报同步事件,通知系统有事件上报
		enter_wait_pen_down_mode();///*进入等待按下模式*/
	}
	else
	{
		/* 进入测量X/Y坐标模式并启动一次ADC */
		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];

	if (s3c_ts_regs->adcdat0 & (1<<15))	//如果触摸屏当前为松开状态
	{
		input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);//上报压力值为0
		input_report_key(s3c_ts_dev, BTN_TOUCH, 0);//上报BTN_TOUCH按键值松开
		input_sync(s3c_ts_dev);//上报同步事件,通知系统有事件上报
		enter_wait_pen_down_mode();///*进入等待按下模式*/
		cnt=0;
	}
	else
	{
		x[cnt]=s3c_ts_regs->adcdat0 & 0x3ff;
		y[cnt]=s3c_ts_regs->adcdat1 & 0x3ff;
		cnt++;
		if(cnt==4)
		{
			if (s3c_filter_ts(x, y))
			{
				input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);//上报X方向值
				input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);//上报Y方向值
				input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);//上报压力方向值
				input_report_key(s3c_ts_dev, BTN_TOUCH, 1);//上报BTN_TOUCH按键值按下
				input_sync(s3c_ts_dev);//上报同步事件,通知系统有事件上报
			}
			cnt=0;
			enter_wait_pen_up_mode();
			/* 启动定时器处理长按/滑动的情况 */
			mod_timer(&ts_timer, jiffies + HZ/100);
		}
		else
		{
			/* 进入测量X/Y坐标模式并启动一次ADC */
			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);//s3c2410手册ADC是10位,所以第四个参数设置为3FF
	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));
	s3c_ts_regs->adccon = (1<<14)|(49<<6);
	
	/*
	*必须得进入等待按下中断或者等待松
	*开中断模式"ts_pen"这个中断才能在触摸
	*屏被按下或松开时被触发。
	*/
	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);

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

	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)
{
	input_unregister_device(s3c_ts_dev);
	input_free_device(s3c_ts_dev);

	free_irq(IRQ_TC,NULL);
	free_irq(IRQ_ADC,NULL);
	iounmap(s3c_ts_regs);
	
	del_timer(&ts_timer);
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");

Makefile完整代码如下:

KERN_DIR = /work/system/linux-2.6.22.6    //内核目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= s3c_ts.o

4. 测试

内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10

4.1 配置内核

    1. 重新配置内核,将内核自带的触摸屏驱动配置为模块。在linux-2.6.22.6内核目录下执行:
      make menuconfig
    2. 配置步骤如下:
      Device Drivers  --->
          Input device support  --->
              [*]   Touchscreens  --->
                  < >   S3C2410/S3C2440 touchscreens 
(将内核自带的触摸屏驱动去掉)

4.2 重烧内核

    1. 编译内核与模块
      make uImage
   
2. 将linux-2.6.22.6/arch/arm/boot下的uImage烧写到开发板,网络文件系统启动。

4.3 测试

     首先安装新驱动。在开发板目录上执行:
      Insmod s3c_ts.ko
      ls /dev/event*

     
      此时新出现了/dev/event0这个设备,就是我们写的触摸屏驱动。继续执行:
      hexdump /dev/event0
      此时再点一下触摸屏,串口端输入如下:
     
    第1列表示hexdump序列号(如0000000)
    第2、3列表示秒(如013c 0000)
    第4、5列表示微妙(如c7c6 0000)
    第6列表示type(如0003表示ABS绝对位移类型,0001表示按键类型)
    第7列表示code(如0000表示x方向ABS_X,0001表示y方向ABS_Y,0018表示ABS_PRESSURE,014a表示BTN_TOUCH)
    第8、9列表示对应type的对应code值(如020b 0000)

4.4 安装tslib

    1. tslib-1.4.tar.gz放到服务器上,在ubuntu执行以下命令:
      tar xzf tslib-1.4.tar.gz  (解压文件)
      cd tslib
      ./autogen.sh
      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)
      cp tmp /work/nfs_root/first_fs/ts_dir -rfd  (将tmp目录复制到网络文件系统目录)

    2. 网络文件系统启动开发板,进入刚才安装好的tslib目录,执行:
      cd /ts_dir/tmp
      cp * / -rfd 
(再将该目录里所有内容拷贝到开发板的根目录里)

    3. 安装lcd相关驱动,lcd相关内容参考十二、Linux驱动之LCD驱动开发板上执行命令如下:
      insmod cfbcopyarea.ko
      insmod cfbfillrect.ko
      insmod cfbimgblt.ko
      insmod lcd.ko 
(安装lcd相关驱动)
      insmod s3c_ts.ko  (安装触摸屏驱动)

    4. 修改 /etc/ts.conf文件,开发板上执行命令如下:
      vi /etc/ts.conf
     

    改为(去掉第二行的#号和第一个空格)
     

    5. 设置触摸屏的环境变量。开发板上执行命令如下:
      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   (lcd设备)

    6. 校验。开发板上执行如下:
      ts_calibrate  (开发板上此时会出现校验触摸点)
     

    7. 测试 。开发板上执行如下:
      ts_test  (就可以在lcd上绘画了!)
     
PS:在第1点中将tmp目录复制到网络文件系统目录时,将一些测试程序如ts_calibrate 、ts_test 等拷贝到了网络文件系统bin目录下。

猜你喜欢

转载自blog.csdn.net/qq_36576792/article/details/84587500