一、触摸屏概述

触摸屏作为一种输入设备,是目前最简单、方便、自然的一种人机交互方式。按照触摸屏的工作原理和传输信息的介质,可以将触摸屏分为四种:电阻式、电容感应式、红外线式和表面声波式。每一种触摸屏都有其各自的优缺点,要了解哪种触摸屏适用于哪种场合,关键就在要了解每一类触摸屏的工作原理和特点。本节我们主要介绍的是4线电阻式触摸屏。

1.1 电阻式触摸屏

电阻式触摸屏利用压力感应进行控制。电阻触摸屏的主要部分是一块与显示器表面紧密结合的电阻薄膜屏,这是一种多层的复合薄膜,以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层涂层、在他们之间有许多细小的的透明隔离点把两层导电层隔开绝缘。所有的电阻式触摸屏都采用分压器原理来产生代表X坐标和Y坐标的电压。分压器是通过将两个电阻进行串联来实现的。电阻R1连接正参考电压VREF,电阻R2接地。两个电阻连接点处的电压测量值与R2的阻值成正比。当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本原理。实际上就是欧姆定律的巧妙运用!

1.2 电阻式触摸屏信号测量

4线触摸屏包含了两个阻性层,如下图所示:

当没有触摸按下时,X层和Y层是分离的,此时就测不到电压。

X坐标方向值测量:

将XP接到3.3V , XM接0V, YP和YM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时触摸屏控制器 (ADC)就可测量到YP输出的当前X坐标值1.66V,如下图:

Y坐标方向值测量:

将YP接3.3V , YM接0V, XP和XM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时触摸屏控制器 (ADC)就可测量到XP输出的当前Y坐标值1.66V,如下图:

二、触摸屏驱动模型

Linux内核中触摸屏驱动实现基于输入子系统架构,具体请移步 Linux - 输入子系统框架详解 ;

对于触摸屏驱动开发者,核心层和事件处理层由Linux内核提供,主要实现xxx_ts.c,

1)分配一个input_dev结构体

2)设置input_dev的成员

3)注册input_dev 驱动设备到内核中

4)设置触摸屏相关的硬件

触摸屏使用过程简单处理简述如下(流程):

按下,产生中断;

在中断服务程序里,启动ADC,转换X,Y坐标;

ADC结束,产生ADC中断;

在ADC中断处理函数里上报(input_event),启动定时器;

定时器时间到,执行2再次启动ADC,转换X,Y坐标;(处理长按、滑动)

松开

二、触摸屏驱动相关的重要数据结构和函数

在输入子系统架构中,会将设备抽象出一个input_dev结构体;它是驱动的主体。每个struct 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)绝对坐标事件,比如摇杆、触摸屏感应

//EV_MSC 其他事件,功能

//EV_LED LED灯事件

//EV_SND (sound)声音事件

//EV_REP 重复键盘按键事件

//(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)

//EV_FF 受力事件

//EV_PWR 电源事件

//EV_FF_STATUS 受力状态事件

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)]; //存放支持的绝对坐标值,存放下面4个absxxx[]

unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能

unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED

unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音

unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备

unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能

... ...

/*以下4个数组都会保存在上面成员absbit[]里,数组号为:ABS_xx ,位于include/linux/input.h */

/*比如数组0,标志就是ABS_X,以下4个的absXXX[0]就是表示绝对位移X方向的最大值、最小值... */

