at91sam9260中断驱动

这个驱动中的程序,用到了按键中断、防抖动的定时器功能、队列等知识

一、各部分理解
1、定时器:

    // 初始化定时器,实现软件的去抖动
     /* 初始化定时器 */
    for(i = 0; i < KEY_NUM; i++)
    {        
        key_timer[i].function=key_timer_handler;     //定时器到时间后执行的函数
            key_timer[i].data=i;                 //data的值
        init_timer(&key_timer[i]);           //初始化
        printk("key_timer[i].data is %x ,i is %x ,KEY_NUM is %x \n", key_timer[i].data,i,KEY_NUM);  
    }
    at91sam9260_key_io_init();   // 初始化IO口  

// 定时器处理函数
static void key_timer_handler(unsigned long data)
{
    int key = data;
    //获取当前按键引脚上的电平值来判断按键是按下还是抬起
    int up = at91_get_gpio_value(key_info_tab[key].gpio_port);
    if (!up)
    {
        if (key_dev.keyStatus[key] == KEYSTATUS_DOWNX)          //按键不确定
        {
    //printk("+++key_dev.keyStatus[%d] == KEYSTATUS_DOWNX\n",key);
            key_dev.keyStatus[key] = KEYSTATUS_DOWN;
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY2; //延迟           
            keyEvent(key);  //记录键值,唤醒等待队列
            add_timer(&key_timer[key]);
        }
        else                                                    //按键按下
        {

    //printk("***key_dev.keyStatus[%d] == KEYSTATUS_DOWN\n",key);
    key_dev.keyStatus[key] = KEYSTATUS_DOWN;
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY2; //延迟
    keyEvent(key); //记录键值,唤醒等待队列
            add_timer(&key_timer[key]);//超时,再次执行此函数

        }

    }
    else       //键已抬起
    {
        key_dev.keyStatus[key] = KEYSTATUS_UP;
        enable_irq(key_info_tab[key].irq_no);
    }
}

2、中断

set_irq_type、irq_set_irq_type在下面代码中会出现两个接口,两个接口都是可以用的,但是针对版本的不同会出现莫名错误,所以在编译出错时可进行替换

   // 注册中断函数
 request_irqs()

  // 按键设备驱动的中断申请函数
// 申请系统中断,中断方式为下降沿触发
static int request_irqs(void)
{
    struct key_info *k;
    int i;
    int ret;

    for(i = 0; i < sizeof(key_info_tab)/sizeof(key_info_tab[1]); i++)  
    {
    //printk( "for is action %d\r\n",i);
        k = key_info_tab + i;
        //设置4个IO口为中断下降沿触发方式
        //set_irq_type(key_info_tab[i].irq_no,key_info_tab[i].irq_type);      // 设置中断类型
        irq_set_irq_type(key_info_tab[i].irq_no,key_info_tab[i].irq_type);
        printk(KERN_NOTICE "type key_info_tab[i].irq_type is %d\r\n",key_info_tab[i].irq_type);
        //申请中断(类型为快速中断,中断服务时屏蔽所有外部中断?)将按键序列号作为参数传入中断服务程序        

        ret=request_irq(k->irq_no,at91sam9260_enit_key,0,DEVICE_NAME,(void *)i);        
        if(ret)
        {
            printk(KERN_NOTICE "buttons:ret is %d\r\n",ret);
            return -1;
        }
        printk( "request_irq_%d is ok %x \r\n\n",i,k->irq_no);
    }
    return 0;
}



#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/atmel_pdc.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/semaphore.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/input.h>
#include <linux/errno.h>
#include <linux/irq.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/list.h>


#include <mach/board.h>
#include <mach/gpio.h>
#include <mach/irqs.h>
#include <mach/hardware.h>
#include <mach/at91_pio.h>
#define DEVICE_NAME "key_driver"


#define KEY_TIMER_DELAY1    (HZ/25)             //按键按下去抖延时20毫秒        
#define KEY_TIMER_DELAY2    (HZ/5)             //按键抬起去抖延时100毫秒


