欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于tiny4412按键中断编程

程序员文章站 2022-06-09 16:33:23
...

准备工作

  • 要先把内核中原有的按键驱动干掉,重新编译内核,用新的内核进行编程。否则原来的按键驱动会阻碍我们编写自己的按键中断驱动,比如相关资源无法使用被系统的按键驱动占用,导致我们无法使用。具体操作方法方法如下:
    基于tiny4412按键中断编程
    基于tiny4412按键中断编程
    基于tiny4412按键中断编程

*号改为M或者空白
执行完毕之后,重新编译内核,在linux-3.5目录执行make -j4至于j几根据机器性能来决定,机器性能越好,数字可以填的越大,可增大编译速度。make执行完毕在arch\arm\boot文件夹生成zImage内核镜像文件,将SD卡插入电脑,与虚拟机连接执行 dd iflag=dsync oflag=dsync if=zImage of=/dev/sdb seek=1057,进行内核烧写。约几十秒内核烧写完毕,将SD卡重新插入开发板并开机,这样开发板便在没有按键中断驱动的内核上跑起来了。我们可以写我们自己的按键中断驱动。
-查看原理图,确定开发版按钮IO连接关系
基于tiny4412按键中断编程
基于tiny4412按键中断编程
基于tiny4412按键中断编程
查看原理图我们知道,4个按键分别连接在EXYNOS4_GPX3的2,3,4,5引脚上


按键程序的编写

linux一切都是文件,可以把开发板四个按键当做一个只能读取四个字节的文件。
对于按键驱动的编写,在基于字符设备驱动模型的基础上只是添加了中断注册函数及中断服务函数的编写,而struct file_operation的文件操作接口只需要实现read接口,则驱动可以正常工作。
软件编程思路:

  • 以一个普通的字符设备驱动模板为基础进行修改
  • 按键作用是给应用程序提供哪个按键被按下,松开,所以,文件操作方法只需要实现read接口。
  • 按键使用外部中断方式,所以需要使用request_irq进行注册:irq中断号,中断服务函数handler,中断属性flag,共享中断识别dev_id,中断名称name
  • 以模块形式后期加载,对irq号来说需要重复使用比较合理,所以把中断注册写在模块初始化代码,中断释放写在模块卸载函数中。
  • 中断编号irq:看原理图,记录它的IO编号,使用gpio_to_irq函数得到它的中断编号。
  • handler原型有两种:这里使用非共享中断
irqreturn_t isr_function(int irq,void *dev_id)
{
  ·······
  return IRQ_HANDLED;
}
  • flag:IRQF_DISABLED | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING(非共享中断,下降沿触发,上升沿触发)
  • dev_id:对非共享中断是可选的

查看原理图我们知道:
K1 -> XEINT26->GPX3_2
K2 -> XEINT26->GPX3_3
K3 -> XEINT26->GPX3_4
K4 -> XEINT26->GPX3_5
从上面的信息可以得到中断编号
K1->gpio_to_irq(EXYNOS4_GPX3(2));
K2->gpio_to_irq(EXYNOS4_GPX3(3));
K3->gpio_to_irq(EXYNOS4_GPX3(4));
K4->gpio_to_irq(EXYNOS4_GPX3(5));

编程思路

  • 将在字符设备驱动模板上修改
    下面是驱动模板代码,只有大体框架没有任何操作硬件的成分。
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自动创建设备头文件
#include<linux/uaccess.h>
//定义字符设备结构体
static struct cdev *xxxdriver_cdev;
//定义设备号(包含主次)
static dev_t xxxdriver_num=0;
//定义设备类
static struct class *xxxdriver_class;
//定义设备结构体
static struct device *xxxdriver_device;

//定义设备名称 注册函数用到
#define XXXDRIVER_NAME "mydevice"



ssize_t XXX_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
  printk("file read success!!\r\n");
  return 0;
}

ssize_t XXX_write (struct file *file, const char __user *usr, size_t size, loff_t *loft)
{
  printk("file write success!!\r\n");
  return 0;
}

int XXX_open (struct inode *node, struct file *pfile)
{
  printk("files open  success!!\r\n");
  return 0;
}

loff_t XXX_llseek(struct file *pfile, loff_t loft, int whence){
    printk("file lseek success!!\r\n");
   return 0;
}


int XXX_release (struct inode *node, struct file *file)
{
  printk("file close  success!!\r\n");
  return 0;
}

