转载至: http://blog.csdn.net/xubin341719/article/details/7820492 向作者致予最崇高的敬意!!!
关键词:android 电容屏 tp ITO
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsung exynos 4210)
作者:xubin341719(欢迎转载,请注明作者)
一、电容屏工作原理
触摸屏的工作原理概括来说就是上报坐标值,X轴、Y轴的值。前面我们分析了电阻触摸屏,它是通过ADC来检测计算X、Y轴坐标值,下面我们分析一下电容触摸屏的工作原理,看它是如何去检测计算X、Y坐标的值。
与电阻式触摸屏不同,电容式触摸屏不依靠手指按力创造、改变电压值来检测坐标的。电容屏通过任何持有电荷的物体包括人体皮肤工作。(人体所带的电荷)电容式触摸屏是由诸如合金或是銦錫氧化物(ITO)这样的材料构成,电荷存储在一根根比头发还要细的微型静电网中。当手指点击屏幕,会从接触点吸收小量电流,造成角落电极的压降,利用感应人体微弱电流的方式来达到触控的目的。(这是为什么当你带上手套触摸屏幕时,没有反应的原因),下图可以清晰的说明电容屏的工作原理。
二、电容屏模组组成
触摸屏:也就是我们手触摸操作的透明部分;
触摸IC:当电容屏触摸到时,要解析到触点的位置坐标,就是通过这颗芯片去计算处理的。
1、电容式触摸屏的类型主要有两种:
(1)、表面电容式: 表面电容式利用位于四个角落的传感器以及均匀分布整个表面的薄膜,有一个普通的ITO层和一个金属边框,当一根手 指触摸屏幕时,从板面上放出电荷,感应在触 屏 的四角完成,不需要复杂的ITO图案;
(2)、投射式电容: 采用一个或多个精心设计,被蚀烛的ITO,这些 ITO层通过蛀蚀形成多个水平和垂直电极,采用成行/列交错同时带有传感功能的独立芯片。现在平板电脑、手机、车载等多用投射式电容,所以我们后面分析表明投射式电容的构成。
投射电容的轴坐标式感应单元矩阵 :轴坐标式感应单元分立的行和列,以两个交叉的滑条实现 X轴滑条 Y轴滑条 检测每一 格感应单元的电容变化。(示意图中电容,实际为透明的)
2、电容触摸屏分辨率,通道数;
上图所示,X,Y轴的透明电极电容屏的精度、分辨率与X、Y轴的通道数有关,通道越多,分辨率越高。
3、电容触屏的结构分类:
(1)、单层ITO
优点:成本 低,透过率 高,
缺点: 抗干扰能力 差
(2)、单面双层ITO
优点:性能 好,良率高
缺点:成本 较高
(3)、双面单层ITO
优点:性能好,抗静电能力强
缺点:抗干扰能力差
3、电容式触屏的分类及工作原理
(1)、自生电容式触摸屏
Cp-寄生电容
手指触摸时寄生电容增加:Cp’=Cp/Cfinger
检测寄生电容的变化量,确定手指触摸的位置
(2)、互电容式触摸屏
CM-耦合电容
手指触摸时耦合电容减小 ,检测耦合电容变化量,确定手指触摸的位置
四、为什么会出现鬼点,鬼点如何消除
1、为什么会出现鬼点?
当一个手指按下时,X、Y轴只有一个交叉点,两个同时按下时就会出现4个交叉点,如下图所示,我们不期望得到的点就是所说的鬼点。
2、消除鬼点的方法
(1)、分时法:基于时间的多点触摸,假设多点触摸 分时进行,操作间 隔续集毫秒;
(2)、分区法 :将整个触屏物理上分割几个区域 通过判断触摸进入推出 相应区域, 从而分出鬼点中分 出真实点。
关键词:android 电容屏 tp 工作队列 中断 多点触摸协议
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsung exynos 4210)
作者:xubin341719(欢迎转载,请注明作者)
参考网站:http://edsionte.com/techblog/archives/1582
这部分参考别人的多一点
电容屏驱动调试先了解Linux电容屏驱动中几个常用的概念:
中断下半部-工作队列;
input机制;
Linux与Android 多点触摸协议。
一、中断下半部-工作队列
1、中断
先看一下宋宝华先生的《linux设备驱动开发详解》里面对中断的描述吧。这本书个人感觉 写的比较好,从开始学驱动到现在,还能从中得到不少知识。
设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。如下图描述了Linux内核的中断处理机制。为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
其实上面这一段大致说明一个问题,那就是:中断要尽可能耗时比较短,尽快恢复系统正常调试,所以把中断触发、中断执行分开,也就是所说的“上半部分(中断触发)、底半部(中断执行)”,其实就是我们后面说的中断上下文。下半部分一般有tasklet、工作队列实现,触摸屏中中断实现以工作队列形式实现的,所以我们今天重点讲工作队列。
2、为什么还需要工作队列?
工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文(process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。
对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。因此,选择tasklet还是工作队列来完成下半部分应该不难选择。当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是你的最佳选择;否则,还是用tasklet吧。
3、中断上下文
有上面那个图可以看出上下两部分吧,就是上下文吧,这个比较好理解。
看下别人比较专业的解释:
在了解中断上下文时,先来回顾另一个熟悉概念:进程上下文(这个中文翻译真的不是很好理解,用“环境”比它好很多)。一般的进程运行在用户态,如果这个进程进行了系统调用,那么此时用户空间中的程序就进入了内核空间,并且称内核代表该进程运行于内核空间中。由于用户空间和内核空间具有不同的地址映射,并且用户空间的进程要传递很多变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。这样就产生了进程上下文。
所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容。当内核需要切换到另一个进程时(上下文切换),它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态继续执行。上述所说的工作队列所要做的工作都交给工作者线程来处理,因此它可以表现出进程的一些特性,比如说可以睡眠等。 对于中断而言,是硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理,中断上下文就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境。因此处于中断上下文的tasklet不会有睡眠这样的特性。
4、工作队列的使用方法
内核中通过下述结构体来表示一个具体的工作:
- struct work_struct
- {
- unsigned long pending;//这个工作是否正在等待处理
- struct list_head entry;//链接所有工作的链表,形成工作队列
- void (*func)(void *);//处理函数
- void *data;//传递给处理函数的参数
- void *wq_data;//内部使用数据
- struct timer_list timer;//延迟的工作队列所用到的定时器
- };
而这些工作(结构体)链接成的链表就是所谓的工作队列。工作者线程会在被唤醒时执行链表上的所有工作,当一个工作被执行完毕后,相应的work_struct结构体也会被删除。当这个工作链表上没有工作时,工作线程就会休眠。
(1)、通过如下宏可以创建一个要推后的完成的工作:
- DECLARE_WORK(name,void(*func)(void*),void*data);
(2)、也可以通过下述宏动态创建一个工作:
- INIT_WORK(struct work_struct*work,void(*func)(void*),void *data);
与tasklet类似,每个工作都有具体的工作队列处理函数,原型如下:
- void work_handler(void *data)
将工作队列机制对应到具体的中断程序中,即那些被推后的工作将会在func所指向的那个工作队列处理函数中被执行。
(3)、实现了工作队列处理函数后,就需要schedule_work函数对这个工作进行调度,就像这样:
- schedule_work(&work);
这样work会马上就被调度,一旦工作线程被唤醒,这个工作就会被执行(因为其所在工作队列会被执行)。
二、input子系统概述 可见文章基于 mini2440 电阻式触摸屏(三):Linux输入子系统(InputSubsystem):
按键、鼠标、触摸屏、电池信息等,都是通过input子系统上报。
三、Linux与Android 多点触摸协议
为了使用功能强大的多点触控设备,就需要一种方案去上报用户层所需的详细的手指触摸数据。这个文档所描述的多点触控协议可以让内核驱动程序向用户层上报任意多指的数据信息。
1、使用说明
单点触摸信息是以ABS承载并按一定顺序发送,如BTN_TOUCH、ABS_X、ABS_Y、SYNC。而多点触摸信息则是以ABS_MT承载并按一定顺序发送,如ABS_MT_POSITION_X、ABS_MT_POSITION_Y,然后通过调用input_mt_sync()产生一个 SYN_MT_REPORT event来标记一个点的结束,告诉接收方接收当前手指的信息并准备接收其它手指的触控信息。最后调用 input_sync()函数上报触摸信息开始动作并告诉接收方开始接收下一系列多点触摸信息。
协议定义了一系列ABS_MT事件,这些事件被分为几大类,充许只应用其中的一部份,多点触摸最小的事件集中应包括ABS_MT_TOUCH_MAJOR、ABS_MT_POSITION_X和 ABS_MT_POSITION_Y,以此来实现多点触摸。如果设备支持ABS_MT_WIDTH_MAJOR这个事件,那么此事件可以提供手指触摸接触面积大小。触摸方向等信息可以由ABS_MT_TOUCH_MINOR, ABS_MT_WIDTH_MINOR and ABS_MT_ORIENTATION提供。ABS_MT_TOOL_TYPE提供触摸设备的类别,如手或是笔或是其它。最后有些设备可能会支持ABS_MT_TRACKING_ID,用来支持硬件跟踪多点信息,即该点属于哪一条线等。
- 下面是两点触摸支持的最小事件集序列:
- ABS_MT_TOUCH_MAJOR
- ABS_MT_POSITION_X
- ABS_MT_POSITION_Y
- SYN_MT_REPORT //上报第一个点
- ABS_MT_TOUCH_MAJOR
- ABS_MT_POSITION_X
- ABS_MT_POSITION_Y
- SYN_MT_REPORT //上报第二个点
- ………… //完成多点上报
- SYN_REPORT //开始动作
2、Event原语
接触”一词用来描述一个物体直接碰到另一个物体的表面。
ABS_MT_TOUCH_MAJOR描述了主接触面的长轴,它和X,Y同一个单位,如果一个面的分辨率为X*Y,则ABS_MT_TOUCH_MAJOR的最大值为sqrt(X^2+Y^2)
- <span style="white-space:pre"> </span>ABS_MT_TOUCH_MINOR描述了接触面的短轴,如果接触面是圆形,它可以不用。
- ABS_MT_WIDTH_MAJOR描述了接触工具的长轴
- ABS_MT_WIDTH_MINOR描述了接触工具的短轴
- ABS_MT_TOUCH_MAJOR := max(X, Y)
- ABS_MT_TOUCH_MINOR := min(X, Y)
- ABS_MT_ORIENTATION := bool(X > Y)
以上四个参数可以用来生成额外的触摸信息,ABS_MT_TOUCH_MAJOR/ABS_MT_WIDTH_MAJOR的比率可以用来描述压力。
ABS_MT_ORIENTATION
ABS_MT_POSITION_X接触面的中心点X坐标
ABS_MT_POSITION_Y接触面的中心点Y坐标
ABS_MT_TOOL_TYPE描述接触工具类型,很多内核驱动无法区分此参数如手指及笔,如果是这样,该参数可以不用,协议目前支持MT_TOOL_FINGER和MT_TOOL_PEN两种类型。
ABS_MT_BLOB_ID形状集ID,集合几个点以描述一个形状,很多驱动没有形状属性,此参数可以不用。ABS_MT_TRACKING_ID描述了从接触开始到释放的整个过程的集合,如果设备不支持,此参数可是不用。
3、触摸轨迹
仅有少数设备可以明触的标识真实的 trackingID,多数情况下 trackingID只能来标识一次触摸动作的过程。
4、手势
多点触摸指定的应用是创建手势动作, TOUCH和 WIDTH参数经常用来区别手指的压力和手指间的距离,另外 MINOR类的参数可以用来区别设备的接触面的大小(点接触还是面接触),ORIENTATION可以产生旋转事件。
5、在Linux内核支持的基础上,Android在其2.0源码中加入多点触摸功能(android4.0中间层有所不同)
由此触摸屏在Android的frameworks被完全分为2种实现途径:单点触摸屏的单点方式,多点触摸屏的单点和多点方式。
在Linux的input.h中,多点触摸功能依赖于以下几个主要的软件位:
- ……
- #define SYN_REPORT0
- #define SYN_CONFIG1
- #define SYN_MT_REPORT2
- ……
- #define ABS_MT_TOUCH_MAJOR0x30
- #define ABS_MT_TOUCH_MINOR0x31
- #define ABS_MT_WIDTH_MAJOR0x32
- #define ABS_MT_WIDTH_MINOR0x33
- #define ABS_MT_ORIENTATION0x34
- #define ABS_MT_POSITION_X0x35
- #define ABS_MT_POSITION_Y0x36
- #define ABS_MT_TOOL_TYPE0x37
- #define ABS_MT_BLOB_ID0x38
- ……
在Android 中对应的软件位定义在RawInputEvent.java中:
- ……
- public class RawInputEvent {
- ……
- public static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
- ……
- public static final int ABS_MT_TOUCH_MAJOR = 0x30;
- public static final int ABS_MT_TOUCH_MINOR = 0x31;
- public static final int ABS_MT_WIDTH_MAJOR = 0x32;
- public static final int ABS_MT_WIDTH_MINOR = 0x33;
- public static final int ABS_MT_ORIENTATION = 0x34;
- public static final int ABS_MT_POSITION_X = 0x35;
- public static final int ABS_MT_POSITION_Y = 0x36;
- public static final int ABS_MT_TOOL_TYPE = 0x37;
- public static final int ABS_MT_BLOB_ID = 0x38;
- ……
- public static final int SYN_REPORT = 0;
- public static final int SYN_CONFIG = 1;
- public static final int SYN_MT_REPORT = 2;
- ……
在Android中,多点触摸的实现方法在具体的代码实现中和单点是完全区分开的。在Android代码的EventHub.cpp中,单点屏和多点屏由如下代码段来判定:
- int EventHub::open_device(const char *deviceName)
- {
- ……
- if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask)
- && test_bit(ABS_MT_POSITION_X, abs_bitmask)
- && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
- device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT;
- //LOGI("It is a multi-touch screen!");
- }
- //single-touch?
- else if (test_bit(BTN_TOUCH, key_bitmask)
- && test_bit(ABS_X, abs_bitmask)
- && test_bit(ABS_Y, abs_bitmask)) {
- device->classes |= CLASS_TOUCHSCREEN;
- //LOGI("It is a single-touch screen!");
- }
- ……
- }
我们知道,在触摸屏驱动中,通常在probe函数中会调用input_set_abs_params给设备的input_dev结构体初始化,这些input_dev的参数会在Android的EventHub.cpp中被读取。如上可知,如果我们的触摸屏想被当成多点屏被处理,只需要在驱动中给input_dev额外增加以下几个参数即可:
- input_set_abs_params(mcs_data.input, ABS_MT_POSITION_X, pdata->abs_x_min, pdata->abs_x_max, 0, 0);
- input_set_abs_params(mcs_data.input, ABS_MT_POSITION_Y, pdata->abs_y_min, pdata->abs_y_max, 0, 0);
- input_set_abs_params(mcs_data.input, ABS_MT_TOUCH_MAJOR, 0, 15, 0, 0);//相当于单点屏的ABX_PRESSURE
- input_set_abs_params(mcs_data.input, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0);//相当于单点屏的ABS_TOOL_WIDTH
由于多点触摸技术需要采集到多个点,然后再一起处理这些点,所以在软件实现中需要保证每一波点的准确性和完整性。因此,Linux内核提供了input_mt_sync(struct input_dev * input)函数。在每波的每个点上报后需要紧跟一句input_mt_sync(),当这波所有点上报后再使用input_sync()进行同步。
- 例如一波要上报3个点:
- ……
- input_mt_sync(input);
- ……
- input_mt_sync(input);
- ……
- input_mt_sync(input);
- input_sync(input);
- 注:即使是仅上报一个点的单点事件,也需要一次input_mt_sync。<span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;">
- </span></span>
android 电容屏(三):驱动调试之驱动程序分析篇
关键词:android 电容屏 tp 工作队列 中断 坐点计算 电容屏主要参数
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsung exynos 4210)
作者:xubin341719(欢迎转载,请注明作者)
以goodix的gt8105为例
一、总体架构
硬件部分:先看一个总体的图吧,其实触摸屏原理也比较简单,触摸屏和主控芯片间的联系,如下主要有三部分:
1、IIC部分,初始化gt8105的数据和传回主控制的坐标位置信息就是通过IIC这条线传输的;
2、INT,当gt8105初触摸时,会发出中断通知主控接收信息(坐标数据);
3、gt8105电源、复位这一部分,不同芯片有所不同,可以根据触摸屏芯片来配置。
软件部分:
二、电容触摸屏的主要参数(这部分面试的时候也许有人会问的)
记得刚出来找工作时有人问我一些问题,我答不上来,现在感觉很清晰(那时候刚毕业IIC我都说不全)1、IIC
(1)、clk370KHz~400KHz;
(2)、触摸屏工作在从模式,这个比较简单;
2、电容检测频率,也就是每秒检测的次数:(大概)
(1)、单指≥100Hz;
(2)、五指≥80Hz;
(3)、十指≥60Hz。
3、手指按下,没抬起时触发多少中断?
中断个数也就是检测频率,按下没提起一直有中断。这样我们就可有判断单点、划线之类的操作;
4、校准功能、自动校准(有个别电容屏没有的,用软件校准)
(1)、初始化校准
不同的温度、湿度及物理空间结构均会影响到电容传感器在闲置状态的基准值。一般电容触摸屏会在初始化的 200ms内根据环境情况自动获得新的检测基准。完成触摸屏检测的初始化。
(2)、 自动温漂补偿
温度、湿度或灰尘等环境因素的缓慢变化,也会影响到电容传感器在闲置状态的基准值。实时检测各点数据的变化,对历史数据进行统计分析,由此来修正检测基准。从而降低环境变化对触摸屏检测的影响。
5、推荐工作条件(环境温度为 25°C,VDD=2.8V)
参数 | 最小值 | 典型值 | 最大值 | 单位 |
模拟AVDD(参考AGND) | 2.5 | 2.8 | 3.6 | V |
数字DVDD(参考DGND) | 2.5 | 2.8 | 3.6 | V |
电源纹波 |
| 50(注意电池、充电器的影响) |
| mV |
工作温度 | -20 | +25 | +85 | 度 |
工作湿度 | - | - | 95 | % |
三、硬件接口电路:
如下图:
SDA | IIC数据 要上拉电阻,为1K; |
SCL | IIC 时钟(400KHz) |
TP_EN | 使能脚(gt8105为高电平) |
INT | 中断(一直点到触摸屏时中断是一直发出的) |
VCC | 3.3V 这个电压一直有 |
GND | 地 |
软件部分,整体流程如下:
三、IIC配置
设备到芯片的数据、初始化值都是从这条总线上传输的,首先我们要配置这个条总线,
/linux/arch/arm/mach-exynos/mach-smdkv310.c,这个因平台而已,地址右移也跟情况而定,如果本来就是7bit的地址就不用移位。
- static struct i2c_board_info i2c_devs5[] __initdata = {
- #if CONFIG_TOUCHSCREEN_GT8105
- {
- I2C_BOARD_INFO("Goodix-TS", (0xaa>>1)),
- .irq = IRQ_EINT(5),
- }
- #endif
- };
四、电源、复位(使能脚)
1、电源
3.3V的电源是一直有的,这个硬件上给就行了。
2、复位(时能脚),这个因触摸屏而已,gt8105工作时要高电平。
在:linux3.0/drivers/input/touchscreen/goodix_touch.h中
- #define RESETPIN_CFG s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT)
- #define RESETPIN_SET0 gpio_direction_output(EXYNOS4_GPB(4),0)
- #define RESETPIN_SET1 gpio_direction_output(EXYNOS4_GPB(4),1)
- static void goodix_reset(void)
- {
- int err;
- err = gpio_request(EXYNOS4_GPB(4), "GPX1");
- if (err)
- printk(KERN_ERR "#### failed to request GPB_4 ####\n");
- RESETPIN_CFG; //配置管脚功能
- RESETPIN_SET0;//管脚拉低
- mdelay(20); //延时
- RESETPIN_SET1;//管脚拉高
- mdelay(60);
- gpio_free(EXYNOS4_GPB(4));
- }
五、中断配置
在:linux3.0/drivers/input/touchscreen/goodix_touch.h中
- #define INT_PORT EXYNOS4_GPX0(5)
- #ifdef INT_PORT
- #define TS_INT IRQ_EINT(5)//中断引脚,中断号
- #define INT_CFG S3C_GPIO_SFN(0x0F)
- #else
- 在:linux3.0/drivers/input/touchscreen/goodix_touch.h中 中断申请
- #ifdef INT_PORT
- client->irq=TS_INT;
- if (client->irq)
- {
- ret = request_irq(client->irq, goodix_ts_irq_handler , IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,client->name, ts);
- #endif
六、驱动程序分析(完整代码见 goodix_touch.c/goodix_touch.h)
驱动有几个比较重要的部分:probe函数分析;中断申请、工作队列调度;中断下半部函数的执行,坐标值计算、上报。
1、probe函数分析
- static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
- {
- struct goodix_ts_data *ts;
- …………
- // 1,分配触摸屏结构内核空间;
- ts = kzalloc(sizeof(*ts), GFP_KERNEL);
- …………
- // 2,初始化工作队列,这个比较重要,中断触发后,调用队列中的goodix_ts_work_func函数,计算上报坐标值;
- INIT_WORK(&ts->work, goodix_ts_work_func);
- …………
- // 3, 触摸芯片初始化;
- for(retry=0; retry<3; retry++)
- {
- ret=goodix_init_panel(ts);
- …………
- }
- //4、触摸屏复位,拉高;
- goodix_reset();
- #ifdef INT_PORT
- // 5,中断申请,TS_INT就是我们所设定的中断脚;
- client->irq=TS_INT;
- ret = request_irq(client->irq, goodix_ts_irq_handler , IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,
- client->name, ts);
- ………………
- #endif
- // 6、分配input驱动内核空间;
- ts->input_dev = input_allocate_device();
- // 7,input初始化参数设定,我们在前面提到Linux与Android 多点触摸协议里有对这部分说明;
- ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;
- ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
- ts->input_dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); // absolute coor (x,y)
- #ifdef HAVE_TOUCH_KEY
- for(retry = 0; retry < MAX_KEY_NUM; retry++)
- {
- input_set_capability(ts->input_dev,EV_KEY,touch_key_array[retry]);
- }
- #endif
- input_set_abs_params(ts->input_dev, ABS_X, 0, ts->abs_x_max, 0, 0);
- input_set_abs_params(ts->input_dev, ABS_Y, 0, ts->abs_y_max, 0, 0);
- input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, 0, 0);
- //8、这部分针对触摸屏参数设定;
- #ifdef GOODIX_MULTI_TOUCH
- input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
- input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
- input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0);
- input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, ts->abs_y_max, 0, 0);
- input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, ts->max_touch_num, 0, 0);
- #endif
- //9、触摸屏版本信息设定;
- sprintf(ts->phys, "input/ts");
- ts->input_dev->name = goodix_ts_name;
- ts->input_dev->phys = ts->phys;
- ts->input_dev->id.bustype = BUS_I2C;
- ts->input_dev->id.vendor = 0xDEAD;
- ts->input_dev->id.product = 0xBEEF;
- ts->input_dev->id.version = 10427; //screen firmware version
- //10,对于input子系统来说,这个是重头戏了,只有注册了input子系统,其他的才有做用;
- ret = input_register_device(ts->input_dev);
- ………………
- // 11,对睡眠唤醒操作;
- #ifdef CONFIG_HAS_EARLYSUSPEND
- ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
- ts->early_suspend.suspend = goodix_ts_early_suspend;
- ts->early_suspend.resume = goodix_ts_late_resume;
- register_early_suspend(&ts->early_suspend);
- #endif
- ………………
- }
(1)、分配触摸屏结构内核空间;
- struct goodix_ts_data {
- uint16_t addr;
- uint8_t bad_data;
- struct i2c_client *client;
- struct input_dev *input_dev;
- int use_reset; //use RESET flag
- int use_irq; //use EINT flag
- int read_mode; //read moudle mode,20110221 by andrew
- struct hrtimer timer;
- struct work_struct work;
- char phys[32];
- int retry;
- struct early_suspend early_suspend;
- int (*power)(struct goodix_ts_data * ts, int on);
- uint16_t abs_x_max;
- uint16_t abs_y_max;
- uint8_t max_touch_num;
- uint8_t int_trigger_type;
- uint8_t green_wake_mode;
- };
(2)、初始化工作队列,这个比较重要,中断触发后,调用队列中的goodix_ts_work_func函数,计算上报坐标值;这个和中断申请一起分析;
(3)、触摸芯片初始化;
对触摸芯片寄存器的初始化,这里面对中断方式设定等,一般芯片厂的FAE在调试的时候会修改这里面的值,这个也是因芯片而异,有的在驱动里做,可以直接改;有的直接做成固件了,那部分要FAE帮忙了。
- uint8_t cfg_info_group1[] =
- {
- 0x65,0x00,0x25,0x80,0x19,0x00,0x00,0x2C,0x11,0x11,0x32,0x02,0x08,0x10,0x20,0x00,
- 0x00,0x88,0x88,0x88,0x03,0x13,0x32,0x64,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
- 0x08,0x09,0x0A,0x0B,0x0C,0xFF,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,
- 0x17,0x18,0x19,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
- 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
- 0x00,0x00,0x00,0x00
- };
(4)、触摸屏复位,拉高;
gt8015在工作时要拉高,所以我们做一个拉低—延时--拉高的操作;
(5)、中断申请,TS_INT就是我们所设定的中断脚,和(2)一起后面分析;
(6)、分配input驱动内核空间;
- ts->input_dev= input_allocate_device();
(7)、input初始化参数设定,我们在前面提到Linux与Android 多点触摸协议里有对这部分说明;(8)、这部分针对触摸屏参数设定;
(9)、触摸屏版本信息设定;
- cat /proc/bus/input/devices时可以看到下面信息(这个是pixcir的触摸屏)
- I: Bus=0018 Vendor=0000 Product=0000 Version=0000
- N: Name="pixcir-ts"
- P: Phys=
- S: Sysfs=/devices/platform/s3c2440-i2c.5/i2c-5/5-005c/input/input3
- U: Uniq=
- H: Handlers=kbd event3
- B: PROP=0
- B: EV=b
- B: KEY=400 0 0 0 0 1000 40000800 0 0 0 0
- B: ABS=2650000 1000000
(10)、对于input子系统来说,这个是重头戏了,驱动注册到input子系统;
- input_register_device(ts->input_dev);
(11),触摸屏睡眠唤醒操作,这部分不做详细说明,感兴趣的可以看下……
2、中断申请、工作队列调度
(1)、中断申请
- ret = request_irq(client->irq, goodix_ts_irq_handler , IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,
- client->name, ts);
- 第一个参数: 中断号,client->irq,client->irq=TS_INT;
- #define TS_INT IRQ_EINT(5)对应到我们要申请的中断;
- 第二个参数:中断执行函数,goodix_ts_irq_handler ;
- 第三个参数:中断触发方式:上升沿触发、下降沿触发、高电平触发、低电平触发
- IRQ_TYPE_EDGE_RISING,
- IRQ_TYPE_EDGE_FALLING,
- IRQ_TYPE_LEVEL_LOW,
- IRQ_TYPE_LEVEL_HIGH
- 第四个参数:
- 第五个参数:
(2)、中断处理函数 goodix_ts_irq_handler
- static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
- {
- struct goodix_ts_data *ts = dev_id;
- queue_work(goodix_wq, &ts->work);
- return IRQ_HANDLED;
- }
看下queue_work()这个函数中的两个参数:
a、goodix_wq
- goodix_wq=create_singlethread_workqueue("goodix_wq"); //createa work queue and worker thread
在函数 goodix_ts_init中,创建工作队列和工作线程,初始化时创建线程。
b、&ts->work
在函数goodix_ts_probe()中:
- INIT_WORK(&ts->work,goodix_ts_work_func);
在工作队列&ts->work中增加 goodix_ts_work_func任务。
也就是当中断函数触发时,执行中断函数goodix_ts_irq_handler(),中断函数里面对队列调度,调用队列中的goodix_ts_work_func()函数。
3、中断下半部函数的执行goodix_ts_work_func()函数
这就是核心部分,坐标点的计算、上报、多点处理都在这个函数中执行。
- static void goodix_ts_work_func(struct work_struct *work)
- {
- int ret=-1;
- int tmp = 0;
- uint8_t point_data[(1-READ_COOR_ADDR)+1+2+5*MAX_FINGER_NUM+1]={ 0 }; //read address(1byte)+key index(1byte)+point mask(2bytes)+5bytes*MAX_FINGER_NUM+coor checksum(1byte)
- uint8_t check_sum = 0;
- uint16_t finger_current = 0;
- uint16_t finger_bit = 0;
- unsigned int count = 0, point_count = 0;
- unsigned int position = 0;
- uint8_t track_id[MAX_FINGER_NUM] = {0};
- unsigned int input_x = 0;
- unsigned int input_y = 0;
- unsigned int input_w = 0;
- unsigned char index = 0;
- unsigned char touch_num = 0;
- struct goodix_ts_data *ts = container_of(work, struct goodix_ts_data, work);
- if(g_enter_isp)return;
- COORDINATE_POLL:
- if((ts->int_trigger_type> 1)&& (gpio_get_value(INT_PORT) != (ts->int_trigger_type&0x01)))
- {
- goto NO_ACTION;
- }
- if( tmp > 9) {
- dev_info(&(ts->client->dev), "I2C transfer error,touchscreen stop working.\n");
- goto XFER_ERROR ;
- }
- if(ts->bad_data)
- msleep(20);
- point_data[0] = READ_COOR_ADDR; //read coor address
- //1、读取触摸屏值,手指数、坐标值等;
- ret=i2c_read_bytes(ts->client, point_data, ((1-READ_COOR_ADDR)+1+2+5*ts->max_touch_num+1));
- …………
- //2、判断是否有手指按下;
- finger_current = (point_data[3 - READ_COOR_ADDR]<<8) + point_data[2 – READ_COOR_ADDR];
- if(finger_current)//3、如果有手指按下
- {
- point_count = 0, finger_bit = finger_current;
- //3,循环判断有多少手指按下;
- for(count = 0; (finger_bit != 0) && (count < ts->max_touch_num); count++)//cal how many point touch currntly
- {
- if(finger_bit & 0x01)
- {
- track_id[point_count] = count;
- point_count++;
- }
- finger_bit >>= 1;
- }
- //4、把按下手指数赋给touch_num;
- touch_num = point_count;
- //5、计算坐标值;
- check_sum = point_data[2 - READ_COOR_ADDR] + point_data[3 - READ_COOR_ADDR]; //cal coor checksum
- count = 4 - READ_COOR_ADDR;
- for(point_count *= 5; point_count > 0; point_count--)
- check_sum += point_data[count++];
- check_sum += point_data[count];
- if(check_sum != 0) //checksum verify error
- {
- printk("coor checksum error!\n");
- if(ts->int_trigger_type> 1)
- goto COORDINATE_POLL;
- else
- goto XFER_ERROR;
- }
- }
- //6、读取值坐标值上报;
- if(touch_num)
- {
- //7、touch_num为按下手指个数,依次循环读取;
- for(index=0; index<touch_num; index++)
- {
- position = 4 - READ_COOR_ADDR + 5*index;
- //8、读出X的值;
- input_x = (unsigned int) (point_data[position]<<8) + (unsigned int)( point_data[position+1]);
- //9、读出Y的值;
- input_y = (unsigned int)(point_data[position+2]<<8) + (unsigned int) (point_data[position+3]);
- input_w =(unsigned int) (point_data[position+4]);
- //10、如果读出值超出范围,退出;
- if((input_x > ts->abs_x_max)||(input_y > ts->abs_y_max))
- continue;
- //11、下面的函数依次上报坐标, input_mt_sync单点同步
- input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x);
- input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y);
- input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);
- input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w);
- input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, track_id[index]);
- input_mt_sync(ts->input_dev);
- }
- }
- //12、没有触摸时,初始值为0;
- else
- {
- input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0);
- input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0);
- input_mt_sync(ts->input_dev);
- }
- //13、同步多点值;
- input_sync(ts->input_dev);
- if(ts->int_trigger_type> 1)
- {
- msleep(POLL_TIME);
- goto COORDINATE_POLL;
- }
- goto END_WORK_FUNC;
- NO_ACTION:
- END_WORK_FUNC:
- XFER_ERROR:
- return;
- }
总的来数,当我们手指按下是,不管是单个手指,还是多个手指,坐标值和一些信息存储到触摸芯片的相应寄存器中,然后再通过IIC读出,送到主控中就可以了,其他事情就是android去处理了。
如下图所示,规格书中坐标及重量:XY坐标缓存寄存器的高低位:
android 物理按键
关键词:android 按键 矩阵按键 AD按键
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsung exynos4210)
作者:xubin341719(欢迎转载,请注明作者)
一、硬件部分:
1、矩阵按键、IO按键、AD按键
这个知识相对来说比较简单,不过上次真有一个网友不太清楚这个。所以这个基础部分我们在这里也说一下。
(1)、矩阵按键
记得上大学时学单片机时,这个矩阵按键还是个重点呢,上面的图还是AT89S52的片子,工作原理比较简单,通过行、列来确定是那个按键按下,比如说上图标号为1的键按下,IO(P1.7,P1.3)有电平变化,程序可以通过这里来判断是那一个键按下的,同理标号为2的按键按下IO(P1.4,P1.0)有电平变化。
这样做程序上要从两个IO来判断是那个键按下,多了一个步骤,但是在硬件上有一个优势,就是如果按键比较多的时候比较节省IO口,比如说上面4x4 = 16,8个IO可以做16个按键,8x8=64,16个IO可以做64个按键。
优点:可以用少的IO来做多个按键,判断按键比较准确;
缺点:程序上相对IO按键来说多了一步。
(2)、IO按键
这个就比较简单了,用一个IO口的高低电平来判断按键是否按下。
优点:程序、硬件电路都比较简单,判断按键比较准确;
缺点:IO有限、按键多时不太合适。比如矩阵按键16个IO可以表示64个按键,IO的话只有16个。
(3)、AD按键
这个在之前在做电视的时候用的比较多一点。
AD按键就是通过一个ADC接口,如下图所示,给一个VCC电压,比如说S1接地时AD接口得到的模拟电压值为ADC=0;当S2按下时,ADC= VCC/(R1+R2)*R2;这样就可以得到不同的ADC值,程序中在这里判断是那个按键按下。
优点:程序、硬件电路都比较简单,一个IO可以做多个按键;
缺点:AD按键有时候判断不准确,所以在程序中要多加检测AD值的次数。
2、S5PV310的矩阵按键
硬件原理图如下:
硬件接口说明:vol+,vol-,back,home,menu为1*5的矩阵键盘,芯片接口信息如下:
行 | XGNSS_GPIO_3/KP_COL3 XGNSS_GPIO_4/KP_COL4 XGNSS_GPIO_5/KP_COL5 XGNSS_GPIO_6/KP_COL6 XGNSS_GPIO_7/KP_COL7 |
列 | XEINT17/KP_ROW1 |
我们这里1*5= 5也没有节省多少IO呀?情况是这样的,我们的原理图是从三星开发板上参考过来的,开发板上按键本来多一点,可是我们用不了那么多,人家那样做比较合理。可是我们“偷懒”,硬件上不用改,软件上也不用改,从这一点也可以看出我们国内做技术这个行业的有点……不太深入呀,整天老板在催,可是我们在细节上做不太好呀。三星在IO矩阵也有专用接口,所以就“奢侈”一次,用1*5的矩阵来实现5个按键。
3、S5PV310的矩阵按键接口
看一下芯片上的专用接口,如下图,全用的话有点多。
关于专用接口的寄存器,这些寄存器我们后面要用得到的,按键的行、列信息会在这里面暂存的。
以S5PV310为例,驱动代码:samsung-keypad.c
软件部分:
总体流程图如下,这个是在触摸屏基础上改过来的,感觉流程都是这个样子的。中断触发,中断处理。
一、矩阵键行、列设定,和上报键值设定
在android-kernel-samsung-dev/arch/arm/mach-exynos/mach-smdkv310.c中
- static uint32_t smdkv310_keymap[] __initdata = {
- /* KEY(row, col, keycode) */
- KEY(0, 3, KEY_1), KEY(0, 4, KEY_2), KEY(0, 5, KEY_3),
- KEY(0, 6, KEY_4), KEY(0, 7, KEY_5),
- KEY(1, 3, KEY_A), KEY(1, 4, KEY_C), KEY(1, 5, KEY_E),
- KEY(1, 6, KEY_B), KEY(1, 7, KEY_D)//(1)、键值初始化;
- };
- static struct matrix_keymap_data smdkv310_keymap_data __initdata = {
- .keymap = smdkv310_keymap,
- .keymap_size = ARRAY_SIZE(smdkv310_keymap),
- };
- static struct samsung_keypad_platdata smdkv310_keypad_data __initdata = {
- .keymap_data = &smdkv310_keymap_data,
- .rows = 2, //(2)、行、列设定,8行、2列,其实我们只用了5行、1列;
- .cols = 8,
- };
- static void __init smdkv310_machine_init(void)
- {
- samsung_keypad_set_platdata(&smdkv310_keypad_data); //(3)、平台设备初始化;
- }
(1)、KEY(row, col,keycode)
KEY这个宏在android-kernel-samsung-dev/include/linux/input/Matrix_keypad.h中实现:
- #define MATRIX_MAX_ROWS 32
- #define MATRIX_MAX_COLS 32
- #define KEY(row, col, val) ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
- (((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
- ((val) & 0xffff))
keycode的值在android-kernel-samsung-dev/include/linux/input.h中有定义,如下:
- #define KEY_RESERVED 0
- #define KEY_ESC 1
- #define KEY_1 2
- #define KEY_2 3
- #define KEY_3 4
- #define KEY_4 5
- #define KEY_5 6
- #define KEY_6 7
- #define KEY_7 8
- #define KEY_8 9
- #define KEY_9 10
- #define KEY_0 11
- #define KEY_MINUS 12
- #define KEY_EQUAL 13
- #define KEY_BACKSPACE 14
- #define KEY_TAB 15
- #define KEY_Q 16
- #define KEY_W 17
- #define KEY_E 18
- #define KEY_R 19
- #define KEY_T 20
- #define KEY_Y 21
- #define KEY_U 22
(2)、行列设定;
- .rows = 2,
- .cols = 8,
(3)、平台设备初始化;
- samsung_keypad_set_platdata(&smdkv310_keypad_data)。
二、上面设定的keycode键值和上层相对应
4.0.3_r1/device/samsung/smdkv310/samsung-keypad.kl中
- key 2 DPAD_UP WAKE_DROPPED
- key 3 DPAD_CENTER WAKE_DROPPED
- key 4 DPAD_DOWN WAKE_DROPPED
- key 5 DPAD_RIGHT WAKE_DROPPED
- key 6 DPAD_LEFT WAKE_DROPPED
- key 18 VOLUME_DOWN WAKE
- key 30 HOME WAKE_DROPPED
- key 32 MENU WAKE_DROPPED
- key 46 VOLUME_UP WAKE
- key 48 BACK WAKE_DROPPED
- key 10 POWER WAKE
总体对应图:
以KEY_A为例,KEY_A 30最终和上层的keypad.kl中的30 HOME相对应
三、矩阵键盘驱动程序分析
android-kernel-samsung-dev/drivers/input/keyboard/samsung-keypad.c
1、probe函数分析:
- static int __devinit samsung_keypad_probe(struct platform_device *pdev)
- {
- const struct samsung_keypad_platdata *pdata;
- const struct matrix_keymap_data *keymap_data;
- struct samsung_keypad *keypad;
- struct resource *res;
- struct input_dev *input_dev;
- unsigned int row_shift;
- unsigned int keymap_size;
- int error;
- ………………
- keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]);
- keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL);
- input_dev = input_allocate_device();
- if (!keypad || !input_dev) {
- error = -ENOMEM;
- goto err_free_mem;
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- error = -ENODEV;
- goto err_free_mem;
- }
- keypad->base = ioremap(res->start, resource_size(res));
- if (!keypad->base) {
- error = -EBUSY;
- goto err_free_mem;
- }
- …………
- //(1)、input参数初始化;
- keypad->input_dev = input_dev;
- keypad->row_shift = row_shift;
- keypad->rows = pdata->rows;
- keypad->cols = pdata->cols;
- init_waitqueue_head(&keypad->wait);
- input_dev->name = pdev->name;
- input_dev->id.bustype = BUS_HOST;
- input_dev->dev.parent = &pdev->dev;
- input_set_drvdata(input_dev, keypad);
- //(2)、打开、关闭函数;
- input_dev->open = samsung_keypad_open;
- input_dev->close = samsung_keypad_close;
- input_dev->evbit[0] = BIT_MASK(EV_KEY);
- if (!pdata->no_autorepeat)
- input_dev->evbit[0] |= BIT_MASK(EV_REP);
- input_set_capability(input_dev, EV_MSC, MSC_SCAN);
- input_dev->keycode = keypad->keycodes;
- input_dev->keycodesize = sizeof(keypad->keycodes[0]);
- input_dev->keycodemax = pdata->rows << row_shift;
- matrix_keypad_build_keymap(keymap_data, row_shift,
- input_dev->keycode, input_dev->keybit);
- keypad->irq = platform_get_irq(pdev, 0);
- if (keypad->irq < 0) {
- error = keypad->irq;
- goto err_put_clk;
- }
- //(3)、中断函数注册;
- error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq,
- IRQF_ONESHOT, dev_name(&pdev->dev), keypad);
- if (error) {
- dev_err(&pdev->dev, "failed to register keypad interrupt\n");
- goto err_put_clk;
- }
- //(4)、input驱动注册。
- error = input_register_device(keypad->input_dev);
- if (error)
- goto err_free_irq;
- device_init_wakeup(&pdev->dev, pdata->wakeup);
- platform_set_drvdata(pdev, keypad);
- return 0;
- ………………
- }
(1)、input参数初始化;
(2)、打开、关闭函数;
- input_dev->open = samsung_keypad_open;
- static int samsung_keypad_open(struct input_dev *input_dev)
- {
- struct samsung_keypad *keypad = input_get_drvdata(input_dev);
- samsung_keypad_start(keypad);
- return 0;
- }
- 其实open函数调用samsung_keypad_start()函数,对按键的寄存器一些操作,如下面寄存器列表中的。
- static void samsung_keypad_start(struct samsung_keypad *keypad)
- {
- unsigned int val;
- /* Tell IRQ thread that it may poll the device. */
- keypad->stopped = false;
- clk_enable(keypad->clk);
- /* Enable interrupt bits. */
- val = readl(keypad->base + SAMSUNG_KEYIFCON);
- val |= SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN;
- writel(val, keypad->base + SAMSUNG_KEYIFCON);
- /* KEYIFCOL reg clear. */
- writel(0, keypad->base + SAMSUNG_KEYIFCOL);
- }
(3)、中断函数注册;
- error=request_threaded_irq(keypad->irq,NULL, samsung_keypad_irq,IRQF_ONESHOT, dev_name(&pdev->dev), keypad);
request_threaded_irq这个函数也许我们比较陌生,可是看下下面一个函数也许就不难理解了:
- static inline int __must_check
- request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
- const char *name, void *dev)
- {
- return request_threaded_irq(irq, handler, NULL, flags, name, dev);
- }
(4)、input驱动注册,input驱动比较重要,触摸屏、按键、gsensor、battery等都是通过input子系统上报的。
2、中断函数: samsung_keypad_irq分析,当有按键按下时,调用这个函数
- static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)
- {
- struct samsung_keypad *keypad = dev_id;
- unsigned int row_state[SAMSUNG_MAX_COLS];
- unsigned int val;
- bool key_down;
- do {
- val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);
- /* Clear interrupt. */
- //(1)、清除中断;
- writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);
- //(2)、扫描行列值,写入寄存器;
- samsung_keypad_scan(keypad, row_state);
- //(3)、键值上报,这是函数的主要部分了;
- key_down = samsung_keypad_report(keypad, row_state);
- //(4)、延时去抖动;
- if (key_down)
- wait_event_timeout(keypad->wait, keypad->stopped,
- msecs_to_jiffies(50));
- } while (key_down && !keypad->stopped);
- return IRQ_HANDLED;
- }
(1)、清除中断;
(2)、扫描行列值,写入寄存器(后面分析);
(3)、键值上报,这是函数的主要部分了(后面分析);
(4)、延时去抖动,如果有按键按下,有一个段时间的延时,看是否真正有按键,这就是所说的去抖动;
3、当按键按下时,行列值的扫描函数samsung_keypad_scan执行,写入相应行列寄存器
上图我们知道,对于矩阵键盘,主控有专门的接口,也有相应的寄存器,
- static void samsung_keypad_scan(struct samsung_keypad *keypad,
- unsigned int *row_state)
- {
- struct device *dev = keypad->input_dev->dev.parent;
- unsigned int col;
- unsigned int val;
- for (col = 0; col < keypad->cols; col++) {
- if (samsung_keypad_is_s5pv210(dev)) {
- val = S5PV210_KEYIFCOLEN_MASK;
- val &= ~(1 << col) << 8;
- } else {
- val = SAMSUNG_KEYIFCOL_MASK;
- val &= ~(1 << col);
- }
- writel(val, keypad->base + SAMSUNG_KEYIFCOL);
- mdelay(1);
- val = readl(keypad->base + SAMSUNG_KEYIFROW);
- row_state[col] = ~val & ((1 << keypad->rows) - 1);
- }
- /* KEYIFCOL reg clear */
- writel(0, keypad->base + SAMSUNG_KEYIFCOL);
- }
4、通过扫描键值写入相应寄存器,然后通过
- static bool samsung_keypad_report(struct samsung_keypad *keypad,
- unsigned int *row_state)
- {
- struct input_dev *input_dev = keypad->input_dev;
- unsigned int changed;
- unsigned int pressed;
- unsigned int key_down = 0;
- unsigned int val;
- unsigned int col, row;
- for (col = 0; col < keypad->cols; col++) {
- changed = row_state[col] ^ keypad->row_state[col];
- key_down |= row_state[col];
- if (!changed)
- continue;
- for (row = 0; row < keypad->rows; row++) {
- if (!(changed & (1 << row)))
- continue;
- pressed = row_state[col] & (1 << row);
- dev_dbg(&keypad->input_dev->dev,
- "key %s, row: %d, col: %d\n",
- pressed ? "pressed" : "released", row, col);
- //(1)、得到按键在矩阵中的位置;
- val = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
- printk("key %s, row: %d, col: %d\n",pressed ? "pressed" : "released", row, col);
- printk("test by xu_bin for val = %d,key = %d\n",val,keypad->keycodes[val]);
- input_event(input_dev, EV_MSC, MSC_SCAN, val);
- //(2)、上报键值keypad->keycodes[val];
- input_report_key(input_dev,
- keypad->keycodes[val], pressed);
- }
- //(3)、input上报后同步;
- input_sync(keypad->input_dev);
- }
- memcpy(keypad->row_state, row_state, sizeof(keypad->row_state));
- return key_down;
- }
(1)、#defineMATRIX_SCAN_CODE(row, col, row_shift) (((row)<< (row_shift)) + (col))
row_shift = 3
如:row = 1; col = 6; row_shift = 3
val = MATRIX_SCAN_CODE(row, col,keypad->row_shift) = ((1)<<(3)+(6)) = 14;
就相当于:(1,6)这个数组里面的值:48
printk("key %s, row: %d, col:%d\n",pressed ? "pressed" : "released", row, col);
printk("test by xu_bin for val =%d,key = %d\n",val,keypad->keycodes[val]);
(2)、上报键值keypad->keycodes[val],这个值是对于我们这个驱动来说的最终值;
(3)、input上报后同步,这个和input子系统相关。
这样就完成了驱动部分的上报。
http://blog.csdn.net/xubin341719/article/details/7960521
关键词:android 4.0 手机模式(phone) 平板模式(table) 虚拟按键 lcd_density
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsungexynos4210/4412)
作者:xubin341719(欢迎转载,请注明作者)
三星新拿回来来的BSP,编译后没有“返回、最近打开应用、home”三个虚拟键。我们硬件在设计的时候也没有设定相应的物理按键,平时调试程序的时候比较麻烦。怎么把这三个按键显示出来??下面我们来说明。
同时在开始分析问前我引入另外两个问题:
table 模式、phone模式选择;
lcd_density参数设定,来决定图标密度大小。
一、引入问题:
1、 手机模式、平板模式
android4.0手机模式、平板模式两种情况 界面的主体布局不太一样,如下图所示。
2、lcd_density
ro.sf.lcd_density=240 和ro.sf.lcd_density=160两种不同的现象,很明显说明这个参数的做用。后面我们遇到一个问题要从这里说明。
- To change the density of the screen change /system/build.prop
- ro.sf.lcd_density=240
- high-density, at the right of the picture, comes by default in the mephisto's roms.
- ro.sf.lcd_density=180
- low density, at the left of the picture
- (This means that the number of pixels per inch is 240=800/3.5"
- 3.5" is the screen of the H1 and 800×480 display resolution).
- So you can pick any number between 240 and 180 - personally I use 220.
- You can do this using root explorer apk for instance:
二、问题分析
1、参考网友的说法:
将\frameworks\base\core\res\res\values\config.xml中的下面属性的值改为true;
- <bool name="config_showNavigationBar">false</bool>
状态:
- 模式:phone
- 参数:config_showNavigationBar=true
出现以下情况:
(1)、虚拟按键边上那个黑框已经出来;
(2)、看不到三个按键图标;
(3)、点击边缘时会有颜色变化,横屏是“返回”键,竖屏时点击为“最近打开程序”;感觉那个按键被放大了一样。 由上面分析,这种现象是布局出问题,我们LCD分辨率为1280*800,其实三个按键出来了,只不过图标显示太大,所以我们看不到。同时在这里我们引入前面我们提到的两个问题:(1)、table 模式、phone模式选择;(2)lcd_density参数设定,来决定图标密度大小。
很明显的我们可以看出现在编译的时phone模式、那个图标为什么看不到, lcd_density设定的图标太大。
2、问题分析
打开机器,在串口终端或者是adb shell中:
cd/system
catdefault.prop
我们可以看到:
- ro.build.characteristics=phone
这就是我们所说的table、phone参数设定,不同的模式在这里决定的。查找这些参数在那里设定,最终找到:
android_ramos_4412_02/android/device/samsung/smdk4x12/device.mk
- ifeq ($(BOARD_USES_HIGH_RESOLUTION_LCD),true) //(1)、如果满足条件,就设为table模式;
- PRODUCT_CHARACTERISTICS := tablet
- PRODUCT_COPY_FILES += \ frameworks/base/data/etc/tablet_core_hardware.xml:system/etc/permissions/tablet_core_hardware.xml
- $(call inherit-product, frameworks/base/build/tablet-dalvik-heap.mk)
- else
- PRODUCT_CHARACTERISTICS := phone //(2)、满足条件就设为phone模式;
- PRODUCT_COPY_FILES += \
- frameworks/base/data/etc/handheld_core_hardware.xml:system/etc/permissions/handheld_core_hardware.xml
- $(call inherit-product, frameworks/base/build/phone-hdpi-512-dalvik-heap.mk) PRODUCT_PROPERTY_OVERRIDES += \
- ro.sf.lcd_density=240 //(3)、lcd_density设定。
- PRODUCT_AAPT_CONFIG := normal hdpi
- Endif
(1)、如果满足条件,就设为table
BOARD_USES_HIGH_RESOLUTION_LCD = true,就设定为table模式。
(2)、满足条件就设为phone模式;
BOARD_USES_HIGH_RESOLUTION_LCD = flash,就设定为phone模式
(3)、lcd_density设定。
在PRODUCT_CHARACTERISTICS := phone时,lcd_density设置为240。
现在我们要用table模式,所以我们要把BOARD_USES_HIGH_RESOLUTION_LCD这个参数设定为true。
android_ramos_4412_02/android/device/samsung/smdk4x12/BoardConfig.mk
- OARD_USES_HIGH_RESOLUTION_LCD := true
把BOARD_USES_HIGH_RESOLUTION_LCD选为ture就可以编译成平板模式。
(4)、把config_showNavigationBar还原成默认值
将\frameworks\base\core\res\res\values\config.xml
- <boolnameboolname="config_showNavigationBar">false</bool>
状态
- 模式:table
- 参数:config_showNavigationBar=false
编译,平板模式三个虚拟按键就可以出来了。我们的问题解决了。设为平板模式;三个虚拟按键出现。
三、phone模式下为什么只有一个黑框?
问题解决了,但是我们还有一个疑问,那就是按网友那种方法改动后,为什么没有出现我们理想的效果呢?回顾“1、参考网友的说法:”更改后,个别键有做用,但是不能看到三个按键。
分析代码android/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
- mNavigationBarHeight =mHasNavigationBar ? mContext.getResources().getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height) : 0; mNavigationBarWidth =mHasNavigationBar ?mContext.getResources().getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width) : 0;
- Log.v(TAG, "xubin testmNavigationBarHeight = " + mNavigationBarHeight
- " mNavigationBarWidth =" +mNavigationBarWidth);
- <span style="font-family:Arial, Helvetica, sans-serif;WHITE-SPACE: normal">打印值为:</span>
- V/WindowManager( 1250): xubin testmNavigationBarHeight = 72 mNavigationBarWidth =63
- V/WindowManager(1250): xubin test mNavigationBarHeight = 72 mNavigationBarWidth =63
打印出来的信息也没什么问题,相对的而已文件也正确。这就回到我们开始提到那个lcd_density参数问题了,上面可以很明显的看到,当lcd_density值发生小的改变后,图标大小有很大的变化,再个来说我们LCD的分辨率太高,1280X800的,所以把那三人图标放大大,所以我们看到上面那种现象。