广告

两个linux按键驱动之二 read(定时器去抖动)

2009-9-4 16:17 4358 0 分类: 软件与OS

//相比之下poll更为实用,但是这个代码实在太具有代表性了...涉及了下面几个知识。代码很大部分是linux设备驱动开发详解的内容。


/*
1、阻塞读取,队列概念
2、睡眠等待中断产生
3、定时器产生及相应函数
疑问:去除按键缓冲 
*/

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-irq.h>
#define KEYSTATUS_DOWNX 2                   // 定义按键不确定状态
#define KEYSTATUS_DOWN 0                    // 定义按键按下后电平
#define KEYSTATUS_UP 1                      // 定义按键抬起后电平
#define BUF_CLEAR _IO(0xFF, 0)              // 清除键盘缓冲区命令


#define DEVICE_NAME "utukey"
#define MAX_KEY_BUF 16      // 按键缓冲区大小
#define KEY_NUM  6          // 按键个数
#define BUF_HEAD (utukey_dev.buf[utukey_dev.head]) //缓冲头
#define BUF_TAIL (utukey_dev.buf[utukey_dev.tail]) //缓冲尾
#define ISKEY_DOWN(key) (s3c2410_gpio_getpin(key_info_tab[key].gpio_port) == KEYSTATUS_DOWN)
#define INCBUF(x,mod) ((++(x))&((mod)-1))
#define KEY_TIME_DELAY (HZ/10)      // 100ms
#define KEY_TIME_DELAY1 (HZ/100)    // 10ms


#define UTUKEY_MAJOR 0                      // 定义0使用自动分配设备号
unsigned int utukey_major = UTUKEY_MAJOR;
/* 定义按键设备结构体 */
struct utukey_dev
{
    struct cdev cdev;                       // cdev结构体
    unsigned int key_status[KEY_NUM];       // 记录按键状态
    unsigned int buf[MAX_KEY_BUF];          // 按键环形缓冲区
    unsigned int head,  tail;               // 按键缓冲区头和尾
    wait_queue_head_t wq;                   // 等待队列
};
struct utukey_dev utukey_dev;               // 定义设备结构体
struct utukey_dev *utukey_devp;             // 定义设备结构体指针
struct timer_list key_timer[KEY_NUM];       // 定义6个按键去抖动定时器
static struct key_info                      // 定义按键所用资源结构体
{
    int irq_no;                             // 占用的中断号
    int irq_type;                           // 中断类型
    unsigned int gpio_port;                 // 占用的引脚
    unsigned int gpio_setting;              // 引脚设置值
    int key_code;                           // 按键值
    char *name;                             // 按键的对应字符串
}key_info_tab[] =
{
    {
        IRQ_EINT11, IRQT_FALLING, S3C2410_GPG3, S3C2410_GPG3_INP, 1, "Key Up" //下降沿触发
    },
    {
        IRQ_EINT0, IRQT_FALLING, S3C2410_GPF0, S3C2410_GPF0_INP, 2, "Key Down"
    },
    {
        IRQ_EINT19, IRQT_FALLING, S3C2410_GPG11,S3C2410_GPG11_INP, 3, "Key Left"
    },
    {
        IRQ_EINT2, IRQT_FALLING, S3C2410_GPF2,S3C2410_GPF2_INP, 4, "Key Right"
    },
    {
        IRQ_EINT6, IRQT_FALLING, S3C2410_GPF6,S3C2410_GPF6_INP, 5, "Key Enter"
    },
    {
        IRQ_EINT5, IRQT_FALLING, S3C2410_GPF5,S3C2410_GPF5_INP, 6, "Key Exit"
    },
};


/* 按键中断服务程序 */
static irqreturn_t utukey_irq(int irq, void *dev_id)
{
    int key = (int)dev_id;      // 传递key_info_tab[]索引
    int i;
    int found = 0;
   
    for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)   // 查找产生中断的按键
    {
        if (key_info_tab[i].irq_no == irq)
  {
            found = 1;
            break;
        }
    }
    if (!found)                                     // 没找到
    {
        printk(KERN_NOTICE"bad irq %d in button\n", irq);
        return IRQ_NONE;   //错误的中断
    }
    disable_irq(key_info_tab[key].irq_no);          // 找到,关闭对应中断
    utukey_dev.key_status[key] = KEYSTATUS_DOWNX;   // 按键处于不确定状态
    key_timer[key].expires = jiffies + KEY_TIME_DELAY1;     // 去抖动延时
    add_timer(&key_timer[key]);    //add_timer
    return IRQ_HANDLED;   //正确的中断
}