//文件操作函数结构体
static struct file_operations xxxdriver_fops={
    .owner=THIS_MODULE,
    .open=XXX_open,
    .release=XXX_release,
    .read=XXX_read,
    .write=XXX_write,
    .llseek=XXX_llseek,
};

static __init int xxxdriver_init(void)
{
    //定义错误返回类型
    static int  err;
//分配字符设备结构体,前面只是定义没有分配空间
xxxdriver_cdev=cdev_alloc();
//判断分配成功与否
if(xxxdriver_cdev==NULL)
{
  err=-ENOMEM;
  printk("xxxdriver alloc is err\r\n");
  goto err_xxxdriver_alloc;
}

//动态分配设备号
err=alloc_chrdev_region(&xxxdriver_num, 0, 1, XXXDRIVER_NAME);
//错误判断
if(err<0)
{
  printk("alloc xxxdriver num is err\r\n");
  goto err_alloc_chrdev_region;
}

//初始化结构体
cdev_init(xxxdriver_cdev,&xxxdriver_fops);

//驱动注册
err=cdev_add(xxxdriver_cdev,xxxdriver_num,1);
if(err<0)
{
  printk("cdev add is err\r\n");
  goto err_cdev_add;
}

//创建设备类
xxxdriver_class=class_create(THIS_MODULE,"xxx_class");
  err=PTR_ERR(xxxdriver_class);
  if(IS_ERR(xxxdriver_class))
    {
printk("xxxdriver creat class is err\r\n");
goto err_class_create;
  }


//创建设备
  xxxdriver_device=device_create(xxxdriver_class,NULL, xxxdriver_num,NULL, "xxxdevice");
 err=PTR_ERR(xxxdriver_device);
    if(IS_ERR(xxxdriver_device))
        {
printk("xxxdriver device creat is err \r\n");
goto err_device_create;
    }


printk("xxxdriver init is success\r\n");
return 0;

//硬件初始化部分

err_device_create:
class_destroy(xxxdriver_class);
err_class_create:
 cdev_del(xxxdriver_cdev);
err_cdev_add:
unregister_chrdev_region(xxxdriver_num, 1);

err_alloc_chrdev_region:
kfree(xxxdriver_cdev);

err_xxxdriver_alloc:
return err;

}

static __exit void xxxdriver_exit(void)
{
    //IO取消映射

  device_destroy(xxxdriver_class,xxxdriver_num);
  class_destroy(xxxdriver_class);
  cdev_del(xxxdriver_cdev);
  unregister_chrdev_region(xxxdriver_num, 1);
  printk("xxxdriver is exit\r\n");
}


module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");

//下面先写一个比较粗糙的按键中断代码
驱动程序源码:

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自动创建设备头文件
#include<linux/uaccess.h>
#include<linux/interrupt.h> //中断注册注销头文件
#include<linux/gpio.h>   //gpio相关的头文件

//按键数量
#define KEY_SIZE (4)

//按键缓冲区
unsigned char kbuf[KEY_SIZE]={"0000"};    //储存按键状态 初始化为字符 0,0表示没有按下,1表示按下 



//定义字符设备结构体
static struct cdev *xxxdriver_cdev;
//定义设备号(包含主次)
static dev_t xxxdriver_num=0;
//定义设备类
static struct class *xxxdriver_class;
//定义设备结构体
static struct device *xxxdriver_device;

//定义设备名称
#define XXXDRIVER_NAME "mydevice"



ssize_t XXX_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
    if(size>KEY_SIZE)
        {
    size=KEY_SIZE;
    }
    if(copy_to_user(usr,kbuf,size))
        {
        printk("copy to user faild\r\n");
    return -EFAULT;
    }

  return size;
}

ssize_t XXX_write (struct file *file, const char __user *usr, size_t size, loff_t *loft)
{
  printk("file write success!!\r\n");
  return 0;
}

int XXX_open (struct inode *node, struct file *pfile)
{
  printk("files open  success!!\r\n");
  return 0;
}

loff_t XXX_llseek(struct file *pfile, loff_t loft, int whence){
    printk("file lseek success!!\r\n");
   return 0;
}


int XXX_release (struct inode *node, struct file *file)
{
  printk("file close  success!!\r\n");
  return 0;
}



