您的位置:首页 > 其它

基于ARM9开发板的按键字符设备驱动实现

2014-10-27 17:41 260 查看
摘要:

该驱动程序实现4个按键设备在Linux系统中基于QT2410E开发板的工作情况,通过该实例可以了解ARM平台Linux系统下的GPIO程序控制,以及硬件中断程序的工作机制。另外还可以熟悉Linux 2.6内核的模块加载和测试方法。

1.了解硬件原理图

由于该设备驱动是针对具体硬件设备的,所以一般需要了解它的硬件原理图(如图1),该模块有四个按键分别是S2,S3,S4和S5,其中这四个按键分别对应的外部中断为:EINT0,EINT2,KBDINT(EINT1)和KBDSPIMISO(EINT13)。工作原理很简单,当系统正常工作时,按这四个任意键会产生相应的中断信号,从而系统会知道哪个按键被触发,在该驱动实现中当有哪个按键被触发时会有相应的打印信息产生。



图1 按键模块的电路图

2.设计驱动程序框架

字符设备指那些必须以串行顺序依次访问的设备,并且不需要缓冲,通常用于不需要大量数据请求传送的设备类型,所以对于该按键设备适合用字符设备类型来实现。对于Linux设备驱动,通常根据设备类型的不同所以实现框架会有所不同,比如字符设备、块设备和网络设备分别都有自己的实现框架和相应的内核API函数。通常字符设备驱动实现的内容有:模块的加载(字符设备的注册,硬件的初始化,中断的注册等);字符设备的操作函数实现(open,close,read,write和ioctl等);中断程序的实现;模块的卸载(注销字符设备,注销其他申请的资源)。下面来分析一下该按键设备驱动的具体实现。

3.按键模块的加载

1 static int __init buttons_init(void)

2 {

3 int ret,devno;

4 dev_t dev;

5 ret = alloc_chrdev_region(&dev,0,1,DEVICE_NAME); //在系统中申请一个字符设备区域,主设备号由系统动态分配。

6 buttons_major_number = MAJOR(dev); //摘取出主设备号

7 printk(KERN_INFO "Initial QT2410E Board Buttons driver!/n");

8 if (ret<0) {

9 printk(KERN_WARNING "button:can't get major number %d/n",buttons_major_number);

10 return ret;

11 }

12 ret = request_irqs(); //注册中断,下文会具体分析该函数的实现

13 if (ret) { //如果注册中断失败,则注销上面申请的字符设备区域。

14 unregister_chrdev_region(dev,1);

15 printk(KERN_WARNING "button:can't request irqs/n");

16 return ret;

17 }

18 devno = MKDEV(buttons_major_number,0);

19 cdev_init(&buttons_dev,&buttons_fops); //初始化buttons_dev字符设备结构

20 buttons_dev.owner = THIS_MODULE;

21

22

23 ret = cdev_add(&buttons_dev,devno,1);// 将字符设备加入到内核中

24 if (ret) { //如果添加失败,则做上述注册的释放操作。

25 free_irqs();

26 unregister_chrdev_region(dev,1);

27 printk(KERN_NOTICE "Error %d adding buttons device/n",ret);

28 return ret;

29 }

30

31 #ifdef CONFIG_DEVFS_FS //如果定义devfs,系统会自动创建/dev目录下的字符设备节点,比如这里会自动创建/dev/buttons字符设备节点

32 devfs_mk_cdev(MKDEV(buttons_major_number,0), S_IFCHR | S_IRUSR | S_IWUSR,DEVICE_NAME);

33 printk(KERN_INFO"/dev/%s has been added to your system./n",DEVICE_NAME);

34 #else //否则需要执行 mknod命令手动创建字符设备节点。

35 printk(DEVICE_NAME "Initialized/n");

36 printk(KERN_INFO "You must create the dev file manually./n");

37 printk(KERN_INFO "Todo: mknod c /dev/%s %d 0/n",DEVICE_NAME,buttons_major_number);

38 #endif

39 return 0;

40 }

分析上述代码,buttons_init是该按键模块的驱动入口函数,也是该内核模块的加载函数,主要用于注册资源、申请资源和初始化设备等工作。第5行,alloc_chrdev_region()是内核提供的申请字符设备号函数,它会动态的为设备申请一个主设备号,并且根据输入参数申请多个次设备。第19行,cdev_init()是用于初始化一个cdev结构,这里初始化的是buttons_dev全局变量。第32行,devfs_mk_cdev()函数会在/dev目录下自动创建一个设备节点,早期的内核版本是需要手动通过mknod工具创建设备节点。

4.字符设备的操作函数

由于该设备功能单一,所以这里只实现了read操作,关于read操作的定义如下:

static struct file_operations buttons_fops = {

.owner = THIS_MODULE,

.read = buttons_read,

};

其中read操作是被定义在file_operations的对象中,由buttons_read函数具体实现。

1 static ssize_t buttons_read(struct file *filp,char __user *buffer,size_t count,loff_t *ppos)

2 {

3 static int key;

4 unsigned long flags;

5 if (!ready) {

6 return -EAGAIN;

7 }

8 if (count != sizeof key_value)

9 return -EINVAL;

10 local_irq_save(flags);

11 key = key_value;

12

13 local_irq_restore(flags);

14 copy_to_user(buffer, &key, sizeof key);

15 ready = 0;

16

17 return sizeof key_value;

18 }

以上代码中最重要的实现是由copy_to_user()函数实现的,它是内核提供的用于将内核空间数据拷贝到用户空间中去API,它是内核空间与用户空间通信的重要实现函数。

5. 中断实现函数

1 static int request_irqs(void)