/* 申请irq中断 */
static int request_irqs(void)
{
    int i;
    for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)
    {
        s3c2410_gpio_cfgpin(key_info_tab[i].gpio_port, key_info_tab[i].gpio_setting);   // 设置按键引脚的模式(输入)
        set_irq_type(key_info_tab[i].irq_no, key_info_tab[i].irq_type);      // 设置中断类型
        if (request_irq(key_info_tab[i].irq_no, utukey_irq, SA_INTERRUPT, DEVICE_NAME, (void *)i)) // 向内核注册中断
        {
            printk(KERN_WARNING "buttons:can't get irq no.%d\n", key_info_tab[i].irq_no);           
            return 1;
        }
    }
    return 0;
}


/* 释放irq中断 */
static void free_irqs(void)
{
    int i;
    for (i = 0; i < ARRAY_SIZE(key_info_tab); i++)
    {
        free_irq(key_info_tab[i].irq_no, (void *)i);
    }
}


/* 记录键值并唤醒等待队列 */
static void keyEvent(unsigned key)
{
    BUF_HEAD = key_info_tab[key].key_code;                  // 记录键值
    utukey_dev.head = INCBUF(utukey_dev.head, MAX_KEY_BUF); // 调整缓冲区头指针
    wake_up_interruptible(&(utukey_dev.wq));                // 唤醒等待队列
}


/* 驱动读函数 */
static ssize_t utukey_read(struct file *filp,char __user *buffer, size_t count, loff_t *ppos)
{
    unsigned int key_ret;
    unsigned long flags;
retry:
    if (utukey_dev.head != utukey_dev.tail)                         // 缓冲区有数据?
    {
        local_irq_save(flags);                                      // 进入临界区 ,关中断      
        key_ret = BUF_TAIL;                                         // 读出键值
        utukey_dev.tail = INCBUF(utukey_dev.tail, MAX_KEY_BUF);     // 调整缓冲区尾指针
        local_irq_restore(flags);                                   // 退出临界区,开中断       
        copy_to_user(buffer, &key_ret, sizeof(unsigned int));       // 拷贝到用户空间
        return sizeof(unsigned int);
    }else   // 缓冲区没数据
    {
        if (filp->f_flags & O_NONBLOCK)                             // 若采用非阻塞方式读取则返回错误
        {
            return -EAGAIN;
        }
        interruptible_sleep_on(&(utukey_dev.wq));                   // 使进程睡眠
        if (signal_pending(current))                               //在这里等中断
        {   // 如果是信号中断
            return -ERESTARTSYS;
        }
        goto retry;
    }
   
    return sizeof(unsigned int);
}


/* ioctl设备控制函数 */
static int utukey_ioctl(struct inode *inodep, struct file *filp, unsigned
  int cmd, unsigned long arg)
{
    unsigned long flags;
   
    switch (cmd)
    {
        case BUF_CLEAR:                             // 清除按键缓冲区
     local_irq_save(flags);     
        utukey_dev.head = utukey_dev.tail = 0;
        local_irq_restore(flags);
        
        printk(KERN_INFO "key buffer is cleared\n");     
        break;


        default:
        return  -EINVAL;
    }
    return 0;
}


/* 定时器中断回调函数 */
static void utukey_timer_callback(unsigned long data)  //定时器时间到了调用  参数代表哪个定时器
{
    int key = data;
    if (ISKEY_DOWN(key))                                    // 按键处于按下状态?
    {
        if (utukey_dev.key_status[key] == KEYSTATUS_DOWNX)  // 已经延时10ms,完成去抖动?
        {
            utukey_dev.key_status[key] = KEYSTATUS_DOWN;   
            key_timer[key].expires = jiffies + KEY_TIME_DELAY; 
            keyEvent(key);                                  // 记录键值,唤醒等待队列
            add_timer(&key_timer[key]);     //抬起按键去抖动延时
        }else
        {   // 已经完成去抖动延时
            key_timer[key].expires = jiffies + KEY_TIME_DELAY;
            add_timer(&key_timer[key]);
        }
    }else
    {   // 按键已经抬起
        utukey_dev.key_status[key] = KEYSTATUS_UP;
        enable_irq(key_info_tab[key].irq_no);
    }
}


static int utukey_open(struct inode *inode, struct file *filp)
{
    printk(KERN_NOTICE "utukey opened\n");
    return 0;
}


static int utukey_release(struct inode *inode, struct file *filp)
{
    printk(KERN_NOTICE "utukey released\n");
    return 0;
}


static const struct file_operations utukey_fops =
{
    .owner = THIS_MODULE,
    .read  = utukey_read,
    .ioctl = utukey_ioctl,
    .open  = utukey_open,
    .release = utukey_release,
};


/* 初始化并注册cdev */
static void utukey_setup_cdev(void)
{
    int err,devno = MKDEV(utukey_major,0);
    cdev_init(&utukey_dev.cdev,&utukey_fops);
    utukey_dev.cdev.owner = THIS_MODULE;
    utukey_dev.cdev.ops = &utukey_fops;
    err = cdev_add(&utukey_dev.cdev, devno, 1);
    if (err)
        printk(KERN_NOTICE "Error %d adding utukey", err);
}


/* 初始化函数 */
static int __init utukey_init(void)
{
    int result, i;
    dev_t devno = MKDEV(utukey_major,0);    // 用主次设备号生成设备号
    /* 申请中断 */
    result = request_irqs();
    if (result) {
        unregister_chrdev_region(devno,1);
        return result;
    }
    /* 申请设备号 */
    if (utukey_major)
        result = register_chrdev_region(devno, 1, DEVICE_NAME);
    else    /* 动态申请设备号 */
    {
        result = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
        utukey_major = MAJOR(devno);
        printk(KERN_INFO "Todo: mknod /dev/%s c %d 0\n", DEVICE_NAME, utukey_major);
    }


    if (result < 0)
        return result;


    /* 动态申请设备结构体的内存 */
    utukey_devp = kmalloc(sizeof(struct utukey_dev), GFP_KERNEL);
    if (!utukey_devp)    /* 申请失败 */
    {
        result = -ENOMEM;
        goto fail_malloc;
    }


    memset(utukey_devp, 0, sizeof(struct utukey_dev));      /* 清零分配的设备结构体内存 */
    utukey_setup_cdev();
    init_waitqueue_head(&(utukey_dev.wq));                  /* 初始化等待队列头 */
    utukey_dev.head = utukey_dev.tail = 0;


    /* 初始化按键状态 */   
    for(i = 0; i < KEY_NUM; i++)
    {
        utukey_dev.key_status[i] = KEYSTATUS_UP;
    }


    /* 初始化定时器 */
    for(i = 0; i < KEY_NUM; i++)
    {       
        key_timer[i].function = utukey_timer_callback;
        key_timer[i].data = i;
        init_timer(&key_timer[i]);
    }
    return 0;


    fail_malloc: unregister_chrdev_region(devno, 1);
    return result;
}


/* 退出函数 */
static void __exit utukey_exit(void)
{
    int i;
    cdev_del(&utukey_dev.cdev);                             // 注销cdev
    kfree(utukey_devp);                                     // 释放结构体内存
    unregister_chrdev_region(MKDEV(utukey_major, 0), 1);    // 释放设备号
    free_irqs();                                            // 释放中断
    for(i = 0; i < KEY_NUM; i++)                            // 注销定时器
    {
        del_timer(&key_timer[i]);
    }
}


MODULE_AUTHOR("lxm<lxm650@163.com>");
MODULE_LICENSE("Dual BSD/GPL");


module_init(utukey_init);
module_exit(utukey_exit);


//测试程序


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>


#define BUF_CLEAR _IO(0xFF, 0)


int main(void)
{
    int buttons_fd;
    int key_value;


    buttons_fd = open("/dev/utukey", 0);
    if (buttons_fd < 0) {
        perror("cann't open device /dev/utukey");
        exit(1);
    }
    else
    {
        if (ioctl(buttons_fd, BUF_CLEAR, 0) < 0)
            printf(" ioctl command failed\n");
    }
    
    while(1)


{
        int ret = read(buttons_fd, &key_value, sizeof key_value);
        printf("You pressed buttons %d\n", key_value);
    }


    close(buttons_fd);
    return 0;
}

广告

文章评论 0条评论)

登录后参与讨论
相关推荐阅读
qiu_1123_510649736 2009-09-04 16:08
两个linux按键驱动之一 poll(未去抖动)
//驱动//未加去抖动//通过poll判断是否可读来得到中断值#include <linux/module.h> #include <linux/kernel.h>#inclu...
qiu_1123_510649736 2009-09-04 09:39
select 使用
以下来自网络搜索:Linux下select调用的过程:<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:offi...
qiu_1123_510649736 2009-09-03 14:52
Linux设备驱动之定时器
Linux内核中定义了一个timer_list结构,我们在驱动程序中可以利用之: #include<linux/timer.h> struct timer_list { struct li...
qiu_1123_510649736 2009-08-28 17:38
loff_t *ppos是什么东东
ssize_t generic_file_read(struct file * filp, char * buf, size_t count, loff_t *ppos) 这是一个文件读函数 我们很容...
qiu_1123_510649736 2009-08-12 17:30
linux 延时函数几个资料
http://www.cppblog.com/CppExplore/archive/2008/04/02/46111.html一、 基础知识1、时间类型。Linux下常用的时间类型有4个:time_t...
我要评论
0
0
广告