/*key1中断服务函数:
按键按下、松开都会进入中断服务函数
程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来

*/
irqreturn_t key1_handler(int irq, void * dev_id)
{
    int dn;        //存放按键状态
    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(EXYNOS4_GPX3(2));
    if(!dn)
        printk("key1 down!!\\r\n");
    else
        printk("key1 up!!\r\n");
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[0]= '0'+!dn;   
    return IRQ_HANDLED;
}

/*key2中断服务函数:
按键按下、松开都会进入中断服务函数
程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来

*/
irqreturn_t key2_handler(int irq, void * dev_id)
{
    int dn;        //存放按键状态
    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(EXYNOS4_GPX3(3));
    if(!dn)
        printk("key2 down!!\\r\n");
    else
        printk("key2 up!!\r\n");
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[1]= '0'+!dn;   
    return IRQ_HANDLED;
}

/*key3中断服务函数:
按键按下、松开都会进入中断服务函数
程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来

*/
irqreturn_t key3_handler(int irq, void * dev_id)
{
    int dn;        //存放按键状态
    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(EXYNOS4_GPX3(4));
    if(!dn)
        printk("key3 down!!\\r\n");
    else
        printk("key3 up!!\r\n");
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[2]= '0'+!dn;   
    return IRQ_HANDLED;
}

/*key4中断服务函数:
按键按下、松开都会进入中断服务函数
程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来

*/
irqreturn_t key4_handler(int irq, void * dev_id)
{  
     //存放按键状态
    int dn;       
    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(EXYNOS4_GPX3(5));
    if(!dn)
        printk("key4 down!!\\r\n");
    else
        printk("key4 up!!\r\n");
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[3]= '0'+!dn;   
    return IRQ_HANDLED;
}


//文件操作函数结构体
static struct file_operations xxxdriver_fops={
    .owner=THIS_MODULE,
    .open=XXX_open,
    .release=XXX_release,
    .read=XXX_read,
    .write=XXX_write,
    .llseek=XXX_llseek,
};

static __init int xxxdriver_init(void)
{
 //定义错误返回类型
 static int err;
 //定义中断编号
 int irq;
 //设置中断属性 非共享中断 上升下降沿触发
 unsigned long flags=IRQF_DISABLED | 
                      IRQF_TRIGGER_FALLING | 
                      IRQF_TRIGGER_RISING;
 irq=gpio_to_irq(EXYNOS4_GPX3(2));                   //得到外部中断K1中断编号
 //开发板上有四个按键  注册四次
 request_irq(irq, key1_handler,flags, "key1",NULL); //注册key1中断

 irq=gpio_to_irq(EXYNOS4_GPX3(3));                   //得到外部中断K2中断编号
 request_irq(irq, key2_handler,flags, "key2",NULL); //注册key2中断

 irq=gpio_to_irq(EXYNOS4_GPX3(4));                   //得到外部中断K3中断编号
 request_irq(irq, key3_handler,flags, "key3",NULL); //注册key3中断

 irq=gpio_to_irq(EXYNOS4_GPX3(5));                   //得到外部中断K4中断编号
 request_irq(irq, key4_handler,flags, "key4",NULL); //注册key4中断 


//分配字符设备结构体,前面只是定义没有分配空间
xxxdriver_cdev=cdev_alloc();
//判断分配成功与否
if(xxxdriver_cdev==NULL)
{
  err=-ENOMEM;
  printk("xxxdriver alloc is err\r\n");
  goto err_xxxdriver_alloc;
}

//动态分配设备号
err=alloc_chrdev_region(&xxxdriver_num, 0, 1, XXXDRIVER_NAME);
//错误判断
if(err<0)
{
  printk("alloc xxxdriver num is err\r\n");
  goto err_alloc_chrdev_region;
}

//初始化结构体
cdev_init(xxxdriver_cdev,&xxxdriver_fops);

//驱动注册
err=cdev_add(xxxdriver_cdev,xxxdriver_num,1);
if(err<0)
{
  printk("cdev add is err\r\n");
  goto err_cdev_add;
}

//创建设备类
xxxdriver_class=class_create(THIS_MODULE,"xxx_class");
  err=PTR_ERR(xxxdriver_class);
  if(IS_ERR(xxxdriver_class))
    {
printk("xxxdriver creat class is err\r\n");
goto err_class_create;
  }


//创建设备
  xxxdriver_device=device_create(xxxdriver_class,NULL, xxxdriver_num,NULL, "xxxdevice");
 err=PTR_ERR(xxxdriver_device);
    if(IS_ERR(xxxdriver_device))
        {
printk("xxxdriver device creat is err \r\n");
goto err_device_create;
    }


printk("xxxdriver init is success\r\n");
return 0;

//硬件初始化部分

err_device_create:
class_destroy(xxxdriver_class);
err_class_create:
 cdev_del(xxxdriver_cdev);
err_cdev_add:
unregister_chrdev_region(xxxdriver_num, 1);

err_alloc_chrdev_region:
kfree(xxxdriver_cdev);

err_xxxdriver_alloc:
return err;

}

