zoukankan      html  css  js  c++  java
  • Linux驱动之按键驱动编写(中断方式)

    Linux驱动之按键驱动编写(查询方式)已经写了一个查询方式的按键驱动,但是查询方式太占用CPU,接下来利用中断方式编写一个驱动程序,使得CPU占有率降低,在按键空闲时调用read系统调用的进程可以休眠,还是以以下步骤编写:

    1、查看原理图,确定需要控制的IO端口

    2、查看芯片手册,确定IO端口的寄存器地址

    3、编写驱动代码

    4、确定应用程序功能,编写测试代码。

    5、编写Makefile,编译驱动代码与测试代码,在开发板上运行

    1、查看原理图,确定需要控制的IO端口

    打开原理图,确定需要控制的IO端口为GPF0、GPF2、GPG3、GPG11。可以看到它的中断号为IRQ_EINT0、IRQ_EINT2、IRQ_EINT11、IRQ_EINT19

    2、查看芯片手册,确定IO端口的寄存器地址,可以看到因为用了两组GPIO端口,所以它的基地址分别为0x56000050、0x56000060。中断方式的寄存器基地址为0x56000088、0x5600008c、0x56000090

    3、编写驱动代码,编写驱动代码的步骤如下:

     1)、编写出口、入口函数。代码如下,具体说明参考Linux驱动之LED驱动编写

    static int second_drv_init(void)
    {
        Secondmajor = register_chrdev(0, "buttons", &second_drv_ops);//注册驱动程序
    
        if(Secondmajor < 0)
            printk("failes 1 buttons_drv register
    ");
        
        second_drv_class = class_create(THIS_MODULE, "buttons");//创建类
        if(second_drv_class < 0)
            printk("failes 2 buttons_drv register
    ");
        second_drv_class_dev = class_device_create(second_drv_class, NULL, MKDEV(Secondmajor,0), NULL,"buttons");//创建设备节点
        if(second_drv_class_dev < 0)
            printk("failes 3 buttons_drv register
    ");
    
        
        gpfcon = ioremap(0x56000050, 16);//重映射
        gpfdat = gpfcon + 1;
        gpgcon = ioremap(0x56000060, 16);//重映射
        gpgdat = gpgcon + 1;
    
        printk("register buttons_drv
    ");
        return 0;
    }
    
    static void second_drv_exit(void)
    {
        unregister_chrdev(Secondmajor,"buttons");
    
        class_device_unregister(second_drv_class_dev);
        class_destroy(second_drv_class);
    
        iounmap(gpfcon);
        iounmap(gpgcon);
    
        printk("unregister buttons_drv
    ");
    }
    
    
    module_init(second_drv_init);
    module_exit(second_drv_exit);

    2)、 添加file_operations 结构体,这个是字符设备驱动的核心结构,所有的应用层调用的函数最终都会调用这个结构下面定义的函数

    static struct file_operations third_drv_ops = 
    {
        .owner   = THIS_MODULE,
        .open    =  third_drv_open,
        .read     = third_drv_read,
        .release = third_drv_close,//增加关闭函数
    };

    3)、分别编写file_operations 结构体下的open、read、release 函数。其中open函数主要将相应的IO端口配置成中断功能,并且向内核注册中断;read函数主要是在按键引脚电平未改变时休眠,然后按键引脚电平改变后,将按键值传给应用程序处理。(按键值的处理在中断处理程序中);relase函数的功能主要是从内核释放掉open函数注册的中断。程序如下:

    static int third_drv_open (struct inode * inode, struct file * file)
    {
        int ret;
        ret = request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "s1", (void * )&pins_desc[0]);//注册一个外部中断S1,双边沿触发,dev_id为&pins_desc[0]
        if(ret)
        {
            printk("open failed 1
    ");
            return -1;
        }
        ret = request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "s2", (void * )& pins_desc[1]);//注册一个外部中断S2,双边沿触发,dev_id为&pins_desc[1]
        if(ret)
        {
            printk("open failed 2
    ");
            return -1;
        }
        ret = request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "s3", (void * )&pins_desc[2]);//注册一个外部中断S3,双边沿触发,dev_id为&pins_desc[2]
        if(ret)
        {
            printk("open failed 3
    ");
            return -1;
        }
        ret = request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "s4", (void * )&pins_desc[3]);//注册一个外部中断S4,双边沿触发,dev_id为&pins_desc[3]
        if(ret)
        {
            printk("open failed 4
    ");
            return -1;
        }
        
        return 0;
    }
    
    
    static int third_drv_close(struct inode * inode, struct file * file)
    {
        free_irq(IRQ_EINT0 ,(void * )&pins_desc[0]);//释放中断,根据IRQ_EINT0找到irq_desc结构。根据pins_desc[0]找到irq_desc->action结构
    
         free_irq(IRQ_EINT2 ,(void * )& pins_desc[1]);//释放中断,根据IRQ_EINT2找到irq_desc结构。根据pins_desc[2]找到irq_desc->action结构
    
        free_irq(IRQ_EINT11 ,(void * )&pins_desc[2]);//释放中断,根据IRQ_EINT11找到irq_desc结构。根据pins_desc[3]找到irq_desc->action结构
    
        free_irq(IRQ_EINT19 ,(void * )&pins_desc[3]);//释放中断,根据IRQ_EINT19找到irq_desc结构。根据pins_desc[4]找到irq_desc->action结构
    
        return 0;
    }
    
    static ssize_t third_drv_read(struct file * file, char __user * userbuf, size_t count, loff_t * off)
    {
        int ret;
    
        if(count != 1)
        {
            printk("read error
    ");
            return -1;
        }
    
        wait_event_interruptible(button_waitq, ev_press);//将当前进程放入等待队列button_waitq中,并且释放CPU进入睡眠状态
        
        ret = copy_to_user(userbuf, &key_val, 1);//将取得的按键值传给上层应用
        ev_press = 0;//按键已经处理可以继续睡眠
        
        if(ret)
        {
            printk("copy error
    ");
            return -1;
        }
        
        return 1;
    }

    4)、中断处理函数的编写,中断处理函数利用注册中断时传入的dev_id这个值来判断是哪个按键发生了中断,dev_iq被赋值为pin_desc结构,如下:

    struct pin_desc 
    {
        unsigned int pin;      //是哪个按键
        unsigned int key_val;  //按键的按键值
    };
    
    static struct pin_desc  pins_desc[4] = 
    {
        {S3C2410_GPF0,0x01},
        {S3C2410_GPF2,0x02},
        {S3C2410_GPG3,0x03},
        {S3C2410_GPG11,0x04}
    };

    取得哪个引脚发生的中断信息后,取得相应的引脚电平,然后确定按键值。接着将值传给key_val,再唤醒调用read的进程,将值直接拷贝给应用程序。具体函数如下

    static unsigned int key_val;//全局变量
     
    /*
      *0x01、0x02、0x03、0x04表示按键被按下
      */
      
    /*
      *0x81、0x82、0x83、0x84表示按键被松开
      */
    
    /*
      *利用dev_id的值为pins_desc来判断是哪一个按键被按下或松开
      */
    static irqreturn_t buttons_irq(int irq, void *dev_id)
    {
        unsigned int pin_val;
        struct pin_desc * pin_desc = (struct pin_desc *)dev_id;//取得哪个按键被按下的状态,dev_id是action->dev_id,即在注册中断时传入的&pin_desc[num]
        
        pin_val = s3c2410_gpio_getpin(pin_desc->pin);          //取得按键对应的IO口的电平状态
        
        if(pin_val) //按键松开
            key_val = 0x80 | pin_desc->key_val;
        else
            key_val = pin_desc->key_val;
    
    
        wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程,即调用read函数的进程 */
        ev_press = 1;    
        
        return IRQ_HANDLED;
    }

    5)、整体代码

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <asm/io.h>        //含有iomap函数iounmap函数
    #include <asm/uaccess.h>//含有copy_from_user函数
    #include <linux/device.h>//含有类相关的处理函数
    #include <asm/arch/regs-gpio.h>//含有S3C2410_GPF0等相关的
    #include <linux/irq.h>    //含有IRQ_HANDLEDIRQ_TYPE_EDGE_RISING
    #include <asm-arm/irq.h>   //含有IRQT_BOTHEDGE触发类型
    #include <linux/interrupt.h> //含有request_irq、free_irq函数
    
    //#include <asm-armarch-s3c2410irqs.h>
    
    
    
    static struct class *third_drv_class;//
    static struct class_device *third_drv_class_dev;//类下面的设备
    static int thirdmajor;
    
    static unsigned long *gpfcon = NULL;
    static unsigned long *gpfdat = NULL;
    static unsigned long *gpgcon = NULL;
    static unsigned long *gpgdat = NULL;
    
    
    
    
    struct pin_desc 
    {
        unsigned int pin;      //是哪个按键
        unsigned int key_val;  //按键的按键值
    };
    
    static struct pin_desc  pins_desc[4] = 
    {
        {S3C2410_GPF0,0x01},
        {S3C2410_GPF2,0x02},
        {S3C2410_GPG3,0x03},
        {S3C2410_GPG11,0x04}
    };
    
    
    unsigned int ev_press;
    DECLARE_WAIT_QUEUE_HEAD(button_waitq);//注册一个等待队列button_waitq
    
    static unsigned int key_val;//全局变量
     
    /*
      *0x01、0x02、0x03、0x04表示按键被按下
      */
      
    /*
      *0x81、0x82、0x83、0x84表示按键被松开
      */
    
    /*
      *利用dev_id的值为pins_desc来判断是哪一个按键被按下或松开
      */
    static irqreturn_t buttons_irq(int irq, void *dev_id)
    {
        unsigned int pin_val;
        struct pin_desc * pin_desc = (struct pin_desc *)dev_id;//取得哪个按键被按下的状态,dev_id是action->dev_id,即在注册中断时传入的&pin_desc[num]
        
        pin_val = s3c2410_gpio_getpin(pin_desc->pin);          //取得按键对应的IO口的电平状态
        
        if(pin_val) //按键松开
            key_val = 0x80 | pin_desc->key_val;
        else
            key_val = pin_desc->key_val;
    
    
        wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程,即调用read函数的进程 */
        ev_press = 1;    
        
        return IRQ_HANDLED;
    }
    
    
    
    static int third_drv_open (struct inode * inode, struct file * file)
    {
        int ret;
        ret = request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "s1", (void * )&pins_desc[0]);//注册一个外部中断S1,双边沿触发,dev_id为&pins_desc[0]
        if(ret)
        {
            printk("open failed 1
    ");
            return -1;
        }
        ret = request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "s2", (void * )& pins_desc[1]);//注册一个外部中断S2,双边沿触发,dev_id为&pins_desc[1]
        if(ret)
        {
            printk("open failed 2
    ");
            return -1;
        }
        ret = request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "s3", (void * )&pins_desc[2]);//注册一个外部中断S3,双边沿触发,dev_id为&pins_desc[2]
        if(ret)
        {
            printk("open failed 3
    ");
            return -1;
        }
        ret = request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "s4", (void * )&pins_desc[3]);//注册一个外部中断S4,双边沿触发,dev_id为&pins_desc[3]
        if(ret)
        {
            printk("open failed 4
    ");
            return -1;
        }
        
        return 0;
    }
    
    
    static int third_drv_close(struct inode * inode, struct file * file)
    {
        free_irq(IRQ_EINT0 ,(void * )&pins_desc[0]);//释放中断,根据IRQ_EINT0找到irq_desc结构。根据pins_desc[0]找到irq_desc->action结构
    
         free_irq(IRQ_EINT2 ,(void * )& pins_desc[1]);//释放中断,根据IRQ_EINT2找到irq_desc结构。根据pins_desc[2]找到irq_desc->action结构
    
        free_irq(IRQ_EINT11 ,(void * )&pins_desc[2]);//释放中断,根据IRQ_EINT11找到irq_desc结构。根据pins_desc[3]找到irq_desc->action结构
    
        free_irq(IRQ_EINT19 ,(void * )&pins_desc[3]);//释放中断,根据IRQ_EINT19找到irq_desc结构。根据pins_desc[4]找到irq_desc->action结构
    
        return 0;
    }
    
    static ssize_t third_drv_read(struct file * file, char __user * userbuf, size_t count, loff_t * off)
    {
        int ret;
    
        if(count != 1)
        {
            printk("read error
    ");
            return -1;
        }
    
        wait_event_interruptible(button_waitq, ev_press);//将当前进程放入等待队列button_waitq中,并且释放CPU进入睡眠状态
        
        ret = copy_to_user(userbuf, &key_val, 1);//将取得的按键值传给上层应用
        ev_press = 0;//按键已经处理可以继续睡眠
        
        if(ret)
        {
            printk("copy error
    ");
            return -1;
        }
        
        return 1;
    }
    
    
    
    static struct file_operations third_drv_ops = 
    {
        .owner   = THIS_MODULE,
        .open    =  third_drv_open,
        .read     = third_drv_read,
        .release = third_drv_close,//增加关闭函数
    };
    
    static int third_drv_init(void)
    {
        thirdmajor = register_chrdev(0, "buttons", &third_drv_ops);//注册驱动程序
    
        if(thirdmajor < 0)
            printk("failes 1 buttons_drv register
    ");
        
        third_drv_class = class_create(THIS_MODULE, "buttons");//创建类
        if(third_drv_class < 0)
            printk("failes 2 buttons_drv register
    ");
        third_drv_class_dev = class_device_create(third_drv_class, NULL, MKDEV(thirdmajor,0), NULL,"buttons");//创建设备节点
        if(third_drv_class_dev < 0)
            printk("failes 3 buttons_drv register
    ");
    
        
        gpfcon = ioremap(0x56000050, 16);//重映射
        gpfdat = gpfcon + 1;
        gpgcon = ioremap(0x56000060, 16);//重映射
        gpgdat = gpgcon + 1;
    
        printk("register buttons_drv
    ");
        return 0;
    }
    
    static void third_drv_exit(void)
    {
        unregister_chrdev(thirdmajor,"buttons");
    
        class_device_unregister(third_drv_class_dev);
        class_destroy(third_drv_class);
    
        iounmap(gpfcon);
        iounmap(gpgcon);
    
        printk("unregister buttons_drv
    ");
    }
    
    
    module_init(third_drv_init);
    module_exit(third_drv_exit);
    
    MODULE_LICENSE("GPL");

    4、确定应用程序功能,编写测试代码。

    测试程序实现四个按键中有一个按键按下时,打印出这个按键的按键值。./third_test。直接看代码

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    /*
      *usage ./buttonstest
      */
    int main(int argc, char **argv)
    {
        int fd;
        char* filename="dev/buttons";
       unsigned char key_val;
      unsigned long cnt=0;
        fd = open(filename, O_RDWR);//打开dev/firstdrv设备文件
        if (fd < 0)//小于0说明没有成功
        {
            printf("error, can't open %s
    ", filename);
            return 0;
        }
        
        if(argc !=1)
        {
            printf("Usage : %s ",argv[0]);
         return 0;
        }
    
      while(1)
      {
         read(fd, &key_val, 1);
         printf("key_val: %x
    ",key_val);
      }
        
       return 0;
    }

    5、编写Makefile,编译驱动代码与测试代码,在开发板上运行

    Makefile源码如下:

    KERN_DIR = /work/system/linux-2.6.22.6
    
    all:
            make -C $(KERN_DIR) M=`pwd` modules //M='pwd'表示当前目录。这句话的意思是利用内核目录下的Makefile规则来编译当前目录下的模块
    
    clean:
            make -C $(KERN_DIR) M=`pwd` modules clean
            rm -rf modules.order
    
    obj-m   +=third_drv.o//调用内核目录下Makefile编译时需要用到这个参数

    1)、然后在当前目录下make后编译出third_drv.ko文件

    2)、arm-linux-gcc -o third_test third_test.c编译出third_test测试程序

    3)、cp third_drv.ko third_test /work/nfs_root将编译出来的文件拷贝到开发板挂接的网络文件系统上

    4)、执行insmod third_drv.ko加载驱动。

    5)、./third_test测试程序,按下按键,成功打印按键值,用top命令查看应用程序发现third_test程序占用了0%的CPU资源,驱动程序相比查询方式的驱动改善了。

  • 相关阅读:
    《Programming WPF》翻译 第8章 1.动画基础
    一些被遗忘的设计模式
    《Programming WPF》翻译 第4章 数据绑定
    《Programming WPF》翻译 第3章 控件
    《Programming WPF》翻译 第5章 样式和控件模板
    《Programming WPF》翻译 第7章 绘图
    《Programming WPF》翻译 第9章 自定义控件
    《Programming WPF》翻译 第7章 绘图 (2)
    《Programming WPF》翻译 第8章 前言
    关于Debug和Release之本质区别
  • 原文地址:https://www.cnblogs.com/andyfly/p/9479763.html
Copyright © 2011-2022 走看看