#define KEYSTATUS_DOWN              0           //按键按下                    
#define KEYSTATUS_UP                1           //按键抬起                
#define KEYSTATUS_DOWNX             2           //按键不确定  
#define KEY_NUM                     6           // 6个按键


#define DP_MAJOR 0//主设备号
#define DP_MINOR 0  //次设备号
unsigned int key_major = DP_MAJOR;


static struct class *SAM9260_keytest_class = NULL; 
static volatile int ev_press = 0;  //等待队列的条件变量


// 按键驱动的设备结构体、定时器
#define MAX_KEY_BUF     16   // 按键缓存区大小
typedef unsigned char KEY_RET;
// 设备结构体
typedef struct
{
    unsigned int keyStatus[KEY_NUM];    // 6个按键的按键状态
    KEY_RET buf[MAX_KEY_BUF];           //缓存
    unsigned int head,tail;             // 按键缓存区头和尾
    wait_queue_head_t wq;               // 等待队列
    struct cdev cdev;                   //cdev 结构体
}KEY_DEV;
KEY_DEV key_dev,*key_devp;




#define BUF_HEAD (key_dev.buf[key_dev.head]) //缓冲头
#define BUF_TAIL (key_dev.buf[key_dev.tail]) //缓冲尾
#define INCBUF(x,mod)  ((++(x))&((mod)-1)) 
/* 
这个宏定义使得队列操作中指针始终能指到当前队列的头或尾。当队列满了后指重新回到᳔初位置。
比如我们有一个容量为8 的队列,当head 为0 时运算结果为1,head 为1时运算结果为2,当head 为
7 时运算结果为0。
有兴趣的读者自己可以取不同的数运算一次。
它在以后的两行代码中用到:
tedev.head=INCBUF(tsdev.head,MAX_TS_BUF); 和
tedev.tail=INCBUF(tsdev.tail,MAX_TS_BUF); 
*/



static struct timer_list key_timer[KEY_NUM]; // 6个按键去抖定时器


// 按键硬件资源、键值信息结构体
static struct key_info
{
    int irq_no;                         // 中断号
    int irq_type;                       // 中断类型
    unsigned int gpio_port;             // GPIO端口
    int key_no;                         // 键值

}key_info_tab[6]=
{
    // 按键所使用的CPU资源
    {
        //AT91_PIN_PB0,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB0,1      //中断类型为上升沿中断
        AT91_PIN_PB0,IRQF_TRIGGER_FALLING,AT91_PIN_PB0,1            //中断类型为下降沿中断
    },
    {
        //AT91_PIN_PB1,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB1,2
        AT91_PIN_PB1,IRQF_TRIGGER_FALLING,AT91_PIN_PB1,2
    },
    {
        //AT91_PIN_PB2,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB2,3
        AT91_PIN_PB2,IRQF_TRIGGER_FALLING,AT91_PIN_PB2,3
    },
    {
        //AT91_PIN_PB3,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB3,4
        AT91_PIN_PB3,IRQF_TRIGGER_FALLING,AT91_PIN_PB3,4
    },
    {
        //AT91_PIN_PB16,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB16,5
        AT91_PIN_PB16,IRQF_TRIGGER_FALLING,AT91_PIN_PB16,5
    },
    {
        //AT91_PIN_PB17,AT91_AIC_SRCTYPE_FALLING,AT91_PIN_PB17,6
        AT91_PIN_PB17,IRQF_TRIGGER_FALLING,AT91_PIN_PB17,6
    },
};


// 初始化
static void at91sam9260_key_io_init(void)
{
    at91_set_gpio_input(AT91_PIN_PB0, 1);        
    at91_set_deglitch(AT91_PIN_PB0, 1);

    at91_set_gpio_input(AT91_PIN_PB1, 1);        
    at91_set_deglitch(AT91_PIN_PB1, 1);


    at91_set_gpio_input(AT91_PIN_PB2, 1);        
    at91_set_deglitch(AT91_PIN_PB2, 1);

    at91_set_gpio_input(AT91_PIN_PB3, 1);        
    at91_set_deglitch(AT91_PIN_PB3, 1);

    at91_set_gpio_input(AT91_PIN_PB16, 1);        
    at91_set_deglitch(AT91_PIN_PB16, 1);

    at91_set_gpio_input(AT91_PIN_PB17, 1);        
    at91_set_deglitch(AT91_PIN_PB17, 1);

}



/* 记录键值并唤醒等待队列 */
static void keyEvent(unsigned int key)
{
    //printk("keyEvent is action \n");
    //printk("key_info_tab[key].key_no is %x \n",key_info_tab[key].key_no);
    //printk("BUF_HEAD is %x \n",BUF_HEAD);

    BUF_HEAD = key_info_tab[key].key_no;                  // 记录键值
    //printk("BUF_HEAD is %x \n",BUF_HEAD);

    key_dev.head = INCBUF(key_dev.head,MAX_KEY_BUF);      // 调整缓冲区头指针
    wake_up_interruptible(&(key_dev.wq));                 // 唤醒等待队列
    //zmz
}



// 按键设备的中断处理
// 键被按下后,将发生中断,在中断处理程序中,应该关闭中断进入查询模式,延迟20ms以实现去抖动
// 这个中断处理过程只包含顶半部,无底半部
static irqreturn_t at91sam9260_enit_key(int irq,void *dev_id)//,struct pt_regs *reg
{
    int key = (int)dev_id;
    int found = 0;
    int i;
    //ARRAY_SIZE 球设备结构体的设备个数
    //printk(KERN_NOTICE"\n ARRAY_SIZE(key_info_tab)  is %d in button\n", ARRAY_SIZE(key_info_tab));


    for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)   // 查找产生中断的按键
    {
        if (key_info_tab[i].irq_no == irq) 
        {
            printk(KERN_NOTICE"irq %d in button\n", irq);
            found = 1;
            break;
        }
    }
    if (!found)                                     // 没找到
    {
        printk(KERN_NOTICE"bad irq %d in button\n", irq);
        return IRQ_NONE;   //错误的中断
    }

    disable_irq_nosync(key_info_tab[key].irq_no);          // 找到,关闭对应中断    

    //printk(KERN_NOTICE"close irq  in button\n");

    key_dev.keyStatus[key] = KEYSTATUS_DOWNX;  // 不确定是否为按下
    key_timer[key].expires = jiffies + KEY_TIMER_DELAY1;// 延迟
    add_timer(&key_timer[key]);  // 启动定时器  

    //printk(KERN_NOTICE"will return IRQ_HANDLED\n");

    return IRQ_HANDLED;   //正确的中断
}


// 定时器处理函数
static void key_timer_handler(unsigned long data)
{
    int key = data;
    //获取当前按键引脚上的电平值来判断按键是按下还是抬起
    int up = at91_get_gpio_value(key_info_tab[key].gpio_port);
    if (!up)
    {
        if (key_dev.keyStatus[key] == KEYSTATUS_DOWNX)          //按键不确定
        {
            //printk("+++key_dev.keyStatus[%d] == KEYSTATUS_DOWNX\n",key);
            key_dev.keyStatus[key] = KEYSTATUS_DOWN;
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY2; //延迟           
            keyEvent(key);  //记录键值,唤醒等待队列
            add_timer(&key_timer[key]);
        }
        else                                                    //按键按下
        {

            //printk("***key_dev.keyStatus[%d] == KEYSTATUS_DOWN\n",key);
            key_dev.keyStatus[key] = KEYSTATUS_DOWN;
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY2; //延迟
            keyEvent(key); //记录键值,唤醒等待队列
            add_timer(&key_timer[key]);

        }

    }
    else       //键已抬起
    {
        key_dev.keyStatus[key] = KEYSTATUS_UP;
        enable_irq(key_info_tab[key].irq_no);
    }
}




// 按键设备驱动的中断申请函数
// 申请系统中断,中断方式为下降沿触发
static int request_irqs(void)
{
    struct key_info *k;
    int i;
    int ret;


    for(i = 0; i < sizeof(key_info_tab)/sizeof(key_info_tab[1]); i++)  
    {
        //printk( "for is action %d\r\n",i);
        k = key_info_tab + i;
        //设置4个IO口为中断下降沿触发方式
        //set_irq_type(key_info_tab[i].irq_no,key_info_tab[i].irq_type);      // 设置中断类型
        irq_set_irq_type(key_info_tab[i].irq_no,key_info_tab[i].irq_type);
        printk(KERN_NOTICE "type key_info_tab[i].irq_type is %d\r\n",key_info_tab[i].irq_type);
        //申请中断(类型为快速中断,中断服务时屏蔽所有外部中断?)将按键序列号作为参数传入中断服务程序        

        ret=request_irq(k->irq_no,at91sam9260_enit_key,0,DEVICE_NAME,(void *)i);        
        if(ret)
        {
            printk(KERN_NOTICE "buttons:ret is %d\r\n",ret);
            return -1;
        }
        printk( "request_irq_%d is ok %x \r\n\n",i,k->irq_no);
    }
    return 0;
}
// 释放中断
static void free_irqs(void)
{
    struct key_info *k;
    int i;


    for(i = 0; i < sizeof(key_info_tab)/sizeof(key_info_tab[1]); i++)
    {
        k = key_info_tab + i;
        free_irq(k->irq_no,(void *)i);
    }

}


// 按键设备驱动的打开、释放函数
static int at91sam9260_key_open(struct inode *inode, struct file *filp)
{
    key_dev.head = key_dev.tail = 0; //清空按键动作缓冲区
    //keyEvent = keyEvent_raw; //函数指针指向按键处理函数keyEvent_raw

    return 0;
}


static int at91sam9260_key_release(struct inode *inode, struct file *filp)
{
    //keyEvent = keyEvent_dummy; //函数指针指向空函数

    return 0;
}




// 按键设备驱动读函数
static ssize_t at91sam9260_key_read(struct file *filp,char __user *buf,ssize_t count,loff_t*ppos)
{
    unsigned int key_ret;
    unsigned long flags;
retry:
    //printk( "1 key_dev.head  is  %x  ,  \r\n",key_dev.head);
    //printk( "1 key_dev.tail  is  %x  ,  \r\n",key_dev.tail);
    if (key_dev.head != key_dev.tail)                         // 缓冲区有数据?
    {
        local_irq_save(flags);                                      // 进入临界区 ,关中断       
        key_ret = BUF_TAIL;         // 读出键值
        key_dev.tail = INCBUF(key_dev.tail, MAX_KEY_BUF);     // 调整缓冲区尾指针
        local_irq_restore(flags);       // 退出临界区,开中断        
        printk( "**********copy_to_user *********\r\n");
        //printk( "sizeof(unsigned int)  is  %x\r\n",sizeof(unsigned int));
        //printk( "(void *)&key_ret  is  %x\r\n",key_ret);
        //if(copy_to_user(buf, (void *)&key_ret, sizeof(unsigned int)))
        if(copy_to_user(buf, (void *)&key_ret, sizeof(unsigned int)))       // 拷贝到用户空间    
        {
            printk( "copy_to_user is  EFAULT\r\n");
            return -EFAULT;
        }
        else
        {
            //key_dev.head = 0;    zmz
            //key_dev.tail = 0;    zmz
            //printk( "copy_to_user is  SUCCEES\r\n");
            return sizeof(unsigned int);
        }

    }
    else   // 缓冲区没数据
    {
        if (filp->f_flags & O_NONBLOCK)                             // 若采用非阻塞方式读取则返回错误
        {
            return -EAGAIN;
        }
        else  
        {
            interruptible_sleep_on(&(key_dev.wq));                   // 使进程睡眠
            //zmz
        }
        if (signal_pending(current))                               //在这里等中断 
        {   // 如果是信号中断
            return -ERESTARTSYS;
        }
        goto retry;
    }

    return sizeof(unsigned int);
}


// 按键设备驱动文件操作结构体,主要实现打开、释放和读函数
static struct file_operations at91sam9260_key_fops = 
{
    .owner = THIS_MODULE,
    .open = at91sam9260_key_open,            // 启动设备
    .release = at91sam9260_key_release,      // 关闭设备
    .read = at91sam9260_key_read,            // 读取按键的键值
};

// 模块加载函数包括:设备号申请、cdev的添加,另:申请中断、初始化定时器和队列;
// 模块卸载函数恰好相反
static int __init at91sam9260_key_init(void)
{
    int i,err,result;


    // 申请设备号       
    dev_t dev = MKDEV(key_major, 0);  


    /* 申请设备号 */
    if (key_major)
        result = register_chrdev_region(dev, 1, DEVICE_NAME);
    else    /* 动态申请设备号 */
    {
        result = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
        key_major = MAJOR(dev);
        printk(KERN_INFO "Todo: mknod /dev/%s c %d 0\n", DEVICE_NAME, key_major);
    }

    if (result < 0)  
    {  
        printk(DEVICE_NAME " can't register major number\n");  
        return result;  
    }  
    printk("register SAM9260_keytest Driver OK! Major = %d\n", key_major);  


    // 分配设备结构体的内存
    //zmz
    key_devp = kmalloc(sizeof(key_dev),GFP_KERNEL);

    //key_devp = kmalloc(sizeof(key_dev),GFP_ATOMIC);
    if(!key_devp)
    {
        result =-ENOMEM;
        goto fail_malloc;
    }
    // 添加cdev
    memset(key_devp,0,sizeof(key_dev));



    cdev_init(&key_dev.cdev, &at91sam9260_key_fops);
    key_dev.cdev.owner=THIS_MODULE;  
    key_dev.cdev.ops=&at91sam9260_key_fops; 

    //err=cdev_add(&key_dev.cdev, dev, 1)
    err=cdev_add(&key_dev.cdev, MKDEV(key_major, 0), 1);
    if(err)
    {
        printk(KERN_ALERT"Add char dev error!\n");
    } 

    /**自动创建设备文件**/  
    SAM9260_keytest_class=class_create(THIS_MODULE,DEVICE_NAME);  
    if(IS_ERR(SAM9260_keytest_class))  
    {  
        printk("Err: failed in SAM9260_ledtest class. \n");  
        //goto fail_create_class;  
    }  
    device_create(SAM9260_keytest_class, NULL, MKDEV(key_major, 0), NULL, DEVICE_NAME);  


    key_dev.head = key_dev.tail = 0;             // 初始化结构体
    for(i = 0; i < KEY_NUM; i++)
    {
        key_dev.keyStatus[i] = KEYSTATUS_UP;
    }
    init_waitqueue_head(&(key_dev.wq));  // 等待队列




    printk("\n");  
    printk("init  timer \n");  
    // 初始化定时器,实现软件的去抖动
     /* 初始化定时器 */
    for(i = 0; i < KEY_NUM; i++)
    {        
        key_timer[i].function=key_timer_handler;
        key_timer[i].data=i;       
        init_timer(&key_timer[i]);
        printk("key_timer[i].data is %x ,i is %x ,KEY_NUM is %x \n", key_timer[i].data,i,KEY_NUM);  
    }
    at91sam9260_key_io_init();   // 初始化   




    printk("\n");  
    printk("request  irq  action   \n\n");  
     // 注册中断函数
    if(-1 == request_irqs())
    {
        printk(KERN_NOTICE "request_irqs failed!\r\n");
    }
    else
    {
        printk(KERN_NOTICE "request_irqs success!\r\n");
    }

    fail_malloc:unregister_chrdev_region(dev,1);
    //fail_create_class:class_destroy(SAM9260_keytest_class);
    printk(KERN_NOTICE "init  success!\r\n");
    return result;

}


// 按键设备驱动的模块卸载函数
static void __exit at91sam9260_key_exit(void)
{
    free_irqs();    // 注销中断
    // 释放设备号,删除cdev
    cdev_del(&key_dev.cdev);   // 删除字符设备结构体
    kfree(key_devp);
    unregister_chrdev_region(MKDEV(key_major,DP_MINOR),1);  // 删除字符设备
    del_timer(&key_timer[0]);
    del_timer(&key_timer[1]);
    del_timer(&key_timer[2]);
    del_timer(&key_timer[3]);
    del_timer(&key_timer[4]);
    del_timer(&key_timer[5]);

}



MODULE_LICENSE("GPL");
module_init(at91sam9260_key_init);
module_exit(at91sam9260_key_exit); 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值