2 {

3 struct key_info *k;

4 int i,request;

5 unsigned int irq;

6 for (i = 0; i < sizeof key_info_tab / sizeof key_info_tab[1]; i++) {

7 k = key_info_tab + i;

8 irq=k->irq_no;

9 s3c_irqext_type(irq, type);

10 if(irq<IRQ_EINT7)

11 s3c_irq_ack(irq);

12 else

13 s3c_irqext_ack(irq);

14 request =request_irq(k->irq_no,&buttons_irq,SA_INTERRUPT,DEVICE_NAME,NULL);

15 if (request) {

16 printk(KERN_WARNING "buttons:can't get irq no./n");

17 return -1;

18 }

19 }

20 return 0;

21 }

其中key_info_tab结构体是用于定义按键的中断号,GPIO等硬件信息,定义如下:

unsigned int irq_no;

unsigned int gpio_port;

int key_no;

unsigned int IN;

unsigned int EINT;

} key_info_tab[4] = {

/*{ IRQ_EINT11, S3C2410_GPG3, 1 ,S3C2410_GPG3_INP,S3C2410_GPG3_EINT11},*/

{ IRQ_EINT0, S3C2410_GPF0, 2 ,S3C2410_GPF0_INP,S3C2410_GPF0_EINT0},

/*{ IRQ_EINT19, S3C2410_GPG11, 3 ,S3C2410_GPG11_INP,S3C2410_GPG11_EINT19},*/

{ IRQ_EINT2, S3C2410_GPF2, 3 ,S3C2410_GPF2_INP,S3C2410_GPF2_EINT2},

{ IRQ_EINT1, S3C2410_GPF1, 4 ,S3C2410_GPF1_INP,S3C2410_GPF1_EINT1},

{ IRQ_EINT13, S3C2410_GPG5, 5 ,S3C2410_GPG5_INP,S3C2410_GPG5_EINT13},

};

通过上面的代码可以看出这四个按键分别对应的外部中断号是:IRQ_EINT0,IRQ_EINT2,IRQ_EINT1和IRQ_EINT13。继续分析request_irqs( )函数,其中s3c_irq_ack()和s3c_irqext_ack()是用来清除中断的。当内核调用__do_IRQ()时首先要对中断做清除(也就是ACK)动作。其中,第14行,通过调用request_irq()来真正的向系统注册该中断服务,其中中断服务函数为该函数的参数buttons_irq()回调函数,当中断产生时系统会自动调用该函数。关于buttons_irq()的具体实现代码如下:

static irqreturn_t buttons_irq(int irq,void *dev_id,struct pt_regs *req)

{

struct key_info *k;

int i;

int found = 0;

int up ;

unsigned long flags;

for (i = 0; i < sizeof key_info_tab / sizeof key_info_tab[1]; i++) {

k = key_info_tab + i;

if (k->irq_no == irq) {

found = 1;

break;

}

}

if (!found) {

printk("bad irq %d in button/n", irq);

return IRQ_NONE;

}

local_irq_save(flags);

mdelay(1);

s3c2410_gpio_cfgpin(k->gpio_port, k->IN);

up = s3c2410_gpio_getpin(k->gpio_port);

local_irq_restore(flags);

s3c_irqext_type(irq, type);

if(irq<IRQ_EINT7)

s3c_irq_ack(irq);

else

s3c_irqext_ack(irq);

if (!up) {

key_value = k->key_no;

ready=1;

}

wake_up_interruptible(&buttons_wait);

return IRQ_HANDLED;

}

上述代码实现的功能是:首先根据中断号判断是否是已经注册的中断,如果不是所注册的中断号范围,那么就返回无中断标识退出。当发现产生的中断号就是之前注册的中断,那么读取相应的硬件IO端口进行判断,获取最终对应的按键值。

6.按键模块的卸载

static void __exit buttons_cleanup(void)

{

dev_t dev=MKDEV(buttons_major_number,0);

free_irqs();

cdev_del(&buttons_dev);

unregister_chrdev_region(dev,1);

#ifdef CONFIG_DEVFS_FS

devfs_remove(DEVICE_NAME);

#endif

printk(KERN_INFO "unregistered the %s/n",DEVICE_NAME);

}

其中卸载模块的实现比较简单,首先通过调用free_irqs()来释放中断资源。其次,调用cdev_del()来删除之前注册的字符设备,最后调用unregister_chrdev_region()来释放系统中曾申请的设备号。

7.编写应用程序测试该驱动

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <errno.h>

#include <fcntl.h>

#include <sys/types.h>

int main(int argc, char **argv)

{

int fd;

int result;

int key_value;

int times = 1;

fd = open("/dev/buttons", 0);

if (fd < 0)

{

perror("Failed to open buttons-key/n");

exit(1);

}

while(1)

{

result = read(fd, &key_value, sizeof(key_value)) == sizeof(key_value);

if (result)

{

printf("KEY's value is %d/n;", key_value);

printf(" Be pressed %d times/n", times++);

}

}

close(fd);

return 0;

}

测试方法:首先编译按键驱动模块,然后通过insmod命令加载驱动到系统中,最后编译上述应用程序,执行该应用程序,通过在开发板上进行按键操作,开发板的执行中断会打印按键对应的数值和按键的次数。

到此为止,一个典型的简单的且有实际意义的字符设备驱动已经展现给了大家,希望大家可以仔细分析该驱动的实现过程,深入了解内核驱动的实现原理。该实验的源代码请在:

http://www.top-e.org/wdxz/html/?20.html下载。

该实验基于QT2410E开发板进行,关于该开发板的具体介绍参考:http://www.top-e.org/page/jgsz/index.php
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: