linux嵌入式系统开发之触摸屏---驱动篇(中/Linux输入子系统)
时间:2011-03-22 来源:☆&寒 烟☆
“小王,别看神话啦,爱情重要,技术才是第一,上节课不是说这次讲触摸屏驱动的下篇吗,难度有点大啊..”语重心长的我总是语重心长。
“嗯,怎么啦, 难道不是..说话不算数..”撅着嘴的小王也总是撅着嘴。
“嗯,还真是这样,我本来是打算讲驱动的,但是我四处搜集了一下,发现现在的触摸屏驱动都是采用的linux输入子系统的方式来做的,所以呢,今天不得不加一下输入子系统的东西了..”我犹豫得看着小王,就怕她不同意。
“好,人家都用这个了,我干嘛还用以前的费劲的方式呢,不要犹豫了,快讲吧”急性子始终是她的标签。
“行,那现在开始今天的课程---linux嵌入式系统开发之触摸屏---驱动篇(中/Linux输入子系统)”咳嗽两声,清清嗓子,咱们继续。
既然说到了linux输入子系统,那么为什么要用输入子系统呢?这得从面向对象的程序设计中说起,通过面向对象技术,极大地的提高了代码的可重用能力。重用的作用可以是很多linux hack们所迷恋,为啥?有了重用,就不用花费大量的精力去做一些相同或相近的事情(什么?吃饭,对,还就是吃饭,天天让小王吃一样东西,她肯定一把鼻涕一把泪的),尽管Linux内核完全由C语言和汇编语言写成,但是却频繁用到了这种面向对象的设计思想。具体到驱动方面,往往为同类的设备设计一个框架,而框架中的核心层则实现了该设备通用的一些功能。万一,我说的是万一,如果具体的设备不想使用核心层的函数,我们当然可以用面向对象里的重载之.
言归正传,linux中的输入设备(如前边讲到的按键,键盘,触摸屏,鼠标等)都是典型的字符设备,一般的工作原理是底层在这些输入设备动作发生时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI,I2C或者外部存储器总线读取键值.坐标等数据,放入一个缓冲区,字符设备驱动管理缓冲区,而驱动的read()接口让用户可以读取键值,坐标等数据。
显然,在这些工作中,只是中断,读键值/坐标值是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。Linux内核输入子系统的框架如图一所示:
结合上图,输入子系统由输入子系统核心层(Input Core),驱动层和事件处理层(Event Handler)三部份组成。一个输入事件,如鼠标移动,键盘按键按下.joystick的移动等等通过 Driver->InputCore->Eventhandler->userspace 的顺序到达用户空间传给应用程序。其中Input Core 即 Input Layer对下提供了设备驱动的接口,对上提供了Event Handler层的编程接口。在Linux输入子系统中主要用的数据结构如下:
数据结构 | 用途 | 定义位置 | 具体数据结构的分配和初始化 |
Struct input_dev | 驱动层物理Input设备的基本数据结构 | Input.h | 通常在具体的设备驱动中分配和填充具体的设备结构 |
Struct Evdev Struct Mousedev Struct Keybdev… |
Event Handler层逻辑Input设备的数据结构 |
Evdev.c Mousedev.c Keybdev.c |
Evdev.c/Mouedev.c …中分配 |
Struct Input_handler | Event Handler的结构 | Input.h | Event Handler层,定义一个具体的Event Handler。 |
Struct Input_handle | 用来创建驱动层Dev和Handler链表的链表项结构 | Input.h | Event Handler层中分配,包含在Evdev/Mousedev…中。 |
另外所有的的输入事件,内核使用统一的数据结构-----input_event来描述,如
struct input_event{
struct timeval time;
_u16 type;
_u16 code;
_s32 value;
};
在input_dev结构体中,一个字段是evbit,它表示响应的事件类型,而具体到这个设备所能产生或接受的的事件类型,包括如下:
EV_RST 0x00 Reset
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 力反馈
需要说明的是,一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件,比如EV_KEY事件,支持哪些按键等。
下面就来讲讲如何利用Linux输入子系统来写前边已经写过的按键驱动(当然啦,这里重点为了说明今天的内容,所以代码注重整体框架,忽略代码顺序和部分细节):
1)定义键盘的码表,这里会在input_dev的keycode用到:
static unsigned char simplekey_keycode[0x08] = {
[0]= KEY_1, [1]= KEY_2, [2]= KEY_3, [3]= KEY_4, [4]= KEY_5, [5]= KEY_6, [6]= KEY_7, [7]= KEY_8,
};
2)定义按键需要的中断号,这里使用了四个中断号,当按键发生时,和触发该按键号对应中断处理函数中的内容:
static int irqArray[4]=
{
IRQ_EINT0, IRQ_EINT2, IRQ_EINT11, IRQ_EINT19
};
if (request_irq(irqArray[i], &simplekey_interrupt, SA_INTERRUPT, "simplekey", NULL)) {
printk("request button irq failed!\n");
return -1;
}
2)声明输入设备结构体,并定义一些字符串,供后面输入设备结构体初始化使用:
static struct input_dev simplekey_dev;
static char *simplekey_name = "simplekey";
static char *simplekey_phys = "input0";
init_input_dev(&simplekey_dev);
simplekey_dev.evbit[0] = BIT(EV_KEY);// | BIT(EV_REP);
simplekey_dev.keycode = simplekey_keycode;
simplekey_dev.keycodesize = sizeof(unsigned char);
simplekey_dev.keycodemax = ARRAY_SIZE(simplekey_keycode);
for (i = 0; i < 0x78; i++)//分别用来设置设备所产生的事件以及上报的按键值。Struct iput_dev 中有两个成员,一个是 evbit.一个是 keybit.分别用
//表示设备所支持的动作和按键类型。
if (simplekey_keycode[i])
set_bit(simplekey_keycode[i], simplekey_dev.keybit);
simplekey_dev.name = simplekey_name;
simplekey_dev.phys = simplekey_phys;
simplekey_dev.id.bustype = BUS_AMIGA;
simplekey_dev.id.vendor = 0x0001;
simplekey_dev.id.product = 0x0001;
simplekey_dev.id.version = 0x0100;
input_register_device(&simplekey_dev);
3)这里的中断处理采用了定时器,为了在收到中断后延时等待,防止抖动:
struct timer_list polling_timer;
static unsigned long polling_jffs=0;
init_timer(&polling_timer);
polling_timer.data = (unsigned long)0;
polling_timer.function = polling_handler;
4)一旦按键的动作发生,就会发生按键时间,引发按键中断,转到按键中断处理函数
static irqreturn_t simplekey_interrupt(int irq, void *dummy, struct pt_regs *fp)
{
disable_irq(IRQ_EINT0); //在处理中断时,屏蔽所有的按键中断触发
disable_irq(IRQ_EINT2);
disable_irq(IRQ_EINT11);
disable_irq(IRQ_EINT19);
polling_timer.expires = jiffies + HZ/5;
add_timer(&polling_timer); //添加到定时器,一旦定时器时间到,就让定时器处理函数,进行处理,为啥?延迟一下,防止键盘抖动,不懂?看前边呗..
return IRQ_HANDLED;
}
5)定时器时间到了,现在应该算是正式的处理按键中断事件
void polling_handler(unsigned long data)
{
int code=-1;
writel(readl(S3C2410_SRCPND)&0xffffffda,S3C2410_SRCPND);//clear srcpnd 0 2 11 19
mdelay(1);
//扫描按键表,根据中断号,找出所按下的按键。
writel(readl(S3C2410_GPBDAT)|0x80,S3C2410_GPBDAT);//set GPB76 to 10
writel(readl(S3C2410_GPBDAT)&0xffffffBf,S3C2410_GPBDAT);
if((readl(S3C2410_GPFDAT)&(1<< 0)) == 0 )
{
code=0;
goto IRQ_OUT;
}
else if( (readl(S3C2410_GPFDAT)&(1<< 2)) == 0 )
{
code=2;
goto IRQ_OUT;
}
else if( (readl(S3C2410_GPGDAT)&(1<< 3)) ==0 )
{
code=4;
goto IRQ_OUT;
}
else if( (readl(S3C2410_GPGDAT)&(1<<11)) == 0 )
{
code=6;
goto IRQ_OUT;
}
writel(readl(S3C2410_SRCPND)&0xffffffda,S3C2410_SRCPND);//clear srcpnd 0 2 11 19
mdelay(1);
writel(readl(S3C2410_GPBDAT)|0x40,S3C2410_GPBDAT);//set GPB76 to 01
writel(readl(S3C2410_GPBDAT)&0xffffff7f,S3C2410_GPBDAT);//...
if((readl(S3C2410_GPFDAT)&(1<< 0)) == 0 )
{
code=1;
goto IRQ_OUT;
}
else if( (readl(S3C2410_GPFDAT)&(1<< 2)) == 0 )
{
code=3;
goto IRQ_OUT;
}
else if( (readl(S3C2410_GPGDAT)&(1<< 3)) ==0 )
{
code=5;
goto IRQ_OUT;
}
else if( (readl(S3C2410_GPGDAT)&(1<<11)) == 0 )
{
code=7;
goto IRQ_OUT;
}
IRQ_OUT: //本次中断处理完成处理完成,开启所有中断
enable_irq(IRQ_EINT0);
enable_irq(IRQ_EINT2);
enable_irq(IRQ_EINT11);
enable_irq(IRQ_EINT19);
if(code>=0)
{
//避免中断连续出现
if((jiffies-polling_jffs)>100)
{
polling_jffs=jiffies;
input_report_key(&simplekey_dev, simplekey_keycode[code], 1); //任何非零值都代表按键按下,0代表释放
input_report_key(&simplekey_dev, simplekey_keycode[code], 0); //只有Value发生变化时,才会把事件上传的内核的输入子系统
input_sync(&simplekey_dev); //该函数说明接下来就要发送一个完整的报告,这在鼠标中很重要,因为X.Y分量是不能分开发送的。
}
}
writel(readl(S3C2410_GPBDAT)&0xffffff3f,S3C2410_GPBDAT);
}
6)最后当然就是一些清理函数了,不用我解释,你也能看懂
static void __exit simplekey_exit(void)
{
int i;
for (i = 0; i <4; i++) {
free_irq(irqArray[i],simplekey_interrupt);
}
input_unregister_device(&simplekey_dev);
}
“小王,看懂了吗?这里只是给了你一个大体框架,让你知道怎么来利用Linux输入子系统完成输入设备驱动的编写,和我们的触摸屏其实没啥关系,你掌握了这个大致的框架,等我们下节讲触摸屏时,你就拿这个框架来套着用就可以了..”
“嗯..嗯..外面樱花开了耶..”小王眼睛盯着电脑,想着樱花,这就是传说中的女孩吗..她啊,长大了..