文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>linux嵌入式系统开发之触摸屏---驱动篇(中/Linux输入子系统)

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
};

for (i = 0; i <4; i++) { //为每个按键注册中断
    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输入子系统完成输入设备驱动的编写,和我们的触摸屏其实没啥关系,你掌握了这个大致的框架,等我们下节讲触摸屏时,你就拿这个框架来套着用就可以了..”

“嗯..嗯..外面樱花开了耶..”小王眼睛盯着电脑,想着樱花,这就是传说中的女孩吗..她啊,长大了..

相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载