本文地址,转载请注明:http://blog.csdn.net/dearsq/article/details/51251009
硬件工作原理
触摸屏的工作原理概括来说就是上报坐标值,X轴、Y轴的值。所以在 Linux 中是采用 input 子系统来对其进行实现。
具体的硬件原理可以参考这一篇文章 电容式触摸屏硬件基本原理。
本文主要归纳其驱动基本原理 与 Android平台上的移植步骤,并分析总结移植过程中碰到的问题。
驱动基本原理
触摸屏的驱动部分大概涉及到三个点:
中断
Linux 内核的中断处理机制如下:
为了在中断执行时间尽可能短和中断处理完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。
这样,顶半部执行的速度就会很快,可以服务更多的中断请求。现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。
底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
总而言之,即中断要尽可能耗时比较短,尽快恢复系统正常调试,所以把中断触发、中断执行分开,也就是所说的“上半部分(中断触发)、底半部(中断执行)。
上半部分(中断触发)、底半部(中断执行) 即 中断上下文。
下半部一般有 tasklet 和 workqueue 来实现,触摸屏是由 workqueue 来实现的。
工作队列
tasklet 工作在软中断上下文,工作队列工作在内核进程。
这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文(process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。
对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。
因此,选择tasklet还是工作队列来完成下半部分应该不难选择。当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是你的最佳选择;否则,还是用tasklet吧。
中断上下文
同类的概念:进程上下文:一般的进程运行在用户态,如果这个进程进行了系统调用,那么此时用户空间中的程序就进入了内核空间,并且称内核代表该进程运行于内核空间中。由于用户空间和内核空间具有不同的地址映射,并且用户空间的进程要传递很多变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。这样就产生了进程上下文。
进程上下文实际上是指,一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容。
工作队列的使用方法
1.采用 struct work_struct 定义一个工作队列。
struct work_struct my_wq;
2.定义一个处理函数
void my_wq_func(strcut work_struct *work);
3.初始化工作队列并绑定处理函数
INIT_WORK(&my_wq,my_wq_func);
4.调度工作队列执行函数
schedule_work(&my_wq);
代码示例
/* 定义工作队列和相关函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);
/* 底半部执行函数 */
void xxx_do_work(struct work_struct *work)
{
...
}
/* 顶半部执行函数 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
schedule_work(&xxx_wq);
...
return IRQ_HANDLED;
}
/* 设备驱动模块加载函数 */
int xxx_init(void)
{
...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt, 0 ,"xxx", NULL);
...
INIT_WORK(&xxx_wq, xxx_do_work);
...
}
/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
...
}
input 子系统
输入子系统是由输入子系统设备驱动层、输入子系统核心层(InputCore)和输入子系统事件处理层(Event Handler)组成。
设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;
核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;
事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。
输入子系统与驱动的关系
input 子系统驱动层实现原理
Input 设备用 input_dev 结构体描述,定义在 input.h 中。
需要按照如下步骤实现:
1. 驱动模块加载函数中设置input设备支持input子系统的数据;
2.将 input 设备注册到 input 子系统中;
3.在 input 设备发生输入操作时,提交发生事件所对应的键值/坐标状态。
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置)
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈
//用于提交较常用的事件类型给输入子系统的函数有:
void input_report_key(struct input_dev *dev, unsigned int code, int value); //提交按键事件的函数
void input_report_rel(struct input_dev *dev, unsigned int code, int value); //提交相对坐标事件的函数
void input_report_abs(struct input_dev *dev, unsigned int code, int value); //提交绝对坐标事件的函数
//在提交输入设备的事件后必须用下列方法使事件同步,让它告知input系统,设备驱动已经发出了一个完整的报告:
void input_sync(struct input_dev *dev)