【IMX6ULL】 Linux 的GPIO中断和定时器消抖配置按键驱动

Linux 中断驱动

相关API函数

request_irq

用于申请中断,但不能在中断上下文或其它禁止睡眠的代码段中使用该函数。

int request_irq(
    unsigned int irq, 
    irq_handler_t handler, 
    unsigned long flags,
    const char *name, 
    void *dev)

irq:要申请中断的中断号。

handler:中断处理函数,当中断发生以后就会执行此中断处理函数。

flags:中断标志,文件 include/linux/interrupt.h 里面存放了所有的中断标志。

name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。

dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

**返回值:**0:中断申请成功,其他负值:中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

部分中断标志:
在这里插入图片描述

free_irq

用于释放中断并删除中断处理函数。

void free_irq(
    unsigned int irq, 
    void *dev)

irq:要释放的中断的中断号。

dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。

gpio_to_irq

用于获取 gpio 的中断号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

gpio:要获取的 GPIO 编号。

返回值:GPIO 对应的中断号

irq_of_parse_and_map

从设备树的 interupts 属性中提取到对应的设备号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

dev:设备节点。

index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。

返回值:中断号。

中断处理函数

irqreturn_t (*irq_handler_t) (int, void *)

int:对应的中断号;

void*:一个通用指针

中断使能和失能函数

/* 使能/失能某一个中断 */
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

/* 不等待当前中断处理程序执行完毕,直接禁止某个中断 */
void disable_irq_nosync(unsigned int irq)

/* 使能/失能全局中断 */
local_irq_enable()
local_irq_disable()

/* 用于禁止中断,并且将中断状态保存在 flags 中 */
local_irq_save(flags)
/* 用于恢复中断,将中断到 flags 状态 */
local_irq_restore(flags)

上半部和下半部

​ 上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。

​ 下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

  1. 为什么分上半部和下半部?
    为了实现中断处理函数的快进快出。

  2. 哪些代码放在上半部?哪些放在下半部?

    ① 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。

    ② 如果要处理的任务对时间敏感,可以放到上半部。

    ③ 如果要处理的任务与硬件有关,可以放到上半部。

    ④ 除了上述三点以外的其他任务,优先考虑放到下半部。

下半部机制

  1. 软中断、tasklet
  2. 工作队列

设备树里的中断信息节点

位于内核源码的 arch/arm/boot/dts/imx6ull.dtsi 文件里
在这里插入图片描述
某个GPIO节点也是可以作为中断控制器,每个IO能作为特定的中断源:
在这里插入图片描述
①、#interrupt-cells,指定中断源的信息 cells 个数。

②、interrupt-controller,表示当前节点为中断控制器。

③、interrupts,指定中断号,触发方式等。

④、interrupt-parent,指定父中断,也就是中断控制器。

利用按键产生外部中断

一、修改设备树文件

在这里插入图片描述
指定父中断即中断控制器为 gpio1,配置中断源为GPIO1_IO18,中断的触发方式为按下和释放都会触发中断。

修改完后重新编译设备树,并用新的设备树文件启动Linux。

二、驱动代码编写

代码思路:

一旦有按键按下,就会产生一个中断,中断中又会启动定时器,定时器计时10ms后,进入定时器处理函数,判断按键是否按下,并对 releasekey 赋值,接下来判断 releasekey 的值,如果为1则表示一次完整的按键过程已经结束,然后把 keyvalue 的值拷贝到读取缓冲区,让用户读取。

#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IRQ_CNT     1
#define IRQ_NAME    "irq"

#define KEY0VALUE   0X01		/* KEY0 按键值 */
#define INVAKEY     0XFF		/* 无效的按键值 */
#define KEY_NUM     1			/* 按键数量 */


/* 中断 IO 描述结构体 */
struct irq_keydesc 
{
    int gpio;                               /* gpio */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
};


/* irq 设备结构体 */
struct irq_dev{
    dev_t   devid;
    struct  cdev    cdev;
    struct  class   *class;
    struct  device  *device;
    struct  device_node *nd;
    int major;
    int minor;
    atomic_t keyvalue;                      /* 有效的按键值 */
    atomic_t releasekey;                    /* 标记是否完成一次释放的按键 */
    struct timer_list timer;                /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */
};

struct irq_dev irq;     /* irq设备 */

/* 中断服务函数:属于中断处理的上半部 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct irq_dev *dev = (struct irq_dev *)dev_id;
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    
    /* 如果产生一个中断信号,则就重新设置定时器的超时时间,即定时器归零 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器处理函数,作为按键的消抖延时函数:属于中断处理的下半部 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct irq_dev *dev = (struct irq_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);
    if(value == 0){ /* 按下按键 */
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else{           /* 按键松开 */
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ 
    }
}

/* 按键 IO 初始化 */
static int key_io_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    /* 获取 key 节点 */
    irq.nd = of_find_node_by_path("/key");
    /* 如果找不到节点,就返回错误信息 */
    if(irq.nd == NULL)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    }
    /* 如果找到了节点,就返回成功找到的提示信息 */
    else
    {
        printk("key node find!\r\n");
    }

    /* 提取 GPIO */
    for ( i = 0; i < KEY_NUM; i++)
    {
        irq.irqkeydesc[i].gpio = of_get_named_gpio(irq.nd, "key-gpio", i);
        if(irq.irqkeydesc[i].gpio < 0)
        {
            printk("can't get key%d\r\n",i);
        }
    }

    /* 初始化 key 所使用的 IO,并且设置成中断模式 */
    /* 轮流初始化所有的按键,包括申请 IO、设置 IO 为输入模式、从设备树中获取 IO 的中断号等等 */
    for ( i = 0; i < KEY_NUM; i++)
    {
        /* 获取按键名 */
        memset(irq.irqkeydesc[i].name, i, sizeof(irq.irqkeydesc[i].name));
        /* 将按键名打印出来 */
        sprintf(irq.irqkeydesc[i].name, "KEY%d", i);
        /* 申请一个IO */
        gpio_request(irq.irqkeydesc[i].gpio, irq.irqkeydesc[i].name);
        /* 配置IO方向为输入模式 */
        gpio_direction_input(irq.irqkeydesc[i].gpio);	
        /* 获取中断号 */
		irq.irqkeydesc[i].irqnum = irq_of_parse_and_map(irq.nd, i);
        /* 打印出按键编号和中断编号 */
        printk("key%d:gpio=%d, irqnum=%d\r\n",i, 
               irq.irqkeydesc[i].gpio, irq.irqkeydesc[i].irqnum);
    }
	/* 申请中断 */
    irq.irqkeydesc[0].handler = key0_handler;
    irq.irqkeydesc[0].value = KEY0VALUE;
    
    /* 循环给每个按键申请中断 */
    for ( i = 0; i < KEY_NUM; i++)
    {
        ret = request_irq(
            irq.irqkeydesc[i].irqnum, irq.irqkeydesc[i].handler,
            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
            irq.irqkeydesc[i].name, &irq);
        
        /* 如果申请失败,就返回错误信息 */
        if(ret < 0){
            printk("irq %d request failed!\r\n", irq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }
    
    /* 创建定时器 */
    init_timer(&irq.timer);
    /* 配置定时器服务函数 */
    irq.timer.function = timer_function;

    return 0;
}


static int irq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &irq;
    return 0;
}

static ssize_t irq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct irq_dev *dev = (struct irq_dev*)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* 有按键按下,就会产生一个中断,中断中又会启动定时器,定时器计时10ms后,进入定时器处理函数,判断按键		 的是否按下,并对 releasekey 赋值,接下来判断 releasekey 的值,如果为1则表示一次完整的按键过程	   已经结束,然后把 keyvalue 的值拷贝到读取缓冲区,让用户读取。 */
    if(releasekey)	
    {
        if(keyvalue & 0x80)
        {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    }
    else
    {
        goto data_error;
    }
    return 0;

    data_error:
    return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations irq_fops = 
{
    .owner = THIS_MODULE,
    .open = irq_open,
    .read = irq_read,
};

static int __init user_irq_init(void)
{

    /* 注册字符驱动设备 */
    if(irq.major)
    {
        irq.devid = MKDEV(irq.major, 0);
        register_chrdev_region(irq.devid, IRQ_CNT, IRQ_NAME);
    }
    else
    {
        alloc_chrdev_region(&irq.devid, 0, IRQ_CNT, IRQ_NAME);
        irq.major = MAJOR(irq.devid);
        irq.minor = MINOR(irq.devid);
    }

    cdev_init(&irq.cdev, &irq_fops);
    cdev_add(&irq.cdev, irq.devid, IRQ_CNT);

    irq.class = class_create(THIS_MODULE, IRQ_NAME);
    if (IS_ERR(irq.class))
    {
        return PTR_ERR(irq.class);
    }

    irq.device = device_create(irq.class, NULL, irq.devid, NULL, IRQ_NAME);
    if (IS_ERR(irq.device))
    {
        return PTR_ERR(irq.device);
    }

    /* 初始化原子变量 */
    atomic_set(&irq.keyvalue, INVAKEY);
    atomic_set(&irq.releasekey, 0);
    /* 初始化按键 */
    key_io_init();
    return 0;
}

static void __exit user_irq_exit(void)
{
    unsigned int i = 0;

    del_timer_sync(&irq.timer);

    for ( i = 0; i < KEY_NUM; i++)
    {
        free_irq(irq.irqkeydesc[i].irqnum, &irq);
    }
    
    cdev_del(&irq.cdev);
    unregister_chrdev_region(irq.devid, IRQ_CNT);
    device_destroy(irq.class, irq.devid);
    class_destroy(irq.class);
}

module_init(user_irq_init);
module_exit(user_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Swiler");

三、应用代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"


/* argv[]:具体的参数内容,字符串形式
 * ./KeyAPP     <filename> 
 * ./KeyAPP     /dev/key    
 */
int main(int argc, char *argv[])
{
    int fd;
    int ret = 0;
    char *filename;
    unsigned char data;

    if(argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }

    while (1)
    {
        ret = read(fd, &data, sizeof(data));
        if(ret < 0)
        {
            /* 数据读取错误或者无效 */
        }
        else/* 数据读取正确 */
        {
            if(data)    /* 读取到数据 */
                printf("KEY0 Press, value = %#X\r\n", data);
        }
    }

    ret = close(fd);
    if (fd < 0)
    {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值