static __exit void xxxdriver_exit(void)
{

   int irq;
    irq=gpio_to_irq(EXYNOS4_GPX3(2));                //得到外部中断K1中断编号
 //开发板上有四个按键  注注销四次
 free_irq(irq,NULL); //注销key1中断

 irq=gpio_to_irq(EXYNOS4_GPX3(3));                   //得到外部中断K2中断编号
 free_irq(irq,NULL); //注销key2中断

 irq=gpio_to_irq(EXYNOS4_GPX3(4));                   //得到外部中断K3中断编号
 free_irq(irq,NULL); //注销key3中断

 irq=gpio_to_irq(EXYNOS4_GPX3(5));                   //得到外部中断K4中断编号
 free_irq(irq,NULL); //注销key4中断 


  device_destroy(xxxdriver_class,xxxdriver_num);
  class_destroy(xxxdriver_class);
  cdev_del(xxxdriver_cdev);
  unregister_chrdev_region(xxxdriver_num, 1);
  printk("xxxdriver is exit\r\n");
}


module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");

Makefile

KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
prog:
    arm-linux-gcc btn_app.c -o app
obj-m += btn.o

在开发板上安装运行驱动模块结果如下:

[aaa@qq.com/zhangchao]#insmod btn.ko
[10030.285000] xxxdriver init is success
[aaa@qq.com/zhangchao]#[10037.015000] key1 down!!
[10038.055000] key1 up!!
[10038.885000] key2 down!!
[10039.070000] key2 up!!
[10039.755000] key3 down!!
[10039.915000] key3 up!!
[10040.500000] key4 down!!
[10040.650000] key4 up!!

由于是中断所以并不需要app参与,就能验证代码的正确性,在这里按下按键即可。写法比较粗糙,下面也可以写一个简单的app函数。
每次有按键按下时,打印按键结果

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv[])
{
  int i,file_fp;
  unsigned char btn[4]={"0000"},cur[4]={"0000"};
  file_fp = open(argv[1],O_RDWR);
    while(1)
        {
        read(file_fp,cur,4);
    for(i=0;i<4;i++)
        {
    if(cur[i]!=btn[i])
        {
        btn[i]=cur[i];
        if(cur[i]=='1')
            printf("按键%d 按下\r\n",i+1);
        else
            printf("按键%d 弹起\r\n",i+1);
    }
    }
    }
  close(file_fp);
}

运行结果如下

[root@ZC/zhangchao]#./app /dev/xxxdevice 
[12301.190000] files open  success!!
[12303.865000] key1 down!!\r
按键1 按下
[12304.025000] key1 up!!
按键1 弹起
[12305.835000] key2 down!!\r
按键2 按下
[12305.975000] key2 up!!
按键2 弹起
[12306.640000] key3 down!!\r
按键3 按下
[12306.785000] key3 up!!
按键3 弹起
[12307.390000] key4 down!!\r
按键4 按下
[12307.510000] key4 up!!
按键4 弹起

代码优化1 基于数组方式的改进

前面写的驱动很粗糙,很冗杂,不够简洁而且没有安全检测,不够严谨,下面将进行一系列改进。

前面写的中断注册函数,简单罗列了4次,完全可以用一个for循环的方式一次搞定

下面是相关代码
全局变量中定义相关变量数组

unsigned int key_gpio[]={EXYNOS4_GPX3(2),EXYNOS4_GPX3(3),EXYNOS4_GPX3(4),EXYNOS4_GPX3(5)};
const char* key_name[]={"key1","key2","key3","key4"};
irq_handler_t key_handler[]={key1_handler,key2_handler,key3_handler,key4_handler};

注册函数中的改动

