zoukankan      html  css  js  c++  java
  • 中断方式按键驱动程序

    学习目的:

    • 使用中断方式改写查询方式按键驱动程序

    上一篇实现了查询方式的按键驱动程序,编写测试程序测试后发现,虽然应用程序可以通过系统调用使用驱动程序获取按键状态,但应用程序占CPU的资源极高。这一篇在编写按键驱动程序中引入中断方式,优化查询方式实现的按键驱动程序。

    核心思想:驱动程序中的button_drv_read函数内部加入休眠操作,当应用程序调用read函数去读取按键值时,此时如果按键无按下或松开动作,该进程被加以休眠队列。当按键按下触发相应中断服务程序,此时唤醒休眠的应用程序,在休眠地方继续运行读取按键值,并返回给应用程序。

    程序编写步骤(以查询方式按键驱动程序为模板进行修改):

    1)修改button_drv_open函数,在其中加入注册按键的中断服务函数

    2)编写中断服务函数,在中断服务函数中唤醒加入休眠队列的用户进程

    3)修改button_drv_read函数,判断按键是否有有效动作,若无有效动作将用户进程加入休眠队列中

    4)修改button_drc_exit函数,卸载驱动程序时,卸载按键中断服务函数设置


    1、硬件连接

    按键名称 Altium Designer绘制电路图表示网络 连接芯片引脚 对应芯片外部中断号
    S2 EINT0 GPF0 EINT0
    S3 EINT2 GPF2 EINT2
    S4 EINT11 GPG3 EINT11
    S5 EINT19 GPG11 EINT19

    如上表所示,S2、S3、S4、S5按键分别连接到2440的GPF0、GPF2、GPG3、GPG11引脚,对应控制器的外部中断0、外部中断2、外部中断11、外部中断19。当使能控制器外部中断0、2、11、19,设置外部中断的触发方式,满足触发条件时,CPU将进入中断模式。得益于这样硬件设计机制,才能保证可以通过中断方式编写驱动程序。

    2、中断注册和卸载函数

    2.1 中断注册函数

    int request_irq(unsigned int irq,
            irq_handler_t handler,
            unsigned long irqflags, const char * devname, void *dev_id)

    request_irq函数是内核中提供的中断注册函数,函数的各参数解释如下:

    irq:中断号,控制器相关联的,内核支持的硬件平台的中断号一般在include/asm-xxx/arch-xxx目录的irq.h文件中定义。以s3c2410为例,存放路径内核源码树includeasm-armarch-s3c2410irq.h文件

    handler:中断服务函数,irq对应中断发生时,调用中断服务函数

    irqflags:中断处理的属性

    devname:设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称

    dev_id: 一般设置为NULL,也可以指定指向特殊数据类型,该参数会传递给中断服务函数

    2.2 中断卸载函数

    void free_irq(unsigned int irq, void *dev_id)

    free_irq函数作用与request_irq功能相反,该函数是内核中提供的卸载中断服务的函数,各参数解释如下:

    irq:要卸载中断号

    dev_id:同request中的dev_id相同

    3、驱动程序实现

    3.1 button_drv_open函数

    static int button_drv_open(struct inode *inode, struct file *file)
    {
        int i;
        
        *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
        *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
        
        /* 注册中断处理函数 */
        for(i = 0; i < BUTTON_NUMS; i++)
            request_irq(btn_desc[i].irq_type, button_irq_handle, btn_desc[i].flags, btn_desc[i].name, &btn_desc[i]);
        
        return 0;
    }

    button_drv_open函数,配置2440连接按键的引脚为输入模式,调用request_irq注册了按键引脚的外部中断,设置以双边沿触发方式触发中断

    btn_desc为struct button_desc类型结构体数组,struct button_desc结构体数据类型描述如下:

    struct button_desc
    {
        int pin;
        int irq_type;
        unsigned long flags;
        char *name;
        int key_val;
    };

    pin:连接按键相关2440引脚

    irq_type:中断编号

    flags:中断处理的属性

    name:中断名称

    key_val:用以描述此按键的值

    3.2 button_irq_handle中断服务函数

    button_irq_handle为中断服务程序,其源码如下:

    static irqreturn_t button_irq_handle(int irq, void *dev_id)
    {    
        struct button_desc *pdesc = NULL;
        unsigned char pin_val;
        
        pdesc = (struct button_desc *)dev_id;
        
        pin_val = gpio_get_value(pdesc->pin);    
    
        if(pin_val == 1)
        {
            key_status = pdesc->key_val | 0x80;
        }
        else
        {
            key_status = pdesc->key_val;
        }
        
        event_trig = 1;
        wake_up_interruptible(&button_waitq); 
        
        return IRQ_RETVAL(IRQ_HANDLED);
    }

    按键按下时,触发外部中断,此时相应的中断服务函数会被调用。中断服务函数中根据传入参数获取当前触发中断的外部引脚信息,并读取该引脚状态。通过调用wake_up_interruptible函数唤醒button_waitq队列中休眠等待有效数据读取的用户进程,并将event_trig变量设置为1。

    gpio_get_val函数根据传入引脚,返回当前引脚的状态,1:按键松开,0:按键按下

    key_status:              S2              S3             S3               S4

      键值:按下     0x01           0x02          0x03            0x04

      键值:松开     0x81           0x82          0x83            0x84

    3.3 button_drv_read函数

    static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
    {
        if(count != 1)
            return EINVAL;
        
        wait_event_interruptible(button_waitq, event_trig);
        
        if(copy_to_user(buf, &key_status, count))
            return EFAULT;
        
        event_trig = 0;
        return 0;
    }

    应用程序调用read函数时被调用,event_trig为1,表示按键有有效值可读取,此时直接拷贝描述按键状态变量key_status值给用户程序;event_trig为0,表示按键无有效值可读,此时调用read函数读取按键信息的进程被加入到button_waitq为头部队列中进行休眠。当按键按下触发中断,中断服务函数中将该休眠的进程唤醒,并将event_trig置为1,完成后续读操作。

    3.3 button_drv_close函数

    static int button_drv_close(struct inode *inode, struct file *file)
    {
        int i;
        
        for(i = 0; i < BUTTON_NUMS; i++)
            free_irq(btn_desc[i].irq_type, &btn_desc[i]);
        
        return 0;
    }

    在不使用时,调用free_irq注销中断服务程序,释放相关资源

    4、编写驱动测试程序

    int main(int argc, char **argv)
    {
        int fd, ret;
        unsigned char key_buf;
        
        fd = open("/dev/button", O_RDWR);
        if(fd == -1)
        {
            printf("can't open...
    ");
            exit(EXIT_FAILURE);
        }
        
        while(1)
        {
            ret = read(fd, &key_buf, 1);
            if(ret < 0)
            {
                printf("read err...
    ");
                continue;
            }
            
            /* 判断有按键按下,打印按键信息 */
            printf("key_val=0x%x
    ", key_buf);
        }
    
        exit(EXIT_SUCCESS);
    }

    读取按键状态信息,并在串口终端打印读取的状态

    5、测试结果

    驱动中设置外部中断触发方式为双边沿触发,按键按下松开时都能够触发中断服务程序

    第一次按下S3按键,读取值时0x3,松开时读取值0x83

    第一次按下S2按键,读取值时0x2,松开时读取值0x82

    第一次按下S1按键,读取值时0x1,松开时读取值0x81                   

     

    读取按键,当无有效按键值可读时,测试进程一般处于休眠状态,占用CPU资源率基本为0,改善了上一篇中查询方式占CPU资源多的缺点

     

    完整驱动程序代码

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <linux/irq.h>
    #include <asm/uaccess.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <plat/gpio-fns.h>
    #include <mach/gpio-nrs.h>
    #include <linux/interrupt.h>
    #include <linux/wait.h>
    #include <linux/sched.h>
    #include <linux/device.h>
    #include <linux/gpio.h>
    
    
    #define BUTTON_NUMS    4
    #define IRQT_BOTHEDGE IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
    
    static int major;
    static int event_trig = 0;
    
    static unsigned char key_status;
    
    static volatile unsigned long *gpfcon = NULL;
    static volatile unsigned long *gpgcon = NULL;
    static volatile unsigned long *gpfdat = NULL;
    static volatile unsigned long *gpgdat = NULL;
    
    static struct class *button_drv_class;
    static struct class_device    *button_drv_class_dev;
    
    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    
    struct button_desc
    {
        int pin;
        int irq_type;
        unsigned long flags;
        char *name;
        int key_val;
    };
    
    static struct button_desc btn_desc[BUTTON_NUMS] = {
        {S3C2410_GPF(0),  IRQ_EINT0,  IRQT_BOTHEDGE, "S2", 1},
        {S3C2410_GPF(2),  IRQ_EINT2,  IRQT_BOTHEDGE, "S3", 2},
        {S3C2410_GPG(3),  IRQ_EINT11, IRQT_BOTHEDGE, "S4", 3},
        {S3C2410_GPG(11), IRQ_EINT19, IRQT_BOTHEDGE, "S5", 4},
    };
    
    static int button_drv_open(struct inode *inode, struct file *file);
    static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
    static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
    static int button_drv_close(struct inode *inode, struct file *file);
    
    struct file_operations button_drv_fileop = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   button_drv_open,
        .read   =   button_drv_read,
        .write  =   button_drv_write,
        .release =  button_drv_close,
    };
    
    static irqreturn_t button_irq_handle(int irq, void *dev_id)
    {    
        struct button_desc *pdesc = NULL;
        unsigned char pin_val;
        
        pdesc = (struct button_desc *)dev_id;
        
        pin_val = gpio_get_value(pdesc->pin);    
    
        if(pin_val == 1)
        {
            key_status = pdesc->key_val | 0x80;
        }
        else
        {
            key_status = pdesc->key_val;
        }
        
        event_trig = 1;
        wake_up_interruptible(&button_waitq); 
        
        return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    static int button_drv_open(struct inode *inode, struct file *file)
    {
        int i;
        
        *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
        *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
        
        /* 注册中断处理函数 */
        for(i = 0; i < BUTTON_NUMS; i++)
            request_irq(btn_desc[i].irq_type, button_irq_handle, btn_desc[i].flags, btn_desc[i].name, &btn_desc[i]);
        
        return 0;
    }
    
    static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
    {
        if(count != 1)
            return EINVAL;
        
        wait_event_interruptible(button_waitq, event_trig);
        
        if(copy_to_user(buf, &key_status, count))
            return EFAULT;
        
        event_trig = 0;
        return 0;
    }
    
    static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
    {
        printk("button_drv_write
    ");
        
        return 0;
    }
    
    static int button_drv_close(struct inode *inode, struct file *file)
    {
        int i;
        
        for(i = 0; i < BUTTON_NUMS; i++)
            free_irq(btn_desc[i].irq_type, &btn_desc[i]);
        
        return 0;
    }
            
    static int button_drv_init(void)
    {
        major = register_chrdev(0, "button_light", &button_drv_fileop);
        
        button_drv_class = class_create(THIS_MODULE, "button_drv");
        //button_drv_class_dev = class_device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
        button_drv_class_dev = device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
        
        gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
        gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
        gpfdat = gpfcon + 1;
        gpgdat = gpgcon + 1;
        
        return 0;
    }
    
    static void button_drv_exit(void)
    {
        unregister_chrdev(major, "button_drv");
        
        //class_device_unregister(button_drv_class_dev);
        device_unregister(button_drv_class_dev);
        class_destroy(button_drv_class);
        
        iounmap(gpfcon);
        iounmap(gpgcon);
    }
    
    module_init(button_drv_init);
    module_exit(button_drv_exit);
    
    MODULE_LICENSE("GPL");
    irq_button_drv.c

    完整测试程序代码

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/fcntl.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    
    int main(int argc, char **argv)
    {
        int fd, ret;
        unsigned char key_buf;
        
        fd = open("/dev/button", O_RDWR);
        if(fd == -1)
        {
            printf("can't open...
    ");
            exit(EXIT_FAILURE);
        }
        
        while(1)
        {
            ret = read(fd, &key_buf, 1);
            if(ret < 0)
            {
                printf("read err...
    ");
                continue;
            }
            
            /* 判断有按键按下,打印按键信息 */
            printf("key_val=0x%x
    ", key_buf);
        }
    
        exit(EXIT_SUCCESS);
    }
    irq_button_test.c
  • 相关阅读:
    第一节:理解垃圾回收平台的基本工作原理
    回想笔记 瞎比比 域名注册 解析绑定ip 下载证书 设置证书 重定向http到https请求
    flask 设置https请求 访问flask服务器
    关于 服务器ip和域名进行一个绑定
    ubuntu 安装flask+nginx+gunicorn 待定
    使用flask-dropzone 上传图片文件
    flask 对于用户登录保持状态 flask_login
    flask 对于邮件url进行一个加密防止爆破
    flask 密码加密 视频资料
    flask 多线程邮件异步发送 视频资料
  • 原文地址:https://www.cnblogs.com/053179hu/p/13393590.html
Copyright © 2011-2022 走看看