/*对于触摸屏常用的标志有:

ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向,比如绘图,越用力线就越粗)* /

int absmax[ABS_MAX + 1]; //绝对坐标的最大值

int absmin[ABS_MAX + 1]; //绝对坐标的最小值

int absfuzz[ABS_MAX + 1]; //绝对坐标的干扰值,默认为0,

int absflat[ABS_MAX + 1]; //绝对坐标的平焊位置,默认为0

... ...

2.2 分配/释放input_dev结构体

函数原型:struct input_dev *input_allocate_device(void)

参数说明 :无

返回值 : 分配到的input_dev结构体

函数原型:input_free_device(struct input_dev dev)

参数说明 :dev 待释放的input_dev结构体

2.3 注册/注销input_dev设备

函数原型:input_register_device(struct input_dev *dev)

参数说明 :*dev 注册的input_dev结构体

说       明 :注册一个input_dev,若有对应的驱动事件,则在/sys/class/input下创建这个类设备。

函数原型:input_unregister_device(struct input_dev *dev)

参数说明 :*dev 注销的input_dev结构体

说       明 :卸载/sys/class/input目录下的input_dev这个类设备;

2.4 事件支持(初始化)

函数原型:int set_bit(int nr,long * addr)

参数说明 :设置某个结构体成员 * addr里面的某位等于nr,支持这个功能

说       明 :告诉input输入子系统支持哪些事件,哪些按键;例如:

set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev类型)

struct input_dev中有两个成员为:

evbit:

事件类型(EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)

keybit:

按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)

2.5 设置绝对位移的支持参数

函数原型:input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)

参数说明 :*dev : 需要设置的input_dev结构体

axis : 需要设置的数组号,常用的有: ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向)

min : axis方向的最小值

max : axis方向的最大值

fuzz : axis方向的干扰值

flat : axis方向的平焊位置

2.5 上报EV_ABS事件

函数原型:input_report_abs(struct input_dev *dev, unsigned int code, int value);

参数说明 :*dev : 要上报哪个input_dev驱动设备的事件

code : EV_ABS事件里支持的哪个方向,比如X坐标方向则填入: ABS_X

value : 对应的方向的值,比如X坐标126

说       明 :该函数实际就是调用的input_event(dev, EV_ABS, code, value);

2.6 上报EV_KEY事件

函数原型:input_report_key(struct input_dev *dev, unsigned int code, int value);

2.7 同步事件通知,通知系统有事件上报

函数原型:input_sync(struct input_dev *dev) ;

参数说明 :*dev : 要上报哪个input_dev驱动设备的事件

说       明 :同步用于告诉input core子系统报告结束

三、触摸屏驱动实现

3.1 在init入口函数中:

1)分配一个input_dev结构体

2)设置input_dev的成员

-> 2.1)设置input_dev->evbit支持按键事件,绝对位移事件

(触摸屏:通过按键BTN_TOUCH获取按下/松开,通过绝对位移获取坐标)

-> 2.2)设置input_dev-> keybit支持BTN_TOUCH触摸屏笔尖按下

-> 2.3)设置input_dev-> absbit 支持ABS_X、ABS_Y、 ABS_PRESSURE

input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0);

input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0); // 0x3FF:最大值为10位ADC,

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启动延时时间设为最大值,使触摸按压更加稳定

->4.4)开启IRQ_TC笔尖中断、开启IRQ_ADC中断获取XY坐标

-> 4.5)初始化定时器,增加触摸滑动功能

->4.6)最后设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断

3.2 在出口函数中:

1)注销内核里的input_dev、

2)释放中断、删除定时器、iounmap注销地址、

3)释放input_dev、

3.3 在IRQ_TC中断函数中:

1)若判断笔尖为松开,设置寄存器ADCTSC =0XD3(按下中断)

2)若判断笔尖按下,设置为XY自动转换模式,启动一次ADC转换,ADC转换成功,会进入ADC中断

3.4 在IRQ_ADC中断函数中:

1)获取ADCDAT0的位[9:0],来算出XY方向坐标值

2)测量n次值保存在数组中,然后再次设置为XY自动转换模式,启动ADC

(PS:要启动ADC转换之前必须设置一次XY为自动转换模式,不然获取的数据会不准)

3)采集完毕,使用快速排序将n次值排序后,以最小值为基准,如有误差非常大的数,则舍弃,如果没有则打印数组的中间值,实现中值滤波。

(PS:

3.5 在定时器超时函数中:

1)判断ADCDAT0的bit15位,若还在按下再次启动ADC转换(实现触摸滑动功能)

2)若松开,设置寄存器ADCTSC =0XD3(按下中断)

代码如下:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

static struct input_dev *ts_dev;

static struct clk *ADC_CLK; //adc时钟

static struct timer_list ts_timer; //定时器

struct adc_regs{

unsigned long adccon;

unsigned long adctsc;

unsigned long adcdly;

unsigned long adcdat0;

unsigned long adcdat1;

unsigned long adcupdn;

};

static volatile struct adc_regs *adc_regs;

/*启动TC 函数*/

static void set_pen_down(void)

{

/* 设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断*/

adc_regs->adctsc = 0xd3;

}

static void set_pen_up(void)

{

/* 设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断*/

adc_regs->adctsc = 0x1d3;

}

/*启动ADC 转换函数*/

static void adc_start(void)

{

adc_regs->adctsc= (1<<3)| (1<<2); //启动XY自动转换

adc_regs->adccon|=(1<<0); //启动1次ADC转换

}

/*快速排序,比冒泡更快*/

/*快速排序详解:http://www.cnblogs.com/lifexy/p/7597276.html*/

static void find_frst(int *s,int lift,int right)

{

int i=lift,j=right,temp; //(1)初始化i、j

if(lift>=right)

return ;

temp=s[i]; //(2)以第一个数组为比较值,保存到temp中

while(i

{

while(j>i&&s[j]>=temp) //(3)j--,找小值

j--;

s[i]= s[j]; //保存小值,到s[i]上

while(i

i++;

s[j--]=s[i]; //保存大值 到s[j]上

}

s[i]=temp; //(5)将比较值放在s[i]上

/*(6)拆分成两个数组 s[0,i-1]、s[i+1,n-1]又开始排序 */

find_frst(s,lift,i-1); //左

find_frst(s,i+1,right); //右

}

/*查找X Y坐标偏移值是否太大*/

/*return: 0误差大, 1误差小 */

static int find_xy_offset(int x[], int y[],int n)

{

int i;

for(i=n;i>=1;i--)

{

if(x[i]-x[i-1]>10) //判断是否大于误差10,

return 0;

if(y[i]-y[i-1]>10)  //判断是否大于误差10,

return 0;

}

return 1;

}

/*定时器函数,实现触摸滑动功能 */

void pen_updown_timer(unsigned long cnt)

{

if((adc_regs->adcdat0>>15)&0x01) //此时笔尖已经抬起

{

set_pen_down(); //设置TC中断

}

else

{

adc_start(); //启动一次ADC转换

}

}

/*触摸中断IRQ_TC */

static irqreturn_t tc_handler(int irq, void *dev_id)

{

if((adc_regs->adcdat0>>15)&0x01) //此时笔尖已经抬起

{

set_pen_down();

}

else

{

adc_start(); //启动一次ADC转换

}

return IRQ_HANDLED;

}

/*ADC中断IRQ_ADC:测XY坐标 */

static irqreturn_t adc_handler(int irq, void *dev_id)

{

static int adc_x[5],adc_y[5]; //保存XY坐标

static unsigned char xy_cnt=0; //计数

adc_y[xy_cnt] =adc_regs->adcdat1&0x3ff; //10位ADC

adc_x[xy_cnt] =adc_regs->adcdat0&0x3ff; //10位ADC

if (adc_regs->adcdat0 & (1<<15))

{

/* 已经松开 */

xy_cnt = 0;

set_pen_down();

}

else{

xy_cnt++;

if(xy_cnt>=5)

{

xy_cnt=0;

find_frst(adc_x,0,4); // 快速排序X

find_frst(adc_y,0,4); // 快速排序y

if(find_xy_offset(adc_x,adc_y,4))

{

printk("X: %04d,y: %04d \n",adc_x[2],adc_y[2]); //中值滤波

}

set_pen_up();

mod_timer(&ts_timer ,jiffies+HZ/100); //启动定时10ms

}

else //在测一次

{

adc_start(); //启动 ADC

}

}

return IRQ_HANDLED;

}

/*入口函数*/

static int myts_init(void)

{

/*1. 申请input_dev */

ts_dev=input_allocate_device();

/*2. 设置input_dev*/

set_bit(EV_ABS, ts_dev->evbit);

set_bit(EV_KEY, ts_dev->evbit);

set_bit(BTN_TOUCH, ts_dev->keybit);

input_set_abs_params(ts_dev, ABS_X , 0 , 0x3ff , 0 , 0); //adc是个10位的,所以为0X3FF

input_set_abs_params(ts_dev, ABS_Y , 0 , 0x3ff , 0 , 0); //adc是个10位的,所以为0X3FF

input_set_abs_params(ts_dev, ABS_PRESSURE, 0 , 1 , 0 , 0); //adc是个10位的,所以为0X3FF

/*3.注册input_dev 驱动设备到内核中*/

input_register_device(ts_dev);

/*4.设置触摸屏相关的硬件*/

/*4.1 开启ADC时钟 */

ADC_CLK =clk_get(0,"adc");

clk_enable(ADC_CLK);

/*4.2 设置寄存器ADCCON分频,*/

adc_regs=ioremap(0x58000000, sizeof(struct adc_regs));

adc_regs->adccon=(1<<14)|(49<<6); //50Mhz/(49+1)=1Mhz

/*4.3 设置中断IRQ_TC IRQ_ADC */

request_irq(IRQ_TC , tc_handler, IRQF_SAMPLE_RANDOM, "pen_updown", 0);

request_irq(IRQ_ADC , adc_handler, IRQF_SAMPLE_RANDOM, "adc" , 0);

/*4.4设置寄存器ADCDLY=0xffff */

adc_regs->adcdly =0xffff;

/*4.5 初始化定时器*/

init_timer(&ts_timer);

ts_timer.function =pen_updown_timer;

add_timer(&ts_timer);

/*4.6设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断*/

set_pen_down();

return 0;

}

/*出口函数*/

static void myts_exit(void)

{

/*1.注销内核里的input_dev、*/

input_unregister_device(ts_dev);

/*2.释放中断、删除定时器、iounmap注销地址、*/

free_irq(IRQ_TC, NULL);

free_irq(IRQ_ADC, NULL);

del_timer(&ts_timer);

iounmap(adc_regs);

/*3.释放input_dev、*/

input_free_device(ts_dev);

}

module_init(myts_init);

module_exit(myts_exit);

MODULE_LICENSE("GPL");

linux 内核 触摸屏,7. Linux - 触摸屏(电阻屏)驱动程序实现相关推荐

  1. 编译Linux内核没有zImage,Linux 编译系统的简单介绍与内核编译安装

    这里不只是讲怎样编译.安装Linux内核的,更主要的是介绍内核的编译系统和各个重要的文件.最后还利用学到的编译.安装Linux内核去修改Linux的01调度变成随机调度.如果你只是需要编译.安装内核的 ...

  2. Linux内核及主流Linux发行版对应关系汇总

    Linux内核及主流Linux发行版对应关系汇总 如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 文章目录 Linux内核及主流Lin ...

  3. linux内核 can总线,Linux Canbus调试笔记

    http://blog.csdn.net/flydream0/article/details/8161418 CAN总线在嵌入式Linux下驱动程序的实现 http://www.21ic.com/ap ...

  4. Linux 内核及 GNU/Linux 操作系统的基本体系结构

    1. Linux 内核简介 内核:在计算机科学中是一个用来管理软件发出的数据 I/O(输入与输出)要求的计算机程序,将这些要求转 译为数据处理的指令并交由中央处理器(CPU)及计算机中其他电子组件进行 ...

  5. 手机linux内核版本了解,Linux内核版本介绍与查询

    Linux内核版本命名在不同时期有着不同的规范,在涉及到Linux版本问题时经常容易混淆,主线版本/稳定版/长期支持版本经常搞不清楚,本文主要记录下内核版本命名的规则以及如何查看Linux系统版本信息 ...

  6. linux内核精髓:精通linux内核必会的75个绝技,Linux内核精髓精通Linux内核必会的75个绝技 PDF 高清扫描版...

    经过近20年的发展,Linux操作系统已经成为当今最成功的开源软件之一,使用广泛,影响深远.随着Linux操作系统功能的不断丰富和完善,Linux内核的源代码也从最初的几万行增加到如今的数百万行,庞大 ...

  7. 【Linux 内核】编译 Linux 内核 ⑤ ( 查看 .config 编译配置文件 | 正式编译内核 )

    文章目录 一.查看 .config 编译配置文件 二.正式编译内核 一.查看 .config 编译配置文件 在上一篇博客 [Linux 内核]编译 Linux 内核 ④ ( 打开 Linux 内核编译 ...

  8. 【Linux 内核】编译 Linux 内核 ① ( 下载指定版本的 Linux 内核源码 | Linux 内核版本号含义 | 主版本号 | 次版本号 | 小版本号 | 稳定版本 )

    文章目录 一.下载 Linux 内核 1.下载最新版本 Linux 内核 2.下载指定版本 Linux 内核 二.Linux 内核版本号含义 一.下载 Linux 内核 1.下载最新版本 Linux ...

  9. 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...

  10. 武汉linux内核好找吗,Linux内核入门

    Linux内核入门 收藏 如何获取Linux内核源代码 下载Linux内核当然要去http://www.kernel.org/了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补 ...

最新文章

  1. html多窗口排列顺序,多窗口页面(Frames)
  2. 电气6机30节点数据介绍(常适用于优化调度)
  3. Java学习之斐波那契数列实现
  4. 人性歪曲的心理调适 一【虚荣心理、投机心理、狭隘心理、吝啬心理、逆反心理】...
  5. Shutdown In Period 1.0
  6. jQuery 点击图片放大 灯箱效果
  7. 你的爬虫是否徘徊在违法的边缘?
  8. 转载知乎大神设置普通路由器支持IPV6
  9. python少儿编程课件ppt_《Python 少儿趣味编程》
  10. 一张图看懂DC ICC PT的关系
  11. 31、通信卫士--拦截黑名单电话
  12. 运行moveit报错
  13. 数字电位器IC市场现状研究分析与发展前景预测报告
  14. win11突然断网,然后找不到wifi图标,记录一下
  15. 校企合作趋紧密化:联想集团CTO芮勇出任东南大学人工智能双院兼职院长
  16. eclipse中安装ant,详细
  17. 整点没用的——Knuth洗牌算法
  18. Frp 内网穿透配置文件
  19. 《Using Cardio-Respiratory Signals to Recognize Emotions Elicited by Watching Music Video Clips》部分意译
  20. ssm+java计算机毕业设计基于云服务器网上论坛设计b1z90(程序+lw+源码+远程部署)

热门文章

  1. CSRF跨站请求伪造漏洞修复方案
  2. MTK车载平台实现MIPI转LVDS驱动移植
  3. f1c100linux系统吗,全志F1C100s怎么样 F1C100s芯片参数介绍
  4. 【单片机】按键消抖及原理(硬件和软件方法详解)
  5. 软件测试界的三无简历,企业拿什么来招聘你,石沉大海的简历
  6. java 遍历所有文件夹名_Java遍历文件夹下所有文件并重新命名
  7. Web版SSH客戶端Sshwifty
  8. Python贴吧小爬虫
  9. JAVA常量池,一篇文章就足够入门了。(含图解)
  10. adobe 不适应 Max to分屏软件的修改