//定义错误返回类型
  int   err;
 //定义中断编号
 int irq,i;
 //设置中断属性 非共享中断 上升下降沿触发
 unsigned long flags=IRQF_DISABLED | 
                      IRQF_TRIGGER_FALLING | 
                      IRQF_TRIGGER_RISING;

    for(i=0;i<4;i++)
        {

    irq=gpio_to_irq(key_gpio[i]);               
     err=request_irq(irq, key_handler[i],flags, key_name[i],NULL); //注册key1中断
    //如果有注册失败的 跳出循环
    if(err<0)
        {
    break;
    }
    }
    //有注册失败的 将前面注册成功的释放掉
    if(err<0){
    for(--i;i>0;i--)
        {
    irq=gpio_to_irq(key_gpio[i]);               
     free_irq(irq, NULL);
    }

    }

注销函数中的改动

int irq,i;
   for(i=0;i<4;i++)
    {
    irq=gpio_to_irq(key_gpio[i]);               
    free_irq(irq,NULL);

   }

相关变动后,开发板上执行

[root@ZC/zhangchao]#insmod btn.ko 
[15675.145000] xxxdriver init is success
[root@ZC/zhangchao]#./app /dev/xxxdevice 
[15684.905000] files open  success!!
[15687.400000] key4 down!!
按键4 按下
[15687.570000] key4 up!!
按键4 弹起
[15688.205000] key3 down!!
按键3 按下
[15688.350000] key3 up!!
按键3 弹起
[15688.930000] key2 down!!
按键2 按下
[15689.075000] key2 up!!
按键2 弹起
[15689.610000] key1 down!!
按键1 按下
[15689.735000] key1 up!!
按键1 弹起
^C[15692.150000] file close  success!!

一样的效果但是代码的简洁性,安全性大大提高。

代码优化2 利用dev_id参数简化中断服务函数

我们会发现四个中断服务函数,代码结构基本类似,功能也大体相同,只有几个参数不一样,如果我们能把这些参数在一个函数内部区分开来,这样四个函数就可以合并为一个函数,又可以进一步简化代码。
怎么做?
利用dev_id参数,之前只是粗糙的写代码,这个参数被赋值NULL,这个参数的意义其实就是在多个中断共享一个中断编号时,用来区分彼此中断用的,虽然我们这里用的是独立中断,我们也可以用这个参数,达成我们想要的目的。我们这里利用这个参数用来进相同的中断函数,在中断服务函数里区分所要执行的事情。利用这个参数我们可以使用一个中断服务函数而不用使用四个。具体功能在函数体内部区分。
具体代码:
在中断注册函数内:

//定义错误返回类型
  int   err;
 //定义中断编号
 int irq,i;
 //设置中断属性 非共享中断 上升下降沿触发
 unsigned long flags=IRQF_DISABLED | 
                      IRQF_TRIGGER_FALLING | 
                      IRQF_TRIGGER_RISING;

    for(i=0;i<4;i++)
        {

    irq=gpio_to_irq(key_gpio[i]);               
     err=request_irq(irq, key_handler,flags, key_name[i],(void*)(i+1)); //注册key1中断
    //如果有注册失败的 跳出循环
    if(err<0)
        {
    break;
    }
    }
    //有注册失败的 将前面注册成功的释放掉
    if(err<0){
    for(--i;i>0;i--)
        {
    irq=gpio_to_irq(key_gpio[i]);               
     free_irq(irq, (void*)(i+1));
    }

中断注销函数:

 int irq,i;
   for(i=0;i<4;i++)
    {
    irq=gpio_to_irq(key_gpio[i]);               
    free_irq(irq,(void*)(i+1));

   }

中断服务函数:

irqreturn_t key_handler(int irq, void * dev_id)
{
    int dn;        //存放按键状态
    int index;     //存放dev_id编号
    index=(int)dev_id;
    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(key_gpio[index-1]);
    if(!dn)
        printk("key%d down!!\r\n",index);
    else
        printk("key%d up!!\r\n",index);
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[index-1]= '0'+!dn; 
    return IRQ_HANDLED;
}

代码优化3 利用结构体方式实现

编写驱动程序比较好的一种思想就是采用面向对象的设计思想
开发板由4个按键,每个功能的按键是一样的,写一个按键驱动代码需要的元素都是相同的
所以,把每个按键需要的元素进行封装,然后多个按键就是定义这个封装的实例
C语言没有类的概念,但是有结构体的概念,就可以把按键定义成结构体

struct key_info{
int key_gpio;  //按键IO
int key_num;   //按键编号
const char* name;  //按键名称
};

struct key_info keys[]={
{EXYNOS4_GPX3(2),0,"key1"},
{EXYNOS4_GPX3(3),1,"key2"},
{EXYNOS4_GPX3(4),2,"key3"},
{EXYNOS4_GPX3(5),3,"key4"}
};

驱动模块初始化函数中

//定义错误返回类型
  int   err;
 //定义中断编号
 int irq,i;
 //设置中断属性 非共享中断 上升下降沿触发
 unsigned long flags=IRQF_DISABLED | 
                      IRQF_TRIGGER_FALLING | 
                      IRQF_TRIGGER_RISING;

    for(i=0;i<4;i++)
        {

    irq=gpio_to_irq(keys[i].key_gpio);              
     err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注册key1中断
    //如果有注册失败的 跳出循环
    if(err<0)
        {
    break;
    }
    }
    //有注册失败的 将前面注册成功的释放掉
    if(err<0){
    for(--i;i>0;i--)
        {
    irq=gpio_to_irq(keys[i].key_gpio);              
     free_irq(irq, &keys[i]);
    }

    }

驱动卸载函数

 int irq,i;
   for(i=0;i<4;i++)
    {
    irq=gpio_to_irq(keys[i].key_gpio);              
    free_irq(irq,&keys[i]);

   }

中断服务函数

irqreturn_t key_handler(int irq, void * dev_id)
{
    int dn;        //存放按键状态
    //注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
    //这里进行还原,还原后可以取得这个结构的所有元素信息
    struct key_info *pdev_data=(struct key_info*)dev_id;

    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(pdev_data->key_gpio);
    if(!dn)
        printk("key%d down!!\r\n",pdev_data->key_num+1);
    else
        printk("key%d up!!\r\n",pdev_data->key_num+1);
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[pdev_data->key_num]= '0'+!dn;  
    return IRQ_HANDLED;
}

整个程序

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自动创建设备头文件
#include<linux/uaccess.h>
#include<linux/interrupt.h> //中断注册注销头文件
#include<linux/gpio.h>   //gpio相关的头文件

//按键数量
#define KEY_SIZE (4)

//按键缓冲区
unsigned char kbuf[KEY_SIZE]={"0000"};    //储存按键状态 初始化为字符 0,0表示没有按下,1表示按下 


struct key_info{
int key_gpio;  //按键IO
int key_num;   //按键编号
const char* name;  //按键名称
};

struct key_info keys[]={
{EXYNOS4_GPX3(2),0,"key1"},
{EXYNOS4_GPX3(3),1,"key2"},
{EXYNOS4_GPX3(4),2,"key3"},
{EXYNOS4_GPX3(5),3,"key4"}
};


//定义字符设备结构体
static struct cdev *xxxdriver_cdev;
//定义设备号(包含主次)
static dev_t xxxdriver_num=0;
//定义设备类
static struct class *xxxdriver_class;
//定义设备结构体
static struct device *xxxdriver_device;

//定义设备名称
#define XXXDRIVER_NAME "mydevice"



ssize_t XXX_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
    if(size>KEY_SIZE)
        {
    size=KEY_SIZE;
    }
    if(copy_to_user(usr,kbuf,size))
        {
        printk("copy to user faild\r\n");
    return -EFAULT;
    }

  return size;
}

ssize_t XXX_write (struct file *file, const char __user *usr, size_t size, loff_t *loft)
{
  printk("file write success!!\r\n");
  return 0;
}

int XXX_open (struct inode *node, struct file *pfile)
{
  printk("files open  success!!\r\n");
  return 0;
}

loff_t XXX_llseek(struct file *pfile, loff_t loft, int whence){
    printk("file lseek success!!\r\n");
   return 0;
}


int XXX_release (struct inode *node, struct file *file)
{
  printk("file close  success!!\r\n");
  return 0;
}




/*key中断服务函数:
按键按下、松开都会进入中断服务函数
程序要把按键的状态,保存起来,让用户通过用户变成API接口来读取按键状态
所以,函数中完成1):判断按键按下还是松开;2)把按键状态存储起来

*/
irqreturn_t key_handler(int irq, void * dev_id)
{
    int dn;        //存放按键状态
    //注册的时候传递进来的是每个案件元素的首地址,类型是struct key_info*
    //这里进行还原,还原后可以取得这个结构的所有元素信息
    struct key_info *pdev_data=(struct key_info*)dev_id;

    //判断按下还是松开按键;可通过读取按键IO电平状态来判断
    //硬件上,按下按键返回电平0 松开返回1
    dn=gpio_get_value(pdev_data->key_gpio);
    if(!dn)
        printk("key%d down!!\r\n",pdev_data->key_num+1);
    else
        printk("key%d up!!\r\n",pdev_data->key_num+1);
    //存储按键状态到按键缓冲区
    //这里我们想实现正逻辑,'0'表示没有按下,'1'表示按下。这里取反一次
    kbuf[pdev_data->key_num]= '0'+!dn;  
    return IRQ_HANDLED;
}




//文件操作函数结构体
static struct file_operations xxxdriver_fops={
    .owner=THIS_MODULE,
    .open=XXX_open,
    .release=XXX_release,
    .read=XXX_read,
    .write=XXX_write,
    .llseek=XXX_llseek,
};

static __init int xxxdriver_init(void)
{
 //定义错误返回类型
  int   err;
 //定义中断编号
 int irq,i;
 //设置中断属性 非共享中断 上升下降沿触发
 unsigned long flags=IRQF_DISABLED | 
                      IRQF_TRIGGER_FALLING | 
                      IRQF_TRIGGER_RISING;

    for(i=0;i<4;i++)
        {

    irq=gpio_to_irq(keys[i].key_gpio);              
     err=request_irq(irq, key_handler,flags,keys[i].name,&keys[i]); //注册key1中断
    //如果有注册失败的 跳出循环
    if(err<0)
        {
    break;
    }
    }
    //有注册失败的 将前面注册成功的释放掉
    if(err<0){
    for(--i;i>0;i--)
        {
    irq=gpio_to_irq(keys[i].key_gpio);              
     free_irq(irq, &keys[i]);
    }
    }



//分配字符设备结构体,前面只是定义没有分配空间
xxxdriver_cdev=cdev_alloc();
//判断分配成功与否
if(xxxdriver_cdev==NULL)
{
  err=-ENOMEM;
  printk("xxxdriver alloc is err\r\n");
  goto err_xxxdriver_alloc;
}

//动态分配设备号
err=alloc_chrdev_region(&xxxdriver_num, 0, 1, XXXDRIVER_NAME);
//错误判断
if(err<0)
{
  printk("alloc xxxdriver num is err\r\n");
  goto err_alloc_chrdev_region;
}

//初始化结构体
cdev_init(xxxdriver_cdev,&xxxdriver_fops);

//驱动注册
err=cdev_add(xxxdriver_cdev,xxxdriver_num,1);
if(err<0)
{
  printk("cdev add is err\r\n");
  goto err_cdev_add;
}

//创建设备类
xxxdriver_class=class_create(THIS_MODULE,"xxx_class");
  err=PTR_ERR(xxxdriver_class);
  if(IS_ERR(xxxdriver_class))
    {
printk("xxxdriver creat class is err\r\n");
goto err_class_create;
  }


//创建设备
  xxxdriver_device=device_create(xxxdriver_class,NULL, xxxdriver_num,NULL, "xxxdevice");
 err=PTR_ERR(xxxdriver_device);
    if(IS_ERR(xxxdriver_device))
        {
printk("xxxdriver device creat is err \r\n");
goto err_device_create;
    }


printk("xxxdriver init is success\r\n");
return 0;

//硬件初始化部分

err_device_create:
class_destroy(xxxdriver_class);
err_class_create:
 cdev_del(xxxdriver_cdev);
err_cdev_add:
unregister_chrdev_region(xxxdriver_num, 1);

err_alloc_chrdev_region:
kfree(xxxdriver_cdev);

err_xxxdriver_alloc:
return err;

}

static __exit void xxxdriver_exit(void)
{

   int irq,i;
   for(i=0;i<4;i++)
    {
    irq=gpio_to_irq(keys[i].key_gpio);              
    free_irq(irq,&keys[i]);

   }


  device_destroy(xxxdriver_class,xxxdriver_num);
  class_destroy(xxxdriver_class);
  cdev_del(xxxdriver_cdev);
  unregister_chrdev_region(xxxdriver_num, 1);
  printk("xxxdriver is exit\r\n");